Correct type for Canvas element - flowtype

I am trying to figure out the correct types for canvas element.
Whatever I do - it does not work. I have tried all possible html element types but flow says they are incompatible. So why cant I assign canvas element to HTMLCanvasElement variable?
Any ideas?
The node parameter is a div element which contains canvas element. It is simple really. If I strip the types, everything works.
export class Graph extends Component {
draw = (node: HTMLDivElement) => {
let canvas: HTMLCanvasElement = node.firstChild
if (canvas){
canvas.width = parseInt(getComputedStyle(node).getPropertyValue('width'))
canvas.height = parseInt(getComputedStyle(node).getPropertyValue('height'))
}
let chart = new Chart(canvas, {
type: this.props.type,
data: this.props.data
})
}
render = () => (
<div ref={this.draw} className='graph'>
<canvas />
</div>
)
}
I get these errors from flow linter:
Node This type is incompatible with HTMLCanvasElement
null This type is incompatible with HTMLCanvasElement
undefined This type is incompatible with HTMLCanvasElement
Thanks
P.S. It is Inferno - not React. So the ref is a function, not a string. Just to avoid people correcting this.

It looks like HTMLDivElement is not defined in Flow internals:
https://github.com/facebook/flow/blob/v0.43.1/lib/dom.js#L2811
HTMLDivElement and HTMLCanvasElement are sibling types (both children of HTMLElement), so naturally Flow will say it's unsafe to try to cast HTMLDivElement to HTMLCanvasElement since you generally never want to do this in any typed system.
You can beat Flow's type system and get around this by casting node.firstChild to any. This is usually not recommended, but seems acceptable in this case since HTMLDivElement doesn't give you anything and you definitely know what it is.
let canvas: HTMLCanvasElement = (node.firstChild: any)
feel free to also view the flowtype.org/try link

Related

Reveal Timestamp React Native (iMessage Clone)

I was wondering how I would go about dragging/sliding left to reveal the timestamp on react native. I am using a flatlist in react-native and I have the timestamp data but I am unsure of how to render the timestamps on slight drag. Anyone have detailed ideas on how to do this. I have included images of current implementation and iMessage slide/drag. This feature is also on Instagram (ios version at least). I should add that I'm not trying to do a drag and drop feature more like just a solution review what is not currently in the view from a slide/drag
Current App
Current iMessage
This is the eventual solution i came up with all credit from #Abe and reading the gesture-handler documentation referenced above. I
import Animated, { useAnimatedStyle, useSharedValue, withSpring } from 'react-native-reanimated'
const translateX = useSharedValue(0)
const startX = useRef(0)
My flatlist renders elements like this now
<Animated.View
className="mx-2 flex-1 flex-row"
style={sty}
onTouchStart={(e) => {
startX.current = e.nativeEvent.pageX
}}
onTouchMove={(e) => {
const delta = startX.current - e.nativeEvent.pageX
if (delta > 0) {
translateX.value = -delta / 2
}
}}
onTouchEnd={() => {
translateX.value = withSpring(0)
}}>
...
</Animated.View>
The most common way to handle this is with a PanGestureHandler from react-native-gesture-handler and an Animated.View from react-native-reanimated.
Wrap your component in one, and make the View that you want to move into an Animated.View. (The component that the PanGestureHandler wraps also should be an Animated.View.) Create a shared value (from Reanimated) that represents the x offset of the component. Then create a handler method that responds to the drag gesture and changes the shared value. You may want to add some limits, like not scrolling past a certain offset in either direction. Then, use the shared value in an animated style (something like left: offsetX,) that you apply to your Animated.View.
Since you're using this inside a scrollable list, make sure to set the activeOffsetX prop to something like [-5, 5]. This means that if the user is only trying to scroll up and down, your gesture handler won't steal that touch.
The example in the gesture-handler docs should help.

Optimizing updating css classes for array of HTMLLIElement's

I have an array of HTMLLIElement that get's it's CSS class set based on a search input value. As soon as the array size becomes large it starts to lock and slow down, and I wonder how to optimize it's execution. I believe the comparison on each li elements text is what makes it slow. Code Follows:
// Takes the input, get the LI elements in it's context and converts them to an array of HTMLLIElement
const searchValue = myHappySeachInput.toLowerCase();
const selectListItems = myHappySelectList.querySelectorAll("li");
const myHappyArray = Array.from(selectListItems);
// ForEach element in the array do a comparison of the (beginning) search input against the innerText
// and add/remove css class accordingly.
myHappyArray.forEach((li: HTMLLIElement) => {
const label = li.querySelector("label");
const text = label ? label.innerText.toLowerCase() : "";
if (text.indexOf(searchValue) < 0) {
li.classList.add("display-none");
} else {
li.classList.remove("display-none");
}
});
Hope there's a better way to do this. Thanks for reading and have a nice day!
EDIT: Thanks to CBroe, I focused on separating the look-up and assignment and ended up with something like this:
myHappyArray.forEach((li) => li.classList.add("display-none"));
const matchingLiElements = myHappyArray.filter((li) => li.querySelector("label")?.innerText.toLowerCase().includes(searchValue));
matchingLiElements.forEach((li) => li.classList.remove("display-none"));
It's not perfect, because we still need update all array elements css class every time, but filtering and re-assigning only on the matching elements already reduced the time by 80-90%.

How do I get Elm animator to animate SVG elements using CSS animations?

I'm trying to do CSS animation in Elm, and I just can't get it to work!
Elm has several animation packages. The one I'm attempting to use is mdgriffith/elm-animator. Sadly, like many Elm packages, its documentation is sparse. It appears it provides two ways to render: one by running an Elm event loop to do a DOM update each frame, and one using CSS animation [which I didn't realise even existed]. I'm trying to do the latter.
I've tried to shrink the code down to a minimal example:
module Main exposing (main)
import Time
import Platform.Cmd
import Browser
import Html
import Html.Attributes
import Html.Events
import Color
import Svg
import Svg.Attributes
import Animator
import Animator.Css
main =
Browser.document
{
init = \ () -> ({fill = Animator.init <| Color.rgb 1 0 0}, Platform.Cmd.none),
subscriptions = \ state -> Animator.toSubscription Tick state animator,
view = \ state -> {title = "Animation test", body = view state},
update = \ msg state -> (update msg state, Platform.Cmd.none)
}
type alias State = { fill : Animator.Timeline Color.Color }
animator : Animator.Animator State
animator =
Animator.animator
|> Animator.Css.watching (\ state -> state.fill) (\ c state -> { state | fill = c })
type Msg = Tick Time.Posix | DoSomething
update : Msg -> State -> State
update msg state0 =
case msg of
Tick t -> Animator.update t animator state0
DoSomething -> { state0 | fill = Animator.go Animator.slowly (Color.rgb 1 1 0) state0.fill }
view : State -> List (Html.Html Msg)
view state0 =
[
Html.button [Html.Events.onClick DoSomething] [Html.text "Do something"],
Svg.svg
[
Svg.Attributes.width "100px",
Svg.Attributes.height "100px"
]
[
Animator.Css.node "circle"
state0.fill
[
Animator.Css.color
"fill"
(\ x -> x)
]
[
Svg.Attributes.cx "50",
Svg.Attributes.cy "50",
Svg.Attributes.r "50"
]
[]
]
]
I appreciate that's still pretty big, but I don't see how to easily make it much shorter without obfuscating what it's doing.
When I run this, the SVG fails to render. When I click the button, nothing happens.
Here's the really weird part: If I open the Firefox Inspector window, I can see the SVG <circle> element. If I edit its various properties, nothing happens. However, if I right-click the <circle> and select "Edit as HTML...", then add a single space to the element text and click off it, suddenly the circle appears! Moreover, if I right-click the element again, now it shows as "Edit as SVG...", which is suspicious.
Even more fun: If I load the page, click the button, and then do the above trick, the colour animation runs!
Note that if I edit the HTML and change nothing, it doesn't work. I have to make a trivial change to the text (or else I guess the browser decides it doesn't need to reprocess?)
I was so convinced this was going to end up being a weird Firefox bug... but then I tried it in Chrome and Edge, and it does exactly the same thing!
Does anybody have to vaguest clue why this doesn't work? Have I done something wrong with the Elm code? (I'm really, really struggling to figure out how this library works; I'm basically just guessing how the types fit together. So maybe I've done something dumb.)
This is due to a weird thing going on with namespaces. In an HTML document (i.e. on any webpage), the default namespace is the HTML namespace and if you want to render embedded documents in other formats, you need to ensure the nodes are created in the correct namespace.
Now when you write HTML as a string and the browser parses it from text, it will do this automatically for you:
<div> <!-- HTML Namespace -->
<svg>
<circle /> <!-- SVG Namespace -->
</svg>
<math>
<mrow></mrow> <!-- MathML namespace
(not really relevant, just pointing out different NS) -->
</math>
</div>
However, when you are constructing nodes dynamically (i.e. from JS or Elm), you need to explicitly ask for the namespace. That is in JS you would use Document.createElementNS() instead of just Document.createElement(). If you look at the source of the elm/svg package you will see that it does this:
node : String -> List (Attribute msg) -> List (Svg msg) -> Svg msg
node =
VirtualDom.nodeNS "http://www.w3.org/2000/svg"
If you don't do this, the browser understands you want to create a new HTML element called circle, which the browser doesn't know about. If the browser doesn't know about an element, it just treats basically like a <div>.
Now if you look at the source of the elm-animator package, you'll notice that it asks for an Html.node, so no namespace!
As to how to make it work with elm-animator, I suspect you can't. But perhaps someone else can contribute some solution...
Finally, you might want to consider SMIL style animation, it tends to get the job done without needing any external package.

