@threlte/flex 0.0.2

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.
package/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2022 Grischa Erbe
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,67 @@
1
+ <div align="right">
2
+ <a href="https://www.npmjs.com/package/@threlte/flex">
3
+ <img alt="npm" src="https://img.shields.io/npm/v/@threlte/flex?color=fe4100&labelColor=171d27&logo=npm&logoColor=white"/>
4
+ </a>
5
+ <a href="https://github.com/threlte/threlte/blob/main/LICENSE.md">
6
+ <img alt="license" src="https://img.shields.io/npm/l/@threlte/core?color=fe4100&labelColor=171d27&logo=git&logoColor=white"/>
7
+ </a>
8
+ <a href="https://discord.com/channels/985983540804091964">
9
+ <img alt="discord" src="https://img.shields.io/discord/985983540804091964?label=discord&color=fe4100&labelColor=171d27&logo=discord&logoColor=white"/>
10
+ </a>
11
+ <a href="https://threlte.xyz">
12
+ <img alt="docs" src="https://img.shields.io/website?down_color=red&down_message=offline&label=docs&color=fe4100&labelColor=171d27&up_message=online&url=https%3A%2F%2Fthrelte.xyz&logo=svelte&logoColor=white"/>
13
+ </a>
14
+ </div>
15
+
16
+ <a href="https://threlte.xyz">
17
+ <img src="https://threlte.xyz/logo/threlte-banner.jpg"/>
18
+ </a>
19
+
20
+ ## Rapidly Build Interactive 3D Apps for the Web
21
+
22
+ Threlte is a [Svelte](https://svelte.dev/) library that simplifies creating 3D apps for the web. It provides a **declarative**, **type-safe**, **reactive** and **interactive** API out-of-the-box.
23
+
24
+ Threlte's **3D rendering** is powered by [Three.js](https://threejs.org/), and it also provides a **physics engine** through [Rapier](https://rapier.rs/) and an **animation studio** via [Theatre.js](https://www.theatrejs.com/); see [packages](#packages) for details.
25
+
26
+ Check out our **[documentation](https://threlte.xyz)** and our **[Discord community](https://discord.gg/EqUBCfCaGm)**.
27
+
28
+ ## @threlte/flex
29
+
30
+ [@threlte/flex](https://threlte.xyz/docs/reference/flex/getting-started) makes placing 3D content based on flexbox principles a breeze.
31
+
32
+ ## Quickstart
33
+
34
+ ### Installation
35
+
36
+ For a quick interactive setup of a fresh Threlte project, run:
37
+
38
+ ```sh
39
+ npm create threlte my-project
40
+ ```
41
+
42
+ Alternatively you can check out the full [installation instructions](https://threlte.xyz/docs/learn/getting-started/installation).
43
+
44
+ ### Tutorial
45
+
46
+ To get a hang of the basics, we recommend following our [introductory tutorial](https://threlte.xyz/docs/learn/getting-started/your-first-scene).
47
+
48
+ ### Support
49
+
50
+ Have questions? Feel free to ask in our [Discord support forum](https://discord.com/channels/985983540804091964/1031843197963477002).
51
+
52
+ ## Contributing
53
+
54
+ Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are **greatly appreciated**.
55
+
56
+ - **Filing Issues** - if you have feature requestions or you think you spotted a bug, [submit an issue](https://github.com/threlte/threlte/issues/new).
57
+ - **Contributing Code** - if you would like to drop us a PR, read the [contribution guide](https://github.com/threlte/threlte/blob/main/CONTRIBUTING.md) first.
58
+
59
+ ## Sponsors
60
+
61
+ [![Powered by Vercel](./assets/vercel/powered-by-vercel.svg)](https://vercel.com/?utm_source=threlte&utm_campaign=oss)
62
+
63
+ ---
64
+
65
+ ### License
66
+
67
+ The MIT License (MIT). Please see the [License File](LICENSE.md) for more information.
@@ -0,0 +1,62 @@
1
+ <script>import { HierarchicalObject, T } from '@threlte/core';
2
+ import { onDestroy } from 'svelte';
3
+ import { Group } from 'three';
4
+ import { useFlex } from '../Flex/context';
5
+ import { createNodeContext } from '../nodes/context';
6
+ export let order = undefined;
7
+ let _class = '';
8
+ export { _class as class };
9
+ const { scaleFactor, onEvent, addNode, removeNode, updateNodeProps, mainAxis, crossAxis, depthAxis, classParser } = useFlex();
10
+ export const group = new Group();
11
+ export const contentGroup = new Group();
12
+ group.userData.isNode = true;
13
+ export const { node } = createNodeContext(order);
14
+ addNode(node, group, $$restProps);
15
+ updateNodeProps(node, { ...classParser?.(_class), ...$$restProps }, true);
16
+ $: updateNodeProps(node, { ...classParser?.(_class), ...$$restProps });
17
+ onDestroy(() => {
18
+ removeNode(node);
19
+ });
20
+ let computedWidth = 1;
21
+ let computedHeight = 1;
22
+ // after the parent has been reflowed, we can use the calculated layout to set the properties of the box
23
+ onEvent('reflow:after', () => {
24
+ computedWidth =
25
+ typeof $$restProps.width === 'number'
26
+ ? $$restProps.width
27
+ : node.getComputedWidth() / $scaleFactor;
28
+ computedHeight =
29
+ typeof $$restProps.height === 'number'
30
+ ? $$restProps.height
31
+ : node.getComputedHeight() / $scaleFactor;
32
+ contentGroup.position[$mainAxis] = computedWidth / 2;
33
+ contentGroup.position[$crossAxis] = -computedHeight / 2;
34
+ contentGroup.position[$depthAxis] = 0;
35
+ });
36
+ </script>
37
+
38
+ <T is={group}>
39
+ <T is={contentGroup} />
40
+ </T>
41
+
42
+ <HierarchicalObject
43
+ onChildMount={(child) => {
44
+ if (child.userData.isNode) {
45
+ group.add(child)
46
+ } else {
47
+ contentGroup.add(child)
48
+ }
49
+ }}
50
+ onChildDestroy={(child) => {
51
+ if (child.userData.isNode) {
52
+ group.remove(child)
53
+ } else {
54
+ contentGroup.remove(child)
55
+ }
56
+ }}
57
+ >
58
+ <slot
59
+ width={computedWidth}
60
+ height={computedHeight}
61
+ />
62
+ </HierarchicalObject>
@@ -0,0 +1,62 @@
1
+ import { SvelteComponentTyped } from "svelte";
2
+ import { Group } from 'three';
3
+ declare const __propDef: {
4
+ props: {
5
+ group?: Group | undefined;
6
+ contentGroup?: Group | undefined;
7
+ node?: any;
8
+ alignItems?: string | number | symbol | undefined;
9
+ alignSelf?: string | number | symbol | undefined;
10
+ alignContent?: string | number | symbol | undefined;
11
+ justifyContent?: string | number | symbol | undefined;
12
+ flexDirection?: string | number | symbol | undefined;
13
+ flexWrap?: string | number | symbol | undefined;
14
+ flex?: unknown;
15
+ flexBasis?: unknown;
16
+ flexGrow?: unknown;
17
+ flexShrink?: unknown;
18
+ height?: unknown;
19
+ width?: unknown;
20
+ maxHeight?: unknown;
21
+ maxWidth?: unknown;
22
+ minHeight?: unknown;
23
+ minWidth?: unknown;
24
+ top?: unknown;
25
+ right?: unknown;
26
+ bottom?: unknown;
27
+ left?: unknown;
28
+ padding?: unknown;
29
+ paddingTop?: unknown;
30
+ paddingRight?: unknown;
31
+ paddingBottom?: unknown;
32
+ paddingLeft?: unknown;
33
+ margin?: unknown;
34
+ marginTop?: unknown;
35
+ marginRight?: unknown;
36
+ marginBottom?: unknown;
37
+ marginLeft?: unknown;
38
+ gap?: unknown;
39
+ gapColumn?: unknown;
40
+ gapRow?: unknown;
41
+ aspectRatio?: unknown;
42
+ order?: number | undefined;
43
+ class?: string | undefined;
44
+ };
45
+ events: {
46
+ [evt: string]: CustomEvent<any>;
47
+ };
48
+ slots: {
49
+ default: {
50
+ width: number;
51
+ height: number;
52
+ };
53
+ };
54
+ };
55
+ export type BoxProps = typeof __propDef.props;
56
+ export type BoxEvents = typeof __propDef.events;
57
+ export type BoxSlots = typeof __propDef.slots;
58
+ export default class Box extends SvelteComponentTyped<BoxProps, BoxEvents, BoxSlots> {
59
+ get group(): Group;
60
+ get contentGroup(): Group;
61
+ }
62
+ export {};
@@ -0,0 +1,21 @@
1
+ <script>import { forwardEventHandlers } from '@threlte/core';
2
+ import { loadYoga } from 'yoga-layout';
3
+ import InnerFlex from './InnerFlex.svelte';
4
+ let yoga;
5
+ const initialize = async () => {
6
+ yoga = await loadYoga();
7
+ };
8
+ initialize();
9
+ const component = forwardEventHandlers();
10
+ </script>
11
+
12
+ {#if yoga}
13
+ <InnerFlex
14
+ {yoga}
15
+ {...$$restProps}
16
+ bind:this={$component}
17
+ let:reflow
18
+ >
19
+ <slot {reflow} />
20
+ </InnerFlex>
21
+ {/if}
@@ -0,0 +1,61 @@
1
+ import { SvelteComponentTyped } from "svelte";
2
+ declare const __propDef: {
3
+ props: {
4
+ flex?: unknown;
5
+ height?: number | undefined;
6
+ width?: number | undefined;
7
+ left?: unknown;
8
+ top?: unknown;
9
+ alignContent?: string | number | symbol | undefined;
10
+ alignItems?: string | number | symbol | undefined;
11
+ alignSelf?: string | number | symbol | undefined;
12
+ aspectRatio?: unknown;
13
+ bottom?: unknown;
14
+ direction?: string | number | symbol | undefined;
15
+ flexBasis?: unknown;
16
+ flexDirection?: string | number | symbol | undefined;
17
+ flexGrow?: unknown;
18
+ flexShrink?: unknown;
19
+ flexWrap?: string | number | symbol | undefined;
20
+ gap?: unknown;
21
+ justifyContent?: string | number | symbol | undefined;
22
+ margin?: unknown;
23
+ marginBottom?: unknown;
24
+ marginLeft?: unknown;
25
+ marginRight?: unknown;
26
+ marginTop?: unknown;
27
+ maxHeight?: unknown;
28
+ maxWidth?: unknown;
29
+ minHeight?: unknown;
30
+ minWidth?: unknown;
31
+ padding?: unknown;
32
+ paddingBottom?: unknown;
33
+ paddingLeft?: unknown;
34
+ paddingRight?: unknown;
35
+ paddingTop?: unknown;
36
+ right?: unknown;
37
+ class?: string | undefined;
38
+ plane?: import("../lib/props").FlexPlane | undefined;
39
+ gapColumn?: unknown;
40
+ gapRow?: unknown;
41
+ scaleFactor?: number | undefined;
42
+ classParser?: import("../lib/props").ClassParser | undefined;
43
+ };
44
+ slots: {
45
+ default: {
46
+ reflow: () => void;
47
+ };
48
+ };
49
+ events: {
50
+ reflow: {
51
+ width: number;
52
+ height: number;
53
+ };
54
+ };
55
+ };
56
+ export type FlexProps = typeof __propDef.props;
57
+ export type FlexEvents = typeof __propDef.events;
58
+ export type FlexSlots = typeof __propDef.slots;
59
+ export default class Flex extends SvelteComponentTyped<FlexProps, FlexEvents, FlexSlots> {
60
+ }
61
+ export {};
@@ -0,0 +1,137 @@
1
+ <script>import { T, createRawEventDispatcher, currentWritable, useFrame } from '@threlte/core';
2
+ import { onDestroy } from 'svelte';
3
+ import { Box3, Group, Vector3 } from 'three';
4
+ import { Direction } from 'yoga-layout';
5
+ import { getDepthAxis } from '../lib/getDepthAxis';
6
+ import { getOrientedBoundingBoxSize } from '../lib/getOrientedBoundingBoxSize';
7
+ import { getRootShift } from '../lib/getRootShift';
8
+ import { applyNodeProps } from '../lib/props';
9
+ import { createNodeContext } from '../nodes/context';
10
+ import { createFlexContext } from './context';
11
+ export let yoga;
12
+ export let width = 1;
13
+ export let height = 1;
14
+ export let plane = 'xy';
15
+ export let direction = 'LTR';
16
+ export let scaleFactor = 1000;
17
+ export let classParser = undefined;
18
+ let _class = '';
19
+ export { _class as class };
20
+ const dispatch = createRawEventDispatcher();
21
+ const rootGroup = new Group();
22
+ rootGroup.userData.isNode = true;
23
+ const boundingBox = new Box3();
24
+ const vec3 = new Vector3();
25
+ /**
26
+ * Reflowing inside useFrame automatically batches reflows to 1 per frame.
27
+ */
28
+ const { start: reflow, stop } = useFrame(() => {
29
+ flexContext.emit('reflow:before');
30
+ for (const { node, group, props } of flexContext.nodes.values()) {
31
+ const scaledWidth = typeof props.width === 'number' ? props.width * scaleFactor : props.width;
32
+ const scaledHeight = typeof props.height === 'number' ? props.height * scaleFactor : props.height;
33
+ if (scaledWidth !== undefined && scaledHeight !== undefined) {
34
+ // Forced size, no need to calculate bounding box
35
+ node.setWidth(scaledWidth);
36
+ node.setHeight(scaledHeight);
37
+ }
38
+ else if (node.getChildCount() === 0) {
39
+ // No size specified, calculate size
40
+ if (rootGroup) {
41
+ getOrientedBoundingBoxSize(group, rootGroup, boundingBox, vec3);
42
+ }
43
+ else {
44
+ // rootGroup ref is missing for some reason, let's just use usual bounding box
45
+ boundingBox.setFromObject(group).getSize(vec3);
46
+ }
47
+ node.setWidth(scaledWidth || vec3[$mainAxis] * scaleFactor);
48
+ node.setHeight(scaledHeight || vec3[$crossAxis] * scaleFactor);
49
+ }
50
+ }
51
+ rootNode.calculateLayout(width * scaleFactor, height * scaleFactor, Direction[direction]);
52
+ const rootWidth = rootNode.getComputedWidth();
53
+ const rootHeight = rootNode.getComputedHeight();
54
+ let minX = 0;
55
+ let maxX = 0;
56
+ let minY = 0;
57
+ let maxY = 0;
58
+ // Reposition after recalculation
59
+ for (const { node, group } of flexContext.nodes.values()) {
60
+ const { left, top, width, height } = node.getComputedLayout();
61
+ const [mainAxisShift, crossAxisShift] = getRootShift(rootWidth, rootHeight, node);
62
+ group.position[$mainAxis] = (mainAxisShift + left) / scaleFactor;
63
+ group.position[$crossAxis] = -(crossAxisShift + top) / scaleFactor;
64
+ group.position[$depthAxis] = 0;
65
+ minX = Math.min(minX, left);
66
+ minY = Math.min(minY, top);
67
+ maxX = Math.max(maxX, left + width);
68
+ maxY = Math.max(maxY, top + height);
69
+ }
70
+ flexContext.emit('reflow:after');
71
+ dispatch('reflow', {
72
+ width: (maxX - minX) / scaleFactor,
73
+ height: (maxY - minY) / scaleFactor
74
+ });
75
+ stop();
76
+ }, { autostart: false });
77
+ const flexContext = createFlexContext({
78
+ yoga,
79
+ nodes: new Map(),
80
+ addNode(node, group, props) {
81
+ flexContext.nodes.set(node, { node, group, props });
82
+ reflow();
83
+ },
84
+ updateNodeProps(node, props, force = false) {
85
+ const nodeData = flexContext.nodes.get(node);
86
+ if (!nodeData)
87
+ return;
88
+ // Updating the props can be forced and is done so on the initial call.
89
+ if (!force) {
90
+ // Because all NodeProps are primitive types, we can make a simple
91
+ // comparison and only request a reflow when necessary. We do that by
92
+ // checking the length of the props object and then checking if all keys
93
+ // are the same and all values are the same.
94
+ const previousKeys = Object.keys(nodeData.props);
95
+ const currentKeys = Object.keys(props);
96
+ if (previousKeys.length === currentKeys.length &&
97
+ currentKeys.every((key) => previousKeys.includes(key)) &&
98
+ previousKeys.every((key) => nodeData.props[key] === props[key])) {
99
+ return;
100
+ }
101
+ }
102
+ applyNodeProps(node, props, scaleFactor);
103
+ nodeData.props = props;
104
+ reflow();
105
+ },
106
+ removeNode(node) {
107
+ flexContext.nodes.delete(node);
108
+ reflow();
109
+ },
110
+ rootWidth: currentWritable(width),
111
+ rootHeight: currentWritable(height),
112
+ scaleFactor: currentWritable(scaleFactor ?? 1000),
113
+ mainAxis: currentWritable(plane[0]),
114
+ crossAxis: currentWritable(plane[1]),
115
+ depthAxis: currentWritable(getDepthAxis(plane)),
116
+ rootGroup: rootGroup,
117
+ reflow,
118
+ classParser
119
+ });
120
+ const { mainAxis, crossAxis, depthAxis } = flexContext;
121
+ const { node: rootNode } = createNodeContext();
122
+ $: rootNode.setWidth(width * scaleFactor), rootNode.setHeight(height * scaleFactor);
123
+ $: applyNodeProps(rootNode, { ...classParser?.(_class), ...$$restProps }, scaleFactor), reflow();
124
+ $: flexContext.rootWidth.set(width), flexContext.reflow('Updated root width');
125
+ $: flexContext.rootHeight.set(height), flexContext.reflow('Updated root height');
126
+ $: flexContext.mainAxis.set(plane[0]), flexContext.reflow('Updated main axis');
127
+ $: flexContext.crossAxis.set(plane[1]), flexContext.reflow('Updated cross axis');
128
+ $: flexContext.depthAxis.set(getDepthAxis(plane)), flexContext.reflow('Updated depth axis');
129
+ $: flexContext.scaleFactor.set(scaleFactor), flexContext.reflow('Updated scale factor');
130
+ onDestroy(() => {
131
+ rootNode.free();
132
+ });
133
+ </script>
134
+
135
+ <T is={rootGroup}>
136
+ <slot {reflow} />
137
+ </T>
@@ -0,0 +1,63 @@
1
+ import { SvelteComponentTyped } from "svelte";
2
+ import { type ClassParser, type FlexPlane } from '../lib/props';
3
+ declare const __propDef: {
4
+ props: {
5
+ alignItems?: string | number | symbol | undefined;
6
+ alignSelf?: string | number | symbol | undefined;
7
+ alignContent?: string | number | symbol | undefined;
8
+ justifyContent?: string | number | symbol | undefined;
9
+ flexDirection?: string | number | symbol | undefined;
10
+ flexWrap?: string | number | symbol | undefined;
11
+ flex?: unknown;
12
+ flexBasis?: unknown;
13
+ flexGrow?: unknown;
14
+ flexShrink?: unknown;
15
+ height?: number | undefined;
16
+ width?: number | undefined;
17
+ maxHeight?: unknown;
18
+ maxWidth?: unknown;
19
+ minHeight?: unknown;
20
+ minWidth?: unknown;
21
+ top?: unknown;
22
+ right?: unknown;
23
+ bottom?: unknown;
24
+ left?: unknown;
25
+ padding?: unknown;
26
+ paddingTop?: unknown;
27
+ paddingRight?: unknown;
28
+ paddingBottom?: unknown;
29
+ paddingLeft?: unknown;
30
+ margin?: unknown;
31
+ marginTop?: unknown;
32
+ marginRight?: unknown;
33
+ marginBottom?: unknown;
34
+ marginLeft?: unknown;
35
+ gap?: unknown;
36
+ gapColumn?: unknown;
37
+ gapRow?: unknown;
38
+ aspectRatio?: unknown;
39
+ yoga: Yoga;
40
+ plane?: FlexPlane | undefined;
41
+ direction?: string | number | symbol | undefined;
42
+ scaleFactor?: number | undefined;
43
+ class?: string | undefined;
44
+ classParser?: ClassParser | undefined;
45
+ };
46
+ slots: {
47
+ default: {
48
+ reflow: () => void;
49
+ };
50
+ };
51
+ events: {
52
+ reflow: {
53
+ width: number;
54
+ height: number;
55
+ };
56
+ };
57
+ };
58
+ export type InnerFlexProps = typeof __propDef.props;
59
+ export type InnerFlexEvents = typeof __propDef.events;
60
+ export type InnerFlexSlots = typeof __propDef.slots;
61
+ export default class InnerFlex extends SvelteComponentTyped<InnerFlexProps, InnerFlexEvents, InnerFlexSlots> {
62
+ }
63
+ export {};
@@ -0,0 +1,39 @@
1
+ import type { CurrentWritable } from '@threlte/core';
2
+ import { type Emitter } from 'mitt';
3
+ import type { Group } from 'three';
4
+ import type { Node, Yoga } from 'yoga-layout';
5
+ import type { Axis, ClassParser, NodeProps } from '../lib/props';
6
+ type FlexContextEvents = {
7
+ 'reflow:before': void;
8
+ 'reflow:after': void;
9
+ };
10
+ type FlexContextNode = {
11
+ node: Node;
12
+ group: THREE.Group;
13
+ props: NodeProps;
14
+ };
15
+ export type FlexContextData = {
16
+ yoga: Yoga;
17
+ nodes: Map<Node, FlexContextNode>;
18
+ addNode: (node: Node, group: Group, props: NodeProps) => void;
19
+ updateNodeProps: (node: Node, props: NodeProps, force?: boolean) => void;
20
+ removeNode: (node: Node) => void;
21
+ scaleFactor: CurrentWritable<number>;
22
+ mainAxis: CurrentWritable<Axis>;
23
+ crossAxis: CurrentWritable<Axis>;
24
+ depthAxis: CurrentWritable<Axis>;
25
+ rootGroup: Group;
26
+ rootWidth: CurrentWritable<number>;
27
+ rootHeight: CurrentWritable<number>;
28
+ reflow: (msg?: string) => void;
29
+ classParser?: ClassParser;
30
+ };
31
+ export type FlexContext = FlexContextData & Emitter<FlexContextEvents> & {
32
+ onEvent: <Type extends keyof FlexContextEvents>(type: Type, callback: (payload: FlexContextEvents[Type]) => void) => void;
33
+ };
34
+ export declare const flexContextName = "__threlte-flex";
35
+ export declare const createFlexContext: (data: FlexContextData) => FlexContextData & Emitter<FlexContextEvents> & {
36
+ onEvent: <Type extends keyof FlexContextEvents>(type: Type, callback: (payload: FlexContextEvents[Type]) => void) => void;
37
+ };
38
+ export declare const useFlex: () => FlexContext;
39
+ export {};
@@ -0,0 +1,18 @@
1
+ import mitt, {} from 'mitt';
2
+ import { getContext, onDestroy, setContext } from 'svelte';
3
+ export const flexContextName = '__threlte-flex';
4
+ export const createFlexContext = (data) => {
5
+ const emitter = mitt();
6
+ const eventCallback = {
7
+ onEvent: (type, callback) => {
8
+ emitter.on(type, callback);
9
+ onDestroy(() => emitter.off(type, callback));
10
+ }
11
+ };
12
+ const context = Object.assign(data, emitter, eventCallback);
13
+ setContext(flexContextName, context);
14
+ return context;
15
+ };
16
+ export const useFlex = () => {
17
+ return getContext(flexContextName);
18
+ };
@@ -0,0 +1,5 @@
1
+ export { default as Box } from './Box/Box.svelte';
2
+ export { default as Flex } from './Flex/Flex.svelte';
3
+ export type { NodeProps } from './lib/props';
4
+ export { createClassParser } from './parsers/createClassParser';
5
+ export { tailwindParser } from './parsers/tailwindParser';
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ export { default as Box } from './Box/Box.svelte';
2
+ export { default as Flex } from './Flex/Flex.svelte';
3
+ // parsers
4
+ export { createClassParser } from './parsers/createClassParser';
5
+ export { tailwindParser } from './parsers/tailwindParser';
@@ -0,0 +1,9 @@
1
+ import type { NodeProps } from './props';
2
+ /**
3
+ * This function will align the flex props to the web-standard.
4
+ *
5
+ * yoga-layout uses `Column` as the default flex direction, but we
6
+ * want to use `Row` as the default (web-standard). This function will return
7
+ * the props with the default flex direction applied if it is not already set.
8
+ */
9
+ export declare const alignFlexProps: (props: NodeProps) => NodeProps;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * This function will align the flex props to the web-standard.
3
+ *
4
+ * yoga-layout uses `Column` as the default flex direction, but we
5
+ * want to use `Row` as the default (web-standard). This function will return
6
+ * the props with the default flex direction applied if it is not already set.
7
+ */
8
+ export const alignFlexProps = (props) => {
9
+ return { flexDirection: 'Row', ...props };
10
+ };
@@ -0,0 +1,2 @@
1
+ import type { FlexPlane } from './props';
2
+ export declare function getDepthAxis(plane: FlexPlane): "x" | "y" | "z";
@@ -0,0 +1,10 @@
1
+ export function getDepthAxis(plane) {
2
+ switch (plane) {
3
+ case 'xy':
4
+ return 'z';
5
+ case 'yz':
6
+ return 'x';
7
+ case 'xz':
8
+ return 'y';
9
+ }
10
+ }
@@ -0,0 +1,2 @@
1
+ import type { FlexPlane } from './props';
2
+ export declare function getFlex2DSize(sizes: [number, number, number], plane: FlexPlane): number[];
@@ -0,0 +1,10 @@
1
+ export function getFlex2DSize(sizes, plane) {
2
+ switch (plane) {
3
+ case 'xy':
4
+ return [sizes[0], sizes[1]];
5
+ case 'yz':
6
+ return [sizes[1], sizes[2]];
7
+ case 'xz':
8
+ return [sizes[0], sizes[2]];
9
+ }
10
+ }
@@ -0,0 +1,11 @@
1
+ import { type Box3, type Object3D, type Vector3 } from 'three';
2
+ /**
3
+ * Adapted code from https://github.com/mrdoob/three.js/issues/11967
4
+ * Calculates oriented bounding box size
5
+ * Essentially it negates flex root rotation to provide proper number
6
+ * E.g. if root flex group rotatet 45 degress, a cube box of size 1 will report sizes of sqrt(2)
7
+ * but it should still be 1
8
+ *
9
+ * NB: This doesn't work when object itself is rotated (well, for now)
10
+ */
11
+ export declare const getOrientedBoundingBoxSize: (object: Object3D, root: Object3D, bb: Box3, size: Vector3) => void;
@@ -0,0 +1,25 @@
1
+ import { Matrix4 } from 'three';
2
+ /**
3
+ * Adapted code from https://github.com/mrdoob/three.js/issues/11967
4
+ * Calculates oriented bounding box size
5
+ * Essentially it negates flex root rotation to provide proper number
6
+ * E.g. if root flex group rotatet 45 degress, a cube box of size 1 will report sizes of sqrt(2)
7
+ * but it should still be 1
8
+ *
9
+ * NB: This doesn't work when object itself is rotated (well, for now)
10
+ */
11
+ export const getOrientedBoundingBoxSize = (object, root, bb, size) => {
12
+ object.updateMatrix();
13
+ const oldMatrix = object.matrix;
14
+ const oldMatrixAutoUpdate = object.matrixAutoUpdate;
15
+ root.updateMatrixWorld();
16
+ const m = new Matrix4().copy(root.matrixWorld).invert();
17
+ object.matrix = m;
18
+ // to prevent matrix being reassigned
19
+ object.matrixAutoUpdate = false;
20
+ root.updateMatrixWorld();
21
+ bb.setFromObject(object).getSize(size);
22
+ object.matrix = oldMatrix;
23
+ object.matrixAutoUpdate = oldMatrixAutoUpdate;
24
+ root.updateMatrixWorld();
25
+ };
@@ -0,0 +1,2 @@
1
+ /** @returns [mainAxisShift, crossAxisShift] */
2
+ export declare const getRootShift: (rootWidth: number, rootHeight: number, node: Node) => number[] | readonly [number, number];
@@ -0,0 +1,10 @@
1
+ import { isTopLevelChildNode } from './isTopLevelChildNode';
2
+ /** @returns [mainAxisShift, crossAxisShift] */
3
+ export const getRootShift = (rootWidth, rootHeight, node) => {
4
+ if (!isTopLevelChildNode(node)) {
5
+ return [0, 0];
6
+ }
7
+ const mainAxisShift = -rootWidth / 2;
8
+ const crossAxisShift = -rootHeight / 2;
9
+ return [mainAxisShift, crossAxisShift];
10
+ };
@@ -0,0 +1 @@
1
+ export declare const isTopLevelChildNode: (node: Node) => boolean;
@@ -0,0 +1 @@
1
+ export const isTopLevelChildNode = (node) => !node.getParent()?.getParent();
@@ -0,0 +1,52 @@
1
+ import type { Align, FlexDirection, Justify, Node, Wrap } from 'yoga-layout';
2
+ export type FlexPlane = 'xy' | 'yz' | 'xz';
3
+ export type ClassParser = (className: string) => NodeProps;
4
+ export type Axis = 'x' | 'y' | 'z';
5
+ /**
6
+ * This map provides the prop setters as well as the types for the props. The
7
+ * first input of a setter is used as the prop value. The second input is the
8
+ * node to apply the prop to. The value of a prop needs to be a primitive type,
9
+ * so that it can be trivially compared to the previous value. This is used to
10
+ * prevent unnecessary reflows.
11
+ */
12
+ export declare const propSetter: {
13
+ alignItems: (align: keyof typeof Align, node: Node) => any;
14
+ alignSelf: (align: keyof typeof Align, node: Node) => any;
15
+ alignContent: (align: keyof typeof Align, node: Node) => any;
16
+ justifyContent: (justify: keyof typeof Justify, node: Node) => any;
17
+ flexDirection: (dir: keyof typeof FlexDirection, node: Node) => any;
18
+ flexWrap: (wrap: keyof typeof Wrap, node: Node) => any;
19
+ flex: (flex: Parameters<Node['setFlex']>[0], node: Node) => any;
20
+ flexBasis: (basis: Parameters<Node['setFlexBasis']>[0], node: Node) => any;
21
+ flexGrow: (grow: Parameters<Node['setFlexGrow']>[0], node: Node) => any;
22
+ flexShrink: (shrink: Parameters<Node['setFlexShrink']>[0], node: Node) => any;
23
+ height: (height: Parameters<Node['setHeight']>[0], node: Node) => any;
24
+ width: (width: Parameters<Node['setWidth']>[0], node: Node) => any;
25
+ maxHeight: (maxHeight: Parameters<Node['setMaxHeight']>[0], node: Node) => any;
26
+ maxWidth: (maxWidth: Parameters<Node['setMaxWidth']>[0], node: Node) => any;
27
+ minHeight: (minHeight: Parameters<Node['setMinHeight']>[0], node: Node) => any;
28
+ minWidth: (minWidth: Parameters<Node['setMinWidth']>[0], node: Node) => any;
29
+ /** As of now, this won't work since the bounding box is still computed by nodes marked as absolutely positioned */
30
+ top: (top: Parameters<Node['setPosition']>[1], node: Node) => any;
31
+ right: (right: Parameters<Node['setPosition']>[1], node: Node) => any;
32
+ bottom: (bottom: Parameters<Node['setPosition']>[1], node: Node) => any;
33
+ left: (left: Parameters<Node['setPosition']>[1], node: Node) => any;
34
+ padding: (padding: Parameters<Node['setPadding']>[1], node: Node) => any;
35
+ paddingTop: (paddingTop: Parameters<Node['setPadding']>[1], node: Node) => any;
36
+ paddingRight: (paddingRight: Parameters<Node['setPadding']>[1], node: Node) => any;
37
+ paddingBottom: (paddingBottom: Parameters<Node['setPadding']>[1], node: Node) => any;
38
+ paddingLeft: (paddingLeft: Parameters<Node['setPadding']>[1], node: Node) => any;
39
+ margin: (margin: Parameters<Node['setMargin']>[1], node: Node) => any;
40
+ marginTop: (marginTop: Parameters<Node['setMargin']>[1], node: Node) => any;
41
+ marginRight: (marginRight: Parameters<Node['setMargin']>[1], node: Node) => any;
42
+ marginBottom: (marginBottom: Parameters<Node['setMargin']>[1], node: Node) => any;
43
+ marginLeft: (marginLeft: Parameters<Node['setMargin']>[1], node: Node) => any;
44
+ gap: (gap: Parameters<Node['setGap']>[1], node: Node) => any;
45
+ gapColumn: (gapColumn: Parameters<Node['setGap']>[1], node: Node) => any;
46
+ gapRow: (gapRow: Parameters<Node['setGap']>[1], node: Node) => any;
47
+ aspectRatio: (aspectRatio: Parameters<Node['setAspectRatio']>[0], node: Node) => any;
48
+ };
49
+ export type NodeProps = {
50
+ [Key in keyof typeof propSetter]?: Parameters<(typeof propSetter)[Key]>[0];
51
+ };
52
+ export declare const applyNodeProps: (node: Node, props: NodeProps, scaleFactor: number) => void;
@@ -0,0 +1,65 @@
1
+ import * as Yoga from 'yoga-layout';
2
+ import { alignFlexProps } from './alignFlexProps';
3
+ // prettier-ignore
4
+ /**
5
+ * This map provides the prop setters as well as the types for the props. The
6
+ * first input of a setter is used as the prop value. The second input is the
7
+ * node to apply the prop to. The value of a prop needs to be a primitive type,
8
+ * so that it can be trivially compared to the previous value. This is used to
9
+ * prevent unnecessary reflows.
10
+ */
11
+ export const propSetter = {
12
+ alignItems: (align, node) => node.setAlignItems(Yoga.Align[align]),
13
+ alignSelf: (align, node) => node.setAlignSelf(Yoga.Align[align]),
14
+ alignContent: (align, node) => node.setAlignContent(Yoga.Align[align]),
15
+ justifyContent: (justify, node) => node.setJustifyContent(Yoga.Justify[justify]),
16
+ flexDirection: (dir, node) => node.setFlexDirection(Yoga.FlexDirection[dir]),
17
+ flexWrap: (wrap, node) => node.setFlexWrap(Yoga.Wrap[wrap]),
18
+ flex: (flex, node) => node.setFlex(flex),
19
+ flexBasis: (basis, node) => node.setFlexBasis(basis),
20
+ flexGrow: (grow, node) => node.setFlexGrow(grow),
21
+ flexShrink: (shrink, node) => node.setFlexShrink(shrink),
22
+ height: (height, node) => node.setHeight(height),
23
+ width: (width, node) => node.setWidth(width),
24
+ maxHeight: (maxHeight, node) => node.setMaxHeight(maxHeight),
25
+ maxWidth: (maxWidth, node) => node.setMaxWidth(maxWidth),
26
+ minHeight: (minHeight, node) => node.setMinHeight(minHeight),
27
+ minWidth: (minWidth, node) => node.setMinWidth(minWidth),
28
+ /** As of now, this won't work since the bounding box is still computed by nodes marked as absolutely positioned */
29
+ // position: (positionType: keyof typeof PositionType, node: Node) => node.setPositionType(Yoga.PositionType[positionType]),
30
+ top: (top, node) => node.setPosition(Yoga.Edge.Top, top),
31
+ right: (right, node) => node.setPosition(Yoga.Edge.Right, right),
32
+ bottom: (bottom, node) => node.setPosition(Yoga.Edge.Bottom, bottom),
33
+ left: (left, node) => node.setPosition(Yoga.Edge.Left, left),
34
+ padding: (padding, node) => node.setPadding(Yoga.Edge.All, padding),
35
+ paddingTop: (paddingTop, node) => node.setPadding(Yoga.Edge.Top, paddingTop),
36
+ paddingRight: (paddingRight, node) => node.setPadding(Yoga.Edge.Right, paddingRight),
37
+ paddingBottom: (paddingBottom, node) => node.setPadding(Yoga.Edge.Bottom, paddingBottom),
38
+ paddingLeft: (paddingLeft, node) => node.setPadding(Yoga.Edge.Left, paddingLeft),
39
+ margin: (margin, node) => node.setMargin(Yoga.Edge.All, margin),
40
+ marginTop: (marginTop, node) => node.setMargin(Yoga.Edge.Top, marginTop),
41
+ marginRight: (marginRight, node) => node.setMargin(Yoga.Edge.Right, marginRight),
42
+ marginBottom: (marginBottom, node) => node.setMargin(Yoga.Edge.Bottom, marginBottom),
43
+ marginLeft: (marginLeft, node) => node.setMargin(Yoga.Edge.Left, marginLeft),
44
+ gap: (gap, node) => node.setGap(Yoga.Gutter.All, gap),
45
+ gapColumn: (gapColumn, node) => node.setGap(Yoga.Gutter.Column, gapColumn),
46
+ gapRow: (gapRow, node) => node.setGap(Yoga.Gutter.Row, gapRow),
47
+ aspectRatio: (aspectRatio, node) => node.setAspectRatio(aspectRatio),
48
+ };
49
+ /**
50
+ * Applies scale factor to props that are numbers. This is used to scale the
51
+ * props to the current scale factor of the root node because yoga-layout
52
+ * is made to work with integer values.
53
+ */
54
+ const applyScaleFactor = (prop, scaleFactor) => {
55
+ if (typeof prop === 'number') {
56
+ return prop * scaleFactor;
57
+ }
58
+ return prop;
59
+ };
60
+ export const applyNodeProps = (node, props, scaleFactor) => {
61
+ return Object.entries(alignFlexProps(props)).forEach(([key, value]) => {
62
+ const scaledValue = applyScaleFactor(value, scaleFactor);
63
+ propSetter[key](scaledValue, node);
64
+ });
65
+ };
@@ -0,0 +1,9 @@
1
+ import type { Node } from 'yoga-layout';
2
+ export type NodeContext = {
3
+ node: Node;
4
+ insertChild: (child: Node, order?: number) => void;
5
+ removeChild: (child: Node) => void;
6
+ };
7
+ export declare const nodeContextName = "__threlte-node";
8
+ export declare const useNode: () => NodeContext;
9
+ export declare const createNodeContext: (order?: number) => NodeContext;
@@ -0,0 +1,31 @@
1
+ import { getContext, onDestroy, setContext } from 'svelte';
2
+ import { useFlex } from '../Flex/context';
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) {
18
+ if (order !== undefined) {
19
+ data.node.insertChild(child, order);
20
+ }
21
+ else {
22
+ data.node.insertChild(child, data.node.getChildCount());
23
+ }
24
+ },
25
+ removeChild(child) {
26
+ data.node.removeChild(child);
27
+ }
28
+ };
29
+ setContext(nodeContextName, data);
30
+ return data;
31
+ };
@@ -0,0 +1,8 @@
1
+ import type { NodeProps } from '../lib/props';
2
+ /**
3
+ * This function is a type helper for creating a class parser. A class parser is
4
+ * a function that takes a class name and returns a NodeProps object. This can
5
+ * be used to create Tailwind-like class names that resolve to yoga-layout
6
+ * props.
7
+ */
8
+ export declare const createClassParser: (callback: (className: string) => NodeProps) => (className: string) => NodeProps;
@@ -0,0 +1,9 @@
1
+ /**
2
+ * This function is a type helper for creating a class parser. A class parser is
3
+ * a function that takes a class name and returns a NodeProps object. This can
4
+ * be used to create Tailwind-like class names that resolve to yoga-layout
5
+ * props.
6
+ */
7
+ export const createClassParser = (callback) => {
8
+ return callback;
9
+ };
@@ -0,0 +1,2 @@
1
+ import type { NodeProps } from '../lib/props';
2
+ export declare const tailwindParser: (className: string) => NodeProps;
@@ -0,0 +1,266 @@
1
+ import { createClassParser } from './createClassParser';
2
+ export const tailwindParser = createClassParser((string) => {
3
+ const styles = {};
4
+ const classes = string.split(' ').map((className) => className.trim());
5
+ const parseNumericOrAutoOrPercentageValue = (value) => {
6
+ if (value === 'auto') {
7
+ return value;
8
+ }
9
+ else if (value.endsWith('%')) {
10
+ return value;
11
+ }
12
+ else if (value === 'full') {
13
+ return '100%';
14
+ }
15
+ else if (value.match(/^\d+\/\d+$/)) {
16
+ const [width, height] = value.split('/');
17
+ const percentage = Math.round((Number(width) / Number(height)) * 100);
18
+ return `${percentage}%`;
19
+ }
20
+ return Number(value);
21
+ };
22
+ const parseNumericOrPercentageValue = (value) => {
23
+ if (value.endsWith('%')) {
24
+ return value;
25
+ }
26
+ else if (value === 'full') {
27
+ return '100%';
28
+ }
29
+ else if (value.match(/^\d+\/\d+$/)) {
30
+ const [width, height] = value.split('/');
31
+ const percentage = Math.round((Number(width) / Number(height)) * 100);
32
+ return `${percentage}%`;
33
+ }
34
+ return Number(value);
35
+ };
36
+ classes.forEach((className) => {
37
+ // padding
38
+ if (className.startsWith('p-')) {
39
+ const [, value] = className.split('-');
40
+ styles.padding = parseNumericOrPercentageValue(value);
41
+ }
42
+ if (className.startsWith('px-')) {
43
+ const [, value] = className.split('-');
44
+ styles.paddingLeft = parseNumericOrPercentageValue(value);
45
+ styles.paddingRight = parseNumericOrPercentageValue(value);
46
+ }
47
+ if (className.startsWith('py-')) {
48
+ const [, value] = className.split('-');
49
+ styles.paddingTop = parseNumericOrPercentageValue(value);
50
+ styles.paddingBottom = parseNumericOrPercentageValue(value);
51
+ }
52
+ if (className.startsWith('pt-')) {
53
+ const [, value] = className.split('-');
54
+ styles.paddingTop = parseNumericOrPercentageValue(value);
55
+ }
56
+ if (className.startsWith('pr-')) {
57
+ const [, value] = className.split('-');
58
+ styles.paddingRight = parseNumericOrPercentageValue(value);
59
+ }
60
+ if (className.startsWith('pb-')) {
61
+ const [, value] = className.split('-');
62
+ styles.paddingBottom = parseNumericOrPercentageValue(value);
63
+ }
64
+ if (className.startsWith('pl-')) {
65
+ const [, value] = className.split('-');
66
+ styles.paddingLeft = parseNumericOrPercentageValue(value);
67
+ }
68
+ // margin
69
+ if (className.startsWith('m-')) {
70
+ const [, value] = className.split('-');
71
+ styles.margin = parseNumericOrAutoOrPercentageValue(value);
72
+ }
73
+ if (className.startsWith('mx-')) {
74
+ const [, value] = className.split('-');
75
+ styles.marginLeft = parseNumericOrAutoOrPercentageValue(value);
76
+ styles.marginRight = parseNumericOrAutoOrPercentageValue(value);
77
+ }
78
+ if (className.startsWith('my-')) {
79
+ const [, value] = className.split('-');
80
+ styles.marginTop = parseNumericOrAutoOrPercentageValue(value);
81
+ styles.marginBottom = parseNumericOrAutoOrPercentageValue(value);
82
+ }
83
+ if (className.startsWith('mt-')) {
84
+ const [, value] = className.split('-');
85
+ styles.marginTop = parseNumericOrAutoOrPercentageValue(value);
86
+ }
87
+ if (className.startsWith('mr-')) {
88
+ const [, value] = className.split('-');
89
+ styles.marginRight = parseNumericOrAutoOrPercentageValue(value);
90
+ }
91
+ if (className.startsWith('mb-')) {
92
+ const [, value] = className.split('-');
93
+ styles.marginBottom = parseNumericOrAutoOrPercentageValue(value);
94
+ }
95
+ if (className.startsWith('ml-')) {
96
+ const [, value] = className.split('-');
97
+ styles.marginLeft = parseNumericOrAutoOrPercentageValue(value);
98
+ }
99
+ // width
100
+ if (className.startsWith('w-')) {
101
+ const [, value] = className.split('-');
102
+ styles.width = parseNumericOrAutoOrPercentageValue(value);
103
+ }
104
+ // height
105
+ if (className.startsWith('h-')) {
106
+ const [, value] = className.split('-');
107
+ styles.height = parseNumericOrAutoOrPercentageValue(value);
108
+ }
109
+ // flex-basis
110
+ if (className.startsWith('basis-')) {
111
+ const [, value] = className.split('-');
112
+ styles.flexBasis = parseNumericOrAutoOrPercentageValue(value);
113
+ }
114
+ // flex-grow
115
+ if (className.startsWith('grow-')) {
116
+ const [, value] = className.split('-');
117
+ styles.flexGrow = Number(value);
118
+ }
119
+ // flex-shrink
120
+ if (className.startsWith('shrink-')) {
121
+ const [, value] = className.split('-');
122
+ styles.flexShrink = Number(value);
123
+ }
124
+ // flex-direction
125
+ if (className.startsWith('flex-')) {
126
+ // first the flex direction
127
+ switch (className) {
128
+ case 'flex-row':
129
+ styles.flexDirection = 'Row';
130
+ case 'flex-row-reverse':
131
+ styles.flexDirection = 'RowReverse';
132
+ case 'flex-col':
133
+ styles.flexDirection = 'Column';
134
+ case 'flex-col-reverse':
135
+ styles.flexDirection = 'ColumnReverse';
136
+ // flex-wrap
137
+ case 'flex-wrap':
138
+ styles.flexWrap = 'Wrap';
139
+ case 'flex-wrap-reverse':
140
+ styles.flexWrap = 'WrapReverse';
141
+ case 'flex-nowrap':
142
+ styles.flexWrap = 'NoWrap';
143
+ default:
144
+ // flex shorthand
145
+ const [, value] = className.split('-');
146
+ styles.flex = Number(value);
147
+ }
148
+ }
149
+ // justify-content
150
+ if (className.startsWith('justify-')) {
151
+ switch (className) {
152
+ case 'justify-start':
153
+ styles.justifyContent = 'FlexStart';
154
+ case 'justify-end':
155
+ styles.justifyContent = 'FlexEnd';
156
+ case 'justify-center':
157
+ styles.justifyContent = 'Center';
158
+ case 'justify-between':
159
+ styles.justifyContent = 'SpaceBetween';
160
+ case 'justify-around':
161
+ styles.justifyContent = 'SpaceAround';
162
+ case 'justify-evenly':
163
+ styles.justifyContent = 'SpaceEvenly';
164
+ }
165
+ }
166
+ // align-items
167
+ if (className.startsWith('items-')) {
168
+ switch (className) {
169
+ case 'items-start':
170
+ styles.alignItems = 'FlexStart';
171
+ case 'items-end':
172
+ styles.alignItems = 'FlexEnd';
173
+ case 'items-center':
174
+ styles.alignItems = 'Center';
175
+ case 'items-baseline':
176
+ styles.alignItems = 'Baseline';
177
+ case 'items-stretch':
178
+ styles.alignItems = 'Stretch';
179
+ }
180
+ }
181
+ // align-content
182
+ if (className.startsWith('content-')) {
183
+ switch (className) {
184
+ case 'content-normal':
185
+ styles.alignContent = 'Auto';
186
+ case 'content-start':
187
+ styles.alignContent = 'FlexStart';
188
+ case 'content-end':
189
+ styles.alignContent = 'FlexEnd';
190
+ case 'content-center':
191
+ styles.alignContent = 'Center';
192
+ case 'content-between':
193
+ styles.alignContent = 'SpaceBetween';
194
+ case 'content-around':
195
+ styles.alignContent = 'SpaceAround';
196
+ case 'content-stretch':
197
+ styles.alignContent = 'Stretch';
198
+ case 'content-baseline':
199
+ styles.alignContent = 'Baseline';
200
+ }
201
+ }
202
+ // align-self
203
+ if (className.startsWith('self-')) {
204
+ switch (className) {
205
+ case 'self-auto':
206
+ styles.alignSelf = 'Auto';
207
+ case 'self-start':
208
+ styles.alignSelf = 'FlexStart';
209
+ case 'self-end':
210
+ styles.alignSelf = 'FlexEnd';
211
+ case 'self-center':
212
+ styles.alignSelf = 'Center';
213
+ case 'self-stretch':
214
+ styles.alignSelf = 'Stretch';
215
+ case 'self-baseline':
216
+ styles.alignSelf = 'Baseline';
217
+ }
218
+ }
219
+ // Gaps
220
+ if (className.startsWith('gap-x-')) {
221
+ const [, value] = className.split('-');
222
+ styles.gapColumn = Number(value);
223
+ }
224
+ else if (className.startsWith('gap-y-')) {
225
+ const [, value] = className.split('-');
226
+ styles.gapRow = Number(value);
227
+ }
228
+ else if (className.startsWith('gap-')) {
229
+ const [, value] = className.split('-');
230
+ styles.gap = Number(value);
231
+ }
232
+ // Position
233
+ if (className.startsWith('top-')) {
234
+ const [, value] = className.split('-');
235
+ styles.top = parseNumericOrPercentageValue(value);
236
+ }
237
+ if (className.startsWith('right-')) {
238
+ const [, value] = className.split('-');
239
+ styles.right = parseNumericOrPercentageValue(value);
240
+ }
241
+ if (className.startsWith('bottom-')) {
242
+ const [, value] = className.split('-');
243
+ styles.bottom = parseNumericOrPercentageValue(value);
244
+ }
245
+ if (className.startsWith('left-')) {
246
+ const [, value] = className.split('-');
247
+ styles.left = parseNumericOrPercentageValue(value);
248
+ }
249
+ // aspect ratio
250
+ if (className.startsWith('aspect-')) {
251
+ switch (className) {
252
+ case 'aspect-square':
253
+ styles.aspectRatio = 1;
254
+ case 'aspect-landscape':
255
+ styles.aspectRatio = 16 / 9;
256
+ case 'aspect-portrait':
257
+ styles.aspectRatio = 9 / 16;
258
+ default:
259
+ const [, value] = className.split('-');
260
+ const [width, height] = value.split('/');
261
+ styles.aspectRatio = Number(width) / Number(height);
262
+ }
263
+ }
264
+ });
265
+ return styles;
266
+ });
@@ -0,0 +1 @@
1
+ export declare const yoga: import("@threlte/core").CurrentWritable<any>;
@@ -0,0 +1,12 @@
1
+ import { currentWritable } from '@threlte/core';
2
+ import { loadYoga } from 'yoga-layout';
3
+ let loading = false;
4
+ let loaded = false;
5
+ export const yoga = currentWritable(undefined);
6
+ const load = async () => {
7
+ if (loading || loaded)
8
+ return;
9
+ const yogaInstance = await loadYoga();
10
+ yoga.set(yogaInstance);
11
+ };
12
+ load();
package/package.json ADDED
@@ -0,0 +1,62 @@
1
+ {
2
+ "name": "@threlte/flex",
3
+ "version": "0.0.2",
4
+ "author": "Grischa Erbe <hello@legrisch.com> (https://legrisch.com)",
5
+ "license": "MIT",
6
+ "devDependencies": {
7
+ "@sveltejs/adapter-auto": "^2.0.0",
8
+ "@sveltejs/kit": "^1.20.4",
9
+ "@sveltejs/package": "^2.1.0",
10
+ "@types/node": "^18.0.3",
11
+ "@types/three": "^0.155.1",
12
+ "@typescript-eslint/eslint-plugin": "^5.45.0",
13
+ "@typescript-eslint/parser": "^5.45.0",
14
+ "@yushijinhun/three-minifier-rollup": "^0.3.1",
15
+ "eslint": "^8.28.0",
16
+ "eslint-config-prettier": "^8.5.0",
17
+ "eslint-plugin-svelte": "^2.30.0",
18
+ "prettier": "^2.8.8",
19
+ "prettier-plugin-svelte": "^2.10.1",
20
+ "publint": "^0.1.12",
21
+ "rimraf": "^5.0.1",
22
+ "svelte": "^4.1.1",
23
+ "svelte-check": "^3.4.3",
24
+ "svelte-preprocess": "^5.0.4",
25
+ "svelte2tsx": "^0.6.19",
26
+ "three": "^0.155.0",
27
+ "tslib": "^2.4.1",
28
+ "typescript": "^5.0.0",
29
+ "vite": "^4.3.6",
30
+ "@threlte/core": "6.0.8",
31
+ "@threlte/extras": "5.6.4"
32
+ },
33
+ "dependencies": {
34
+ "mitt": "^3.0.1",
35
+ "yoga-layout": "^2.0.0"
36
+ },
37
+ "peerDependencies": {
38
+ "svelte": ">=4",
39
+ "three": ">=0.133"
40
+ },
41
+ "type": "module",
42
+ "exports": {
43
+ ".": {
44
+ "types": "./dist/index.d.ts",
45
+ "svelte": "./dist/index.js"
46
+ }
47
+ },
48
+ "types": "./dist/index.d.ts",
49
+ "svelte": "./dist/index.js",
50
+ "files": [
51
+ "dist"
52
+ ],
53
+ "scripts": {
54
+ "dev": "vite dev",
55
+ "package": "svelte-kit sync && svelte-package && node ./scripts/cleanupPackage.js && publint",
56
+ "check": "svelte-check --tsconfig ./tsconfig.json",
57
+ "check:watch": "svelte-check --tsconfig ./tsconfig.json --watch",
58
+ "lint": "prettier --check --plugin-search-dir=. . && eslint .",
59
+ "format": "prettier --write --plugin-search-dir=. .",
60
+ "cleanup": "rimraf node_modules .svelte-kit dist"
61
+ }
62
+ }