@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,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Owns the resources and queued work of one layout observer initialization.
|
|
3
|
+
*
|
|
4
|
+
* @param {Element} target Target element.
|
|
5
|
+
* @returns {object} Observer lifetime and cleanup helpers.
|
|
6
|
+
*/
|
|
7
|
+
export const createObserverScope = (target) => {
|
|
8
|
+
const defaultView = target.ownerDocument.defaultView;
|
|
9
|
+
const cleanups = new Set();
|
|
10
|
+
const frames = new Set();
|
|
11
|
+
let disposed = false;
|
|
12
|
+
|
|
13
|
+
return {
|
|
14
|
+
get disposed() {
|
|
15
|
+
return disposed;
|
|
16
|
+
},
|
|
17
|
+
addCleanup(cleanup) {
|
|
18
|
+
if (disposed) {
|
|
19
|
+
cleanup();
|
|
20
|
+
} else {
|
|
21
|
+
cleanups.add(cleanup);
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
requestAnimationFrame(callback) {
|
|
25
|
+
if (disposed) {
|
|
26
|
+
return 0;
|
|
27
|
+
}
|
|
28
|
+
if (!defaultView?.requestAnimationFrame) {
|
|
29
|
+
callback();
|
|
30
|
+
return 0;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const id = defaultView.requestAnimationFrame((time) => {
|
|
34
|
+
frames.delete(id);
|
|
35
|
+
if (!disposed) {
|
|
36
|
+
callback(time);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
frames.add(id);
|
|
40
|
+
return id;
|
|
41
|
+
},
|
|
42
|
+
dispose() {
|
|
43
|
+
if (disposed) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
disposed = true;
|
|
47
|
+
frames.forEach((id) => defaultView.cancelAnimationFrame(id));
|
|
48
|
+
frames.clear();
|
|
49
|
+
cleanups.forEach((cleanup) => cleanup());
|
|
50
|
+
cleanups.clear();
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
};
|
|
@@ -22,29 +22,29 @@ const getRegistry = () => {
|
|
|
22
22
|
return globalThis[INITIALIZER_REGISTRY_KEY];
|
|
23
23
|
};
|
|
24
24
|
|
|
25
|
-
const
|
|
26
|
-
if (!
|
|
27
|
-
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const targets = new Set();
|
|
31
|
-
|
|
32
|
-
if (root.matches?.(initializer.selector)) {
|
|
33
|
-
targets.add(root);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
root.querySelectorAll?.(initializer.selector).forEach((target) => {
|
|
37
|
-
targets.add(target);
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
targets.forEach((target) => {
|
|
25
|
+
const initializeTarget = (initializer, target) => {
|
|
26
|
+
if (!target?.isConnected || target.nodeType !== Node.ELEMENT_NODE) return;
|
|
27
|
+
if (!initializer.enabled() || !target.matches?.(initializer.selector)) {
|
|
41
28
|
if (initializer.initialized.has(target)) {
|
|
42
|
-
|
|
29
|
+
initializer.cleanups.get(target)?.();
|
|
30
|
+
initializer.cleanups.delete(target);
|
|
31
|
+
initializer.initialized.delete(target);
|
|
32
|
+
initializer.reset?.(target);
|
|
43
33
|
}
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
if (initializer.initialized.has(target)) return;
|
|
37
|
+
const cleanup = initializer.initialize(target);
|
|
38
|
+
initializer.initialized.add(target);
|
|
39
|
+
if (typeof cleanup === 'function') initializer.cleanups.set(target, cleanup);
|
|
40
|
+
};
|
|
44
41
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
42
|
+
const initializeInitializer = (initializer, root) => {
|
|
43
|
+
if (!root?.isConnected || root.nodeType !== Node.ELEMENT_NODE) return;
|
|
44
|
+
initializeTarget(initializer, root);
|
|
45
|
+
root
|
|
46
|
+
.querySelectorAll?.(initializer.selector)
|
|
47
|
+
.forEach((target) => initializeTarget(initializer, target));
|
|
48
48
|
};
|
|
49
49
|
|
|
50
50
|
const initializeNode = (root) => {
|
|
@@ -58,6 +58,27 @@ const initializeNode = (root) => {
|
|
|
58
58
|
});
|
|
59
59
|
};
|
|
60
60
|
|
|
61
|
+
// Inspect detached subtrees without relying on selectors that may have changed before removal.
|
|
62
|
+
const disposeNode = (root) => {
|
|
63
|
+
const registry = getRegistry();
|
|
64
|
+
if (!registry || root?.isConnected || root?.nodeType !== Node.ELEMENT_NODE) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
[root, ...root.querySelectorAll('*')].forEach((target) => {
|
|
69
|
+
registry.initializers.forEach((initializer) => {
|
|
70
|
+
const cleanup = initializer.cleanups?.get(target);
|
|
71
|
+
if (!cleanup || target.isConnected) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
initializer.cleanups.delete(target);
|
|
76
|
+
initializer.initialized.delete(target);
|
|
77
|
+
cleanup();
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
};
|
|
81
|
+
|
|
61
82
|
const flushPendingNodes = () => {
|
|
62
83
|
const registry = getRegistry();
|
|
63
84
|
if (!registry) {
|
|
@@ -82,25 +103,44 @@ const observeDocument = () => {
|
|
|
82
103
|
|
|
83
104
|
registry.observer = new MutationObserver((entries) => {
|
|
84
105
|
entries.forEach((entry) => {
|
|
106
|
+
if (entry.type === 'attributes') {
|
|
107
|
+
registry.initializers.forEach((initializer) => initializeTarget(initializer, entry.target));
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
entry.removedNodes.forEach(disposeNode);
|
|
85
111
|
entry.addedNodes.forEach((addedNode) => {
|
|
86
|
-
if (addedNode?.nodeType === Node.ELEMENT_NODE) {
|
|
112
|
+
if (addedNode?.isConnected && addedNode.nodeType === Node.ELEMENT_NODE) {
|
|
87
113
|
registry.pendingNodes.add(addedNode);
|
|
88
114
|
}
|
|
89
115
|
});
|
|
90
116
|
});
|
|
91
117
|
|
|
118
|
+
registry.pendingNodes.forEach((node) => {
|
|
119
|
+
if (!node.isConnected) {
|
|
120
|
+
registry.pendingNodes.delete(node);
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
|
|
92
124
|
if (!registry.rafId && 0 < registry.pendingNodes.size) {
|
|
93
125
|
registry.rafId = requestAnimationFrame(flushPendingNodes);
|
|
94
126
|
}
|
|
95
127
|
});
|
|
96
128
|
|
|
97
129
|
registry.observer.observe(document.body, {
|
|
130
|
+
attributes: true,
|
|
131
|
+
attributeFilter: ['data-unitone-layout', 'data-unitone-react-layout'],
|
|
98
132
|
childList: true,
|
|
99
133
|
subtree: true,
|
|
100
134
|
});
|
|
101
135
|
};
|
|
102
136
|
|
|
103
|
-
export const registerLayoutInitializer = ({
|
|
137
|
+
export const registerLayoutInitializer = ({
|
|
138
|
+
key,
|
|
139
|
+
selector,
|
|
140
|
+
initialize,
|
|
141
|
+
reset,
|
|
142
|
+
enabled = () => true,
|
|
143
|
+
}) => {
|
|
104
144
|
const registry = getRegistry();
|
|
105
145
|
if (!registry || registry.initializers.has(key)) {
|
|
106
146
|
return;
|
|
@@ -109,8 +149,10 @@ export const registerLayoutInitializer = ({ key, selector, initialize, enabled =
|
|
|
109
149
|
const initializer = {
|
|
110
150
|
selector,
|
|
111
151
|
initialize,
|
|
152
|
+
reset,
|
|
112
153
|
enabled,
|
|
113
154
|
initialized: new WeakSet(),
|
|
155
|
+
cleanups: new WeakMap(),
|
|
114
156
|
};
|
|
115
157
|
|
|
116
158
|
registry.initializers.set(key, initializer);
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useEffect, useImperativeHandle, useLayoutEffect, useRef } from 'react';
|
|
4
|
+
import { dividersResizeObserver, stairsResizeObserver, verticalsResizeObserver } from './library';
|
|
5
|
+
import { layoutRefreshEvent, resetLayoutBehavior } from './layout-behavior-state';
|
|
6
|
+
|
|
7
|
+
const useBrowserLayoutEffect = typeof window === 'undefined' ? useEffect : useLayoutEffect;
|
|
8
|
+
const observers = {
|
|
9
|
+
divider: dividersResizeObserver,
|
|
10
|
+
stairs: stairsResizeObserver,
|
|
11
|
+
vertical: verticalsResizeObserver,
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/** React owns observer lifetimes; calculations and resize handling remain shared with HTML. */
|
|
15
|
+
export const useLayoutBehaviors = (enabled, forwardedRef) => {
|
|
16
|
+
const targetRef = useRef(null);
|
|
17
|
+
const lifetimeRef = useRef(null);
|
|
18
|
+
useImperativeHandle(forwardedRef, () => targetRef.current);
|
|
19
|
+
|
|
20
|
+
const dispose = () => {
|
|
21
|
+
const lifetime = lifetimeRef.current;
|
|
22
|
+
if (!lifetime) return;
|
|
23
|
+
lifetime.target.removeEventListener(layoutRefreshEvent, lifetime.refresh);
|
|
24
|
+
lifetime.controllers.forEach((controller, name) => {
|
|
25
|
+
controller.dispose();
|
|
26
|
+
resetLayoutBehavior(lifetime.target, name);
|
|
27
|
+
});
|
|
28
|
+
lifetimeRef.current = null;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
useBrowserLayoutEffect(() => dispose, []);
|
|
32
|
+
useBrowserLayoutEffect(() => {
|
|
33
|
+
const target = targetRef.current;
|
|
34
|
+
if (lifetimeRef.current?.target !== target) dispose();
|
|
35
|
+
if (!target) return;
|
|
36
|
+
if (!lifetimeRef.current) {
|
|
37
|
+
const controllers = new Map();
|
|
38
|
+
const refresh = () => controllers.forEach((controller) => controller.refresh());
|
|
39
|
+
lifetimeRef.current = { target, controllers, refresh };
|
|
40
|
+
target.addEventListener(layoutRefreshEvent, refresh);
|
|
41
|
+
}
|
|
42
|
+
const { controllers } = lifetimeRef.current;
|
|
43
|
+
for (const [name, observe] of Object.entries(observers)) {
|
|
44
|
+
if (!enabled[name]) {
|
|
45
|
+
if (controllers.has(name)) {
|
|
46
|
+
controllers.get(name).dispose();
|
|
47
|
+
controllers.delete(name);
|
|
48
|
+
resetLayoutBehavior(target, name);
|
|
49
|
+
}
|
|
50
|
+
} else if (!controllers.has(name)) {
|
|
51
|
+
const stop = observe(target, { reactOwned: true });
|
|
52
|
+
controllers.set(name, { dispose: stop, refresh: stop.refresh });
|
|
53
|
+
} else {
|
|
54
|
+
controllers.get(name).refresh();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
return targetRef;
|
|
60
|
+
};
|