@threlte/flex 0.0.7 → 0.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,8 +2,8 @@
2
2
  import { onDestroy } from 'svelte';
3
3
  import { Group } from 'three';
4
4
  import { useFlex } from '../Flex/context';
5
- import { createNodeContext } from '../nodes/context';
6
5
  import { createUseDimensionsContext } from '../hooks/useDimensions';
6
+ import { createNodeContext } from '../nodes/context';
7
7
  export let order = undefined;
8
8
  let _class = '';
9
9
  export { _class as class };
@@ -13,10 +13,18 @@ const dispatch = createRawEventDispatcher();
13
13
  */
14
14
  const dimensionsContext = createUseDimensionsContext();
15
15
  const { scaleFactor, onEvent, addNode, removeNode, updateNodeProps, mainAxis, crossAxis, depthAxis, classParser, reflow } = useFlex();
16
- export const group = new Group();
17
- export const contentGroup = new Group();
16
+ const group = new Group();
18
17
  group.userData.isNode = true;
19
- export const { node } = createNodeContext(order);
18
+ const contentGroup = new Group();
19
+ const { yoga } = useFlex();
20
+ const node = yoga.Node.create();
21
+ const parentNodeContext = createNodeContext(node);
22
+ parentNodeContext?.insertNode(node, order);
23
+ onDestroy(() => {
24
+ parentNodeContext?.removeNode(node);
25
+ });
26
+ // update the order of the node
27
+ $: parentNodeContext?.updateNodeOrder(node, order);
20
28
  addNode(node, group, $$restProps);
21
29
  updateNodeProps(node, { ...classParser?.(_class, {}), ...$$restProps }, true);
22
30
  $: updateNodeProps(node, { ...classParser?.(_class, {}), ...$$restProps });
@@ -2,7 +2,7 @@ import { SvelteComponent } from 'svelte'
2
2
  import type { NodeProps } from '../lib/props'
3
3
 
4
4
  type BoxProps = NodeProps & {
5
- order?: number
5
+ order?: number | undefined
6
6
  class?: string
7
7
  }
8
8
 
@@ -2,13 +2,14 @@
2
2
  import { onDestroy } from 'svelte';
3
3
  import { Box3, Group, Vector3 } from 'three';
4
4
  import { Direction } from 'yoga-layout';
5
+ import { createUseDimensionsContext } from '../hooks/useDimensions';
5
6
  import { getDepthAxis } from '../lib/getDepthAxis';
6
7
  import { getOrientedBoundingBoxSize } from '../lib/getOrientedBoundingBoxSize';
7
8
  import { getRootShift } from '../lib/getRootShift';
8
9
  import { applyNodeProps } from '../lib/props';
10
+ import { propsChanged } from '../lib/propsChanged';
9
11
  import { createNodeContext } from '../nodes/context';
10
12
  import { createFlexContext } from './context';
11
- import { createUseDimensionsContext } from '../hooks/useDimensions';
12
13
  export let yoga;
13
14
  export let width = 1;
14
15
  export let height = 1;