Using react-Beautiful-dnd, how can you change draggable dimensions before the drag begins (onBeforeCapture)

This is the code I've found to retrieve the correct draggable element and edit it's dimensions, during the onBeforeCapture() responder. Changing dimensions during this responder is in accordance with the documentation. This seems to work in only changing the dimensions, the other problems is that I am using the renderClone method, and so the draggable is just dragging with a huge offset that is not close to the correct mouse position. Also dnd is treating the drop placeholder as if the draggable is the original large size. Is there any way to correct for this mouse position, and placeholder dimensions? I've looked into adding mouseDown/mouseUp handlers on the inner element of the draggable but that doesn't seem to work either.
const cardSize = 140;
const draggableAttr = "data-rbd-drag-handle-draggable-id";
const getAttr = (key: string, value: string) => `[${key}=${value}]`;
const draggableQuery = getAttr(draggableAttr, update.draggableId);
const draggable = document.querySelector(draggableQuery);
draggable.setAttribute("style", `width: ${cardSize}px; height: ${cardSize}px;`);
I noticed onBeforeCapture was triggering after onDragEnd (therefore resizing the draggable improperly), so I created some state to remember the last beforeCaptureResult and return if it is equivalent to the current result.

Recursive Observer Dependencies?

My model is a tree structure where the position of the child object is relative to its parent.
Basically, it looks like this:
var Node = Ember.Object.extend({
parent: null,
location: null,
absoluteLocation: Ember.computed('location', 'parent', 'parent.absoluteLocation', function() {
var parent = this.get('parent');
if(parent) {
return this.get('location') + parent.get('absoluteLocation');
} else {
return this.get('location');
}
})
});
The parent property is another instance of the Node class.
In theory, this should recursively update all children's absoluteLocation when the parent's location is updated. However, this is not what I'm seeing. Right now in my app I have a location and absoluteLocation of -39 for the main node, a location of -116 for the child and an absolute location of -56 for the same child. This doesn't add up.
Could it be that there's a bug in Ember somewhere, or am I on the wrong track in finding the issue?
Note that I have simplified the above example, in reality this is a Ember Data object and the location is a two-dimensional object. Also, the absoluteLocation property is defined in a mixin or using reopen (tried both). I hope that I didn't “fix” it by removing those details.
This is on Ember 1.13.2.
It turned out to be an ember issue. It got better after updating to 2.0, but still isn't fully fixed.
I ended up not using property dependencies to work around this, instead triggering the refresh manually.

Resources