@inc2734/unitone-css 1.4.0 → 1.4.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/dist/app.css +1 -1
- package/dist/app.js +1 -1
- package/dist/behaviors/dividers.js +1 -1
- package/dist/behaviors/index.js +1 -1
- package/dist/behaviors/stairs.js +1 -1
- package/dist/layout-primitives/cluster/react.js +1 -1
- package/dist/layout-primitives/index.js +1 -1
- package/dist/layout-primitives/marquee/behavior.js +1 -1
- package/dist/layout-primitives/marquee/react.js +1 -1
- package/dist/layout-primitives/responsive-grid/react.js +1 -1
- package/dist/layout-primitives/stack/react.js +1 -1
- package/dist/layout-primitives/switcher/react.js +1 -1
- package/dist/layout-primitives/vertical-writing/behavior.js +1 -1
- package/dist/layout-primitives/vertical-writing/react.js +1 -1
- package/dist/layout-primitives/with-sidebar/react.js +1 -1
- package/dist/library.js +1 -1
- package/package.json +2 -2
- package/src/behaviors/dividers.js +3 -1
- package/src/behaviors/stairs.js +3 -1
- package/src/layout-behavior-state.js +41 -0
- package/src/layout-primitives/cluster/index.jsx +25 -16
- package/src/layout-primitives/marquee/_index.scss +15 -8
- package/src/layout-primitives/marquee/behavior.js +1 -1
- package/src/layout-primitives/marquee/index.jsx +69 -7
- package/src/layout-primitives/marquee/layout.js +163 -0
- package/src/layout-primitives/responsive-grid/index.jsx +29 -17
- package/src/layout-primitives/stack/index.jsx +25 -16
- package/src/layout-primitives/switcher/index.jsx +26 -17
- package/src/layout-primitives/vertical-writing/behavior.js +3 -1
- package/src/layout-primitives/vertical-writing/index.jsx +11 -11
- package/src/layout-primitives/with-sidebar/index.jsx +27 -18
- package/src/library.js +379 -442
- package/src/observer-scope.js +53 -0
- package/src/register-layout-initializer.js +64 -22
- package/src/use-layout-behaviors.js +60 -0
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { createObserverScope } from '../../observer-scope';
|
|
2
|
+
|
|
3
|
+
const marqueeCopyAttribute = 'data-unitone-marquee-copy';
|
|
4
|
+
const marqueeBoundaryAttribute = 'data-unitone-marquee-boundary';
|
|
5
|
+
const marqueeRefreshEvent = 'unitone:marquee-refresh';
|
|
6
|
+
|
|
7
|
+
export const isReactMarquee = (target) => target.matches('[data-unitone-marquee-react]');
|
|
8
|
+
|
|
9
|
+
const marqueeStyle = (element) => element.ownerDocument.defaultView.getComputedStyle(element);
|
|
10
|
+
const marqueeHasBox = (element) => element.isConnected && element.getClientRects().length > 0;
|
|
11
|
+
|
|
12
|
+
const marqueeWidth = (style, borderBox = false) => {
|
|
13
|
+
const edges = ['paddingLeft', 'paddingRight', 'borderLeftWidth', 'borderRightWidth'].reduce(
|
|
14
|
+
(total, property) => total + (parseFloat(style[property]) || 0),
|
|
15
|
+
0,
|
|
16
|
+
);
|
|
17
|
+
return (
|
|
18
|
+
(parseFloat(style.width) || 0) +
|
|
19
|
+
(borderBox ? edges : 0) -
|
|
20
|
+
(style.boxSizing === 'border-box' ? edges : 0)
|
|
21
|
+
);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/** Returns the original items and generated items without moving either collection. */
|
|
25
|
+
export const getMarqueeParts = (target) => {
|
|
26
|
+
const marquees = Array.from(target.children).filter((child) =>
|
|
27
|
+
child.matches('[data-unitone-layout~="marquee"]'),
|
|
28
|
+
);
|
|
29
|
+
const marquee = marquees.length === 1 ? marquees[0] : null;
|
|
30
|
+
const originals = [];
|
|
31
|
+
const copies = [];
|
|
32
|
+
let afterBoundary = false;
|
|
33
|
+
for (const child of marquee?.children ?? []) {
|
|
34
|
+
if (child.hasAttribute(marqueeBoundaryAttribute)) {
|
|
35
|
+
afterBoundary = true;
|
|
36
|
+
} else if (afterBoundary || child.hasAttribute(marqueeCopyAttribute)) {
|
|
37
|
+
copies.push(child);
|
|
38
|
+
} else {
|
|
39
|
+
originals.push(child);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return { marquee, originals, copies };
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/** Decorations belong to the behavior; React retains ownership of the copied nodes. */
|
|
46
|
+
export const markMarqueeCopy = (copy) => {
|
|
47
|
+
for (const [name, value] of [
|
|
48
|
+
[marqueeCopyAttribute, ''],
|
|
49
|
+
['aria-hidden', 'true'],
|
|
50
|
+
['inert', ''],
|
|
51
|
+
]) {
|
|
52
|
+
if (copy.getAttribute(name) !== value) copy.setAttribute(name, value);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Measures the start-to-start repeat distance, including the CSS gap at the boundary.
|
|
58
|
+
* One complete copy is requested first so no separate gap resolver is needed.
|
|
59
|
+
*/
|
|
60
|
+
export const measureMarquee = (target, { marquee, originals, copies }) => {
|
|
61
|
+
const empty = { groups: 0, distance: 0, rtl: false };
|
|
62
|
+
if (!marquee || !marqueeHasBox(target) || !marqueeHasBox(marquee)) return empty;
|
|
63
|
+
const style = marqueeStyle(marquee);
|
|
64
|
+
if (!style.writingMode.startsWith('horizontal')) return empty;
|
|
65
|
+
const wrapperStyle = marqueeStyle(target);
|
|
66
|
+
const width = marqueeWidth(wrapperStyle);
|
|
67
|
+
const items = originals.filter(marqueeHasBox);
|
|
68
|
+
if (!(width > 0) || !items.some((item) => item.getBoundingClientRect().width > 0)) return empty;
|
|
69
|
+
const rtl = style.direction === 'rtl';
|
|
70
|
+
const firstCopy = copies.find(marqueeHasBox);
|
|
71
|
+
if (!firstCopy) return { groups: 1, distance: 0, rtl };
|
|
72
|
+
const firstRect = items[0].getBoundingClientRect();
|
|
73
|
+
const copyRect = firstCopy.getBoundingClientRect();
|
|
74
|
+
// Positive, axis-aligned ancestor scales must be removed from CSS translation lengths.
|
|
75
|
+
const scale = target.getBoundingClientRect().width / marqueeWidth(wrapperStyle, true);
|
|
76
|
+
const distance =
|
|
77
|
+
(rtl ? firstRect.right - copyRect.right : copyRect.left - firstRect.left) / scale;
|
|
78
|
+
if (!(distance > 0) || !Number.isFinite(distance)) return empty;
|
|
79
|
+
return { groups: Math.ceil(width / distance), distance, rtl };
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
/** Observes layout changes, never measuring on each animation frame. */
|
|
83
|
+
export const observeMarquee = (target, update, { listenForRefresh = true } = {}) => {
|
|
84
|
+
const scope = createObserverScope(target);
|
|
85
|
+
const observed = new Set();
|
|
86
|
+
const sizes = new WeakMap();
|
|
87
|
+
let frame = 0;
|
|
88
|
+
|
|
89
|
+
const observeMutations = () => {
|
|
90
|
+
mutations.observe(target, {
|
|
91
|
+
attributes: true,
|
|
92
|
+
childList: true,
|
|
93
|
+
characterData: true,
|
|
94
|
+
subtree: true,
|
|
95
|
+
});
|
|
96
|
+
for (let ancestor = target.parentElement; ancestor; ancestor = ancestor.parentElement) {
|
|
97
|
+
mutations.observe(ancestor, { attributes: true });
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
const refresh = () => {
|
|
101
|
+
if (scope.disposed || !target.isConnected) return;
|
|
102
|
+
mutations.disconnect();
|
|
103
|
+
try {
|
|
104
|
+
update();
|
|
105
|
+
const { marquee, originals } = getMarqueeParts(target);
|
|
106
|
+
const next = new Set([target, marquee, ...originals].filter(Boolean));
|
|
107
|
+
for (const element of observed) {
|
|
108
|
+
if (!next.has(element)) {
|
|
109
|
+
resizes.unobserve(element);
|
|
110
|
+
observed.delete(element);
|
|
111
|
+
sizes.delete(element);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
for (const element of next) {
|
|
115
|
+
if (!observed.has(element)) {
|
|
116
|
+
resizes.observe(element, { box: 'border-box' });
|
|
117
|
+
observed.add(element);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
} finally {
|
|
121
|
+
observeMutations();
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
const schedule = () => {
|
|
125
|
+
if (scope.disposed || frame) return;
|
|
126
|
+
frame = scope.requestAnimationFrame(() => {
|
|
127
|
+
frame = 0;
|
|
128
|
+
refresh();
|
|
129
|
+
});
|
|
130
|
+
};
|
|
131
|
+
const mutations = new MutationObserver(schedule);
|
|
132
|
+
const resizes = new ResizeObserver((entries) => {
|
|
133
|
+
let changed = false;
|
|
134
|
+
for (const entry of entries) {
|
|
135
|
+
const box = entry.borderBoxSize?.[0];
|
|
136
|
+
const size = [
|
|
137
|
+
entry.contentRect.width,
|
|
138
|
+
entry.contentRect.height,
|
|
139
|
+
box?.inlineSize,
|
|
140
|
+
box?.blockSize,
|
|
141
|
+
].join(',');
|
|
142
|
+
changed ||= sizes.get(entry.target) !== size;
|
|
143
|
+
sizes.set(entry.target, size);
|
|
144
|
+
}
|
|
145
|
+
if (changed) refresh();
|
|
146
|
+
});
|
|
147
|
+
const view = target.ownerDocument.defaultView;
|
|
148
|
+
view.addEventListener('resize', schedule);
|
|
149
|
+
if (listenForRefresh) target.addEventListener(marqueeRefreshEvent, refresh);
|
|
150
|
+
scope.addCleanup(() => {
|
|
151
|
+
mutations.disconnect();
|
|
152
|
+
resizes.disconnect();
|
|
153
|
+
observed.clear();
|
|
154
|
+
view.removeEventListener('resize', schedule);
|
|
155
|
+
if (listenForRefresh) target.removeEventListener(marqueeRefreshEvent, refresh);
|
|
156
|
+
});
|
|
157
|
+
refresh();
|
|
158
|
+
return { refresh, dispose: scope.dispose };
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
export const requestMarqueeRefresh = (target) => {
|
|
162
|
+
target.dispatchEvent(new target.ownerDocument.defaultView.Event(marqueeRefreshEvent));
|
|
163
|
+
};
|
|
@@ -1,21 +1,31 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
1
3
|
import React from 'react';
|
|
4
|
+
import { useLayoutBehaviors } from '../../use-layout-behaviors';
|
|
2
5
|
|
|
3
|
-
export const ResponsiveGrid = (
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
6
|
+
export const ResponsiveGrid = React.forwardRef(function ResponsiveGrid(
|
|
7
|
+
{
|
|
8
|
+
columnMinWidth,
|
|
9
|
+
gap,
|
|
10
|
+
autoRepeat,
|
|
11
|
+
divider,
|
|
12
|
+
dividerWidth,
|
|
13
|
+
dividerStyle,
|
|
14
|
+
dividerColor,
|
|
15
|
+
stairs,
|
|
16
|
+
stairsUp,
|
|
17
|
+
containerType,
|
|
18
|
+
responsiveContext,
|
|
19
|
+
fluidReference, // @deprecated Kept for backward compatibility. Use responsiveContext on an ancestor instead.
|
|
20
|
+
style,
|
|
21
|
+
...props
|
|
22
|
+
},
|
|
23
|
+
forwardedRef,
|
|
24
|
+
) {
|
|
25
|
+
const layoutRef = useLayoutBehaviors(
|
|
26
|
+
{ divider: '' !== (divider ?? ''), stairs: '' !== (stairs ?? '') },
|
|
27
|
+
forwardedRef,
|
|
28
|
+
);
|
|
19
29
|
style = {
|
|
20
30
|
...style,
|
|
21
31
|
'--unitone--column-min-width': '' !== columnMinWidth ? columnMinWidth : undefined,
|
|
@@ -41,8 +51,10 @@ export const ResponsiveGrid = ({
|
|
|
41
51
|
.join(' ')}
|
|
42
52
|
style={style}
|
|
43
53
|
{...props}
|
|
54
|
+
ref={layoutRef}
|
|
55
|
+
data-unitone-react-layout=""
|
|
44
56
|
>
|
|
45
57
|
{props.children}
|
|
46
58
|
</div>
|
|
47
59
|
);
|
|
48
|
-
};
|
|
60
|
+
});
|
|
@@ -1,20 +1,27 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
1
3
|
import React from 'react';
|
|
4
|
+
import { useLayoutBehaviors } from '../../use-layout-behaviors';
|
|
2
5
|
|
|
3
|
-
export const Stack = (
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
6
|
+
export const Stack = React.forwardRef(function Stack(
|
|
7
|
+
{
|
|
8
|
+
gap,
|
|
9
|
+
negative,
|
|
10
|
+
revert,
|
|
11
|
+
divider,
|
|
12
|
+
dividerWidth,
|
|
13
|
+
dividerStyle,
|
|
14
|
+
dividerColor,
|
|
15
|
+
containerType,
|
|
16
|
+
responsiveContext,
|
|
17
|
+
fluidReference, // @deprecated Kept for backward compatibility. Use responsiveContext on an ancestor instead.
|
|
18
|
+
tagName = 'div',
|
|
19
|
+
style,
|
|
20
|
+
...props
|
|
21
|
+
},
|
|
22
|
+
forwardedRef,
|
|
23
|
+
) {
|
|
24
|
+
const layoutRef = useLayoutBehaviors({ divider: '' !== (divider ?? '') }, forwardedRef);
|
|
18
25
|
const Tag = tagName;
|
|
19
26
|
|
|
20
27
|
style = {
|
|
@@ -40,8 +47,10 @@ export const Stack = ({
|
|
|
40
47
|
.join(' ')}
|
|
41
48
|
style={style}
|
|
42
49
|
{...props}
|
|
50
|
+
ref={layoutRef}
|
|
51
|
+
data-unitone-react-layout=""
|
|
43
52
|
>
|
|
44
53
|
{props.children}
|
|
45
54
|
</Tag>
|
|
46
55
|
);
|
|
47
|
-
};
|
|
56
|
+
});
|
|
@@ -1,21 +1,28 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
1
3
|
import React from 'react';
|
|
4
|
+
import { useLayoutBehaviors } from '../../use-layout-behaviors';
|
|
2
5
|
|
|
3
|
-
export const Switcher = (
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
6
|
+
export const Switcher = React.forwardRef(function Switcher(
|
|
7
|
+
{
|
|
8
|
+
gap,
|
|
9
|
+
columnGap,
|
|
10
|
+
rowGap,
|
|
11
|
+
limit,
|
|
12
|
+
revert,
|
|
13
|
+
threshold,
|
|
14
|
+
alignItems,
|
|
15
|
+
stairs,
|
|
16
|
+
stairsUp,
|
|
17
|
+
containerType,
|
|
18
|
+
responsiveContext,
|
|
19
|
+
fluidReference, // @deprecated Kept for backward compatibility. Use responsiveContext on an ancestor instead.
|
|
20
|
+
style,
|
|
21
|
+
...props
|
|
22
|
+
},
|
|
23
|
+
forwardedRef,
|
|
24
|
+
) {
|
|
25
|
+
const layoutRef = useLayoutBehaviors({ stairs: '' !== (stairs ?? '') }, forwardedRef);
|
|
19
26
|
style = {
|
|
20
27
|
...style,
|
|
21
28
|
'--unitone--threshold': '' !== threshold ? threshold : undefined,
|
|
@@ -40,8 +47,10 @@ export const Switcher = ({
|
|
|
40
47
|
.join(' ')}
|
|
41
48
|
style={style}
|
|
42
49
|
{...props}
|
|
50
|
+
ref={layoutRef}
|
|
51
|
+
data-unitone-react-layout=""
|
|
43
52
|
>
|
|
44
53
|
{props.children}
|
|
45
54
|
</div>
|
|
46
55
|
);
|
|
47
|
-
};
|
|
56
|
+
});
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import { resetLayoutBehavior } from '../../layout-behavior-state';
|
|
1
2
|
import { verticalsResizeObserver } from '../../library';
|
|
2
3
|
import { registerLayoutInitializer } from '../../register-layout-initializer';
|
|
3
4
|
|
|
4
5
|
registerLayoutInitializer({
|
|
5
6
|
key: 'layout-primitives/vertical-writing',
|
|
6
|
-
selector: '[data-unitone-layout~="vertical-writing"]',
|
|
7
|
+
selector: '[data-unitone-layout~="vertical-writing"]:not([data-unitone-react-layout])',
|
|
8
|
+
reset: (target) => resetLayoutBehavior(target, 'vertical'),
|
|
7
9
|
initialize: verticalsResizeObserver,
|
|
8
10
|
});
|
|
@@ -1,15 +1,13 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
1
3
|
import React from 'react';
|
|
4
|
+
import { useLayoutBehaviors } from '../../use-layout-behaviors';
|
|
2
5
|
|
|
3
|
-
export const VerticalWriting = (
|
|
4
|
-
textOrientation,
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
threshold,
|
|
9
|
-
queryContext,
|
|
10
|
-
style,
|
|
11
|
-
...props
|
|
12
|
-
}) => {
|
|
6
|
+
export const VerticalWriting = React.forwardRef(function VerticalWriting(
|
|
7
|
+
{ textOrientation, gap, maxHeight, switchWritingMode, threshold, queryContext, style, ...props },
|
|
8
|
+
forwardedRef,
|
|
9
|
+
) {
|
|
10
|
+
const layoutRef = useLayoutBehaviors({ vertical: true }, forwardedRef);
|
|
13
11
|
style = {
|
|
14
12
|
...style,
|
|
15
13
|
'--unitone--max-height': '' !== maxHeight ? maxHeight : undefined,
|
|
@@ -30,9 +28,11 @@ export const VerticalWriting = ({
|
|
|
30
28
|
.join(' ')}
|
|
31
29
|
style={style}
|
|
32
30
|
{...props}
|
|
31
|
+
ref={layoutRef}
|
|
32
|
+
data-unitone-react-layout=""
|
|
33
33
|
>
|
|
34
34
|
{props.children}
|
|
35
35
|
</div>
|
|
36
36
|
</div>
|
|
37
37
|
);
|
|
38
|
-
};
|
|
38
|
+
});
|
|
@@ -1,22 +1,29 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
1
3
|
import React from 'react';
|
|
4
|
+
import { useLayoutBehaviors } from '../../use-layout-behaviors';
|
|
2
5
|
|
|
3
|
-
export const WithSidebar = (
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
6
|
+
export const WithSidebar = React.forwardRef(function WithSidebar(
|
|
7
|
+
{
|
|
8
|
+
gap,
|
|
9
|
+
columnGap,
|
|
10
|
+
rowGap,
|
|
11
|
+
contentMinWidth,
|
|
12
|
+
revert,
|
|
13
|
+
sidebar,
|
|
14
|
+
sidebarWidth,
|
|
15
|
+
alignItems,
|
|
16
|
+
overflow,
|
|
17
|
+
divider,
|
|
18
|
+
dividerWidth,
|
|
19
|
+
dividerStyle,
|
|
20
|
+
dividerColor,
|
|
21
|
+
style,
|
|
22
|
+
...props
|
|
23
|
+
},
|
|
24
|
+
forwardedRef,
|
|
25
|
+
) {
|
|
26
|
+
const layoutRef = useLayoutBehaviors({ divider: '' !== (divider ?? '') }, forwardedRef);
|
|
20
27
|
style = {
|
|
21
28
|
...style,
|
|
22
29
|
'--unitone--sidebar-width': '' !== sidebarWidth ? sidebarWidth : undefined,
|
|
@@ -43,8 +50,10 @@ export const WithSidebar = ({
|
|
|
43
50
|
.join(' ')}
|
|
44
51
|
style={style}
|
|
45
52
|
{...props}
|
|
53
|
+
ref={layoutRef}
|
|
54
|
+
data-unitone-react-layout=""
|
|
46
55
|
>
|
|
47
56
|
{props.children}
|
|
48
57
|
</div>
|
|
49
58
|
);
|
|
50
|
-
};
|
|
59
|
+
});
|