@@ -90,25 +91,13 @@ const flexContext = createFlexContext({
90
91
  },
91
92
  updateNodeProps(node, props, force = false) {
92
93
  const nodeData = flexContext.nodes.get(node);
93
- if (!nodeData)
94
- return;
95
94
  // Updating the props can be forced and is done so on the initial call.
96
- if (!force) {
97
- // Because all NodeProps are primitive types, we can make a simple
98
- // comparison and only request a reflow when necessary. We do that by
99
- // checking the length of the props object and then checking if all keys
100
- // are the same and all values are the same.
101
- const previousKeys = Object.keys(nodeData.props);
102
- const currentKeys = Object.keys(props);
103
- if (previousKeys.length === currentKeys.length &&
104
- currentKeys.every((key) => previousKeys.includes(key)) &&
105
- previousKeys.every((key) => nodeData.props[key] === props[key])) {
106
- return;
107
- }
95
+ if (force || propsChanged(node, props)) {
96
+ applyNodeProps(node, props, scaleFactor);
97
+ reflow();
98
+ if (nodeData)
99
+ nodeData.props = props;
108
100
  }
109
- applyNodeProps(node, props, scaleFactor);
110
- nodeData.props = props;
111
- reflow();
112
101
  },
113
102
  removeNode(node) {
114
103
  flexContext.nodes.delete(node);
@@ -124,13 +113,14 @@ const flexContext = createFlexContext({
124
113
  reflow,
125
114
  classParser
126
115
  });
116
+ const rootNode = yoga.Node.create();
117
+ createNodeContext(rootNode);
127
118
  const { mainAxis, crossAxis, depthAxis } = flexContext;
128
- const { node: rootNode } = createNodeContext();
129
119
  $: rootNode.setWidth(width * scaleFactor), rootNode.setHeight(height * scaleFactor);
130
- $: {
131
- applyNodeProps(rootNode, { ...classParser?.(_class, {}), ...$$restProps }, scaleFactor);
132
- reflow();
133
- }
120
+ // prettier-ignore
121
+ flexContext.updateNodeProps(rootNode, { ...classParser?.(_class, {}), ...$$restProps }, true);
122
+ // prettier-ignore
123
+ $: flexContext.updateNodeProps(rootNode, { ...classParser?.(_class, {}), ...$$restProps });
134
124
  $: flexContext.rootWidth.set(width), flexContext.reflow();
135
125
  $: flexContext.rootHeight.set(height), flexContext.reflow();
136
126
  $: flexContext.mainAxis.set(plane[0]), flexContext.reflow();
@@ -0,0 +1,8 @@
1
+ import type { NodeProps } from './props';
2
+ /**
3
+ * Because all NodeProps are primitive types, we can make a simple comparison
4
+ * and only request a reflow when necessary. We do that by checking the length
5
+ * of the props object and then checking if all keys are the same and all values
6
+ * are the same.
7
+ */
8
+ export declare const propsChanged: (node: Node, props: NodeProps) => boolean;
@@ -0,0 +1,26 @@
1
+ const nodePropsMap = new WeakMap();
2
+ /**
3
+ * Because all NodeProps are primitive types, we can make a simple comparison
4
+ * and only request a reflow when necessary. We do that by checking the length
5
+ * of the props object and then checking if all keys are the same and all values
6
+ * are the same.
7
+ */
8
+ export const propsChanged = (node, props) => {
9
+ // get a reference to the props data for this node
10
+ const propsData = nodePropsMap.get(node);
11
+ // assume that the props have changed
12
+ let changed = true;
13
+ if (propsData) {
14
+ // compare the keys and values of the previous and current props
15
+ const previousKeys = Object.keys(propsData);
16
+ const currentKeys = Object.keys(props);
17
+ if (previousKeys.length === currentKeys.length &&
18
+ currentKeys.every((key) => previousKeys.includes(key)) &&
19
+ previousKeys.every((key) => propsData[key] === props[key])) {
20
+ changed = false;
21
+ }
22
+ }
23
+ // update the props data for this node
24
+ nodePropsMap.set(node, props);
25
+ return changed;
26
+ };
@@ -1,9 +1,8 @@
1
1
  import type { Node } from 'yoga-layout';
2
2
  export type NodeContext = {
3
- node: Node;
4
- insertChild: (child: Node, order?: number) => void;
5
- removeChild: (child: Node) => void;
3
+ insertNode: (childNode: Node, order?: number) => void;
4
+ removeNode: (childNode: Node) => void;
5
+ updateNodeOrder: (childNode: Node, order?: number) => void;
6
6
  };
7
7
  export declare const nodeContextName = "__threlte-node";
8
- export declare const useNode: () => NodeContext;
9
- export declare const createNodeContext: (order?: number) => NodeContext;
8
+ export declare const createNodeContext: (node: Node) => NodeContext | undefined;
@@ -1,31 +1,103 @@
1
- import { getContext, onDestroy, setContext } from 'svelte';
2
- import { useFlex } from '../Flex/context';
1
+ import { getContext, setContext } from 'svelte';
2
+ import { useReflow } from '..';
3
3
  export const nodeContextName = '__threlte-node';
4
- export const useNode = () => {
5
- return getContext(nodeContextName);
6
- };
7
- export const createNodeContext = (order) => {
8
- const { yoga } = useFlex();
9
- const node = yoga.Node.create();
10
- const parentNodeContext = useNode();
11
- parentNodeContext?.insertChild(node, order);
12
- onDestroy(() => {
13
- parentNodeContext?.removeChild(node);
14
- });
15
- const data = {
16
- node,
17
- insertChild(child, order) {
4
+ export const createNodeContext = (node) => {
5
+ const reflow = useReflow();
6
+ /** Set to keep track of all child nodes */
7
+ const childNodes = new Set();
8
+ /** Map to keep track of the requested order of nodes */
9
+ const childNodesOrderMap = new Map();
10
+ const parentNodeContext = getContext(nodeContextName);
11
+ const removeAllChildNodes = () => {
12
+ childNodes.forEach((childNode) => {
13
+ node.removeChild(childNode);
14
+ });
15
+ };
16
+ setContext(nodeContextName, {
17
+ insertNode(childNode, order) {
18
+ // we want to keep track of all child nodes
19
+ childNodes.add(childNode);
20
+ // Additionally, we need to keep track of child nodes that need to be
21
+ // inserted at a specific order
18
22
  if (order !== undefined) {
19
- data.node.insertChild(child, order);
23
+ childNodesOrderMap.set(childNode, {
24
+ requestedOrder: order
25
+ });
26
+ }
27
+ if (childNodesOrderMap.size) {
28
+ // we need to sort the child nodes by their requested order. We leave
29
+ // the nodes that don't have a requested order untouched.
30
+ const sorted = Array.from(childNodes)
31
+ .map((node, index) => {
32
+ return {
33
+ order: childNodesOrderMap.get(node)?.requestedOrder ?? index,
34
+ node
35
+ };
36
+ })
37
+ .sort((a, b) => a.order - b.order)
38
+ .map(({ node }) => node);
39
+ // Then we need to remove all child nodes from the node and insert them
40
+ // in the correct order.
41
+ removeAllChildNodes();
42
+ sorted.forEach((childNode, index) => {
43
+ node.insertChild(childNode, index);
44
+ });
20
45
  }
21
46
  else {
22
- data.node.insertChild(child, data.node.getChildCount());
47
+ node.insertChild(childNode, node.getChildCount());
23
48
  }
49
+ reflow();
50
+ },
51
+ removeNode(childNode) {
52
+ node.removeChild(childNode);
53
+ childNodes.delete(childNode);
54
+ childNodesOrderMap.delete(childNode);
55
+ reflow();
24
56
  },
25
- removeChild(child) {
26
- data.node.removeChild(child);
57
+ updateNodeOrder(childNode, order) {
58
+ let update = false;
59
+ const oldOrder = childNodesOrderMap.get(childNode)?.requestedOrder;
60
+ if (order === undefined) {
61
+ // if the order is undefined, we remove the node from the map
62
+ if (oldOrder !== undefined) {
63
+ childNodesOrderMap.delete(childNode);
64
+ update = true;
65
+ }
66
+ }
67
+ else {
68
+ // if the order is defined, we update the node in the map
69
+ const oldOrder = childNodesOrderMap.get(childNode)?.requestedOrder;
70
+ if (oldOrder !== order) {
71
+ childNodesOrderMap.set(childNode, {
72
+ requestedOrder: order
73
+ });
74
+ update = true;
75
+ }
76
+ }
77
+ // if there's no update, return early
78
+ if (!update)
79
+ return;
80
+ // remove all child nodes from the node
81
+ removeAllChildNodes();
82
+ // we need to sort the child nodes by their requested order. We leave the
83
+ // nodes that don't have a requested order untouched.
84
+ const sorted = Array.from(childNodes)
85
+ .map((node, index) => {
86
+ return {
87
+ order: childNodesOrderMap.get(node)?.requestedOrder ?? index,
88
+ node
89
+ };
90
+ })
91
+ .sort((a, b) => a.order - b.order)
92
+ .map(({ node }) => node);
93
+ // Then we need to remove all child nodes from the node and insert them in
94
+ // the correct order.
95
+ removeAllChildNodes();
96
+ sorted.forEach((childNode, index) => {
97
+ node.insertChild(childNode, index);
98
+ });
99
+ reflow();
27
100
  }
28
- };
29
- setContext(nodeContextName, data);
30
- return data;
101
+ });
102
+ return parentNodeContext;
31
103
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@threlte/flex",
3
- "version": "0.0.7",
3
+ "version": "0.0.9",
4
4
  "author": "Grischa Erbe <hello@legrisch.com> (https://legrisch.com)",
5
5
  "license": "MIT",
6
6
  "devDependencies": {