@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
package/src/library.js
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
import { requestReactLayoutRefresh } from './layout-behavior-state';
|
|
2
|
+
import { createObserverScope } from './observer-scope';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
getMarqueeParts,
|
|
6
|
+
isReactMarquee,
|
|
7
|
+
markMarqueeCopy,
|
|
8
|
+
measureMarquee,
|
|
9
|
+
observeMarquee,
|
|
10
|
+
requestMarqueeRefresh,
|
|
11
|
+
} from './layout-primitives/marquee/layout';
|
|
12
|
+
|
|
1
13
|
const layoutAttributeName = 'data-unitone-layout';
|
|
2
14
|
const layoutIntersectionMargin = 200;
|
|
3
15
|
const layoutIntersectionRootMargin = `${layoutIntersectionMargin}px 0px`;
|
|
@@ -40,31 +52,38 @@ const setLayoutTokens = (element, tokens) => {
|
|
|
40
52
|
* Observes target resizes and invokes the callback when a relevant change is detected.
|
|
41
53
|
*
|
|
42
54
|
* @param {Element} target Target element.
|
|
43
|
-
* @param {(target: Element
|
|
44
|
-
* @param {
|
|
55
|
+
* @param {(target: Element) => void} callback Callback to run.
|
|
56
|
+
* @param {ReturnType<typeof createObserverScope>} scope Observer lifetime.
|
|
57
|
+
* @param {(entry: ResizeObserverEntry) => unknown} [getValue] Value to compare.
|
|
45
58
|
* @returns {ResizeObserver} ResizeObserver instance.
|
|
46
59
|
*/
|
|
47
|
-
const createResizeObserver = (target, callback,
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
const currentValue = getValue?.(entry);
|
|
55
|
-
if (isFirstEntry) {
|
|
56
|
-
prevValue = currentValue;
|
|
57
|
-
isFirstEntry = false;
|
|
58
|
-
continue;
|
|
59
|
-
}
|
|
60
|
+
const createResizeObserver = (target, callback, scope, getValue) => {
|
|
61
|
+
const prevValues = new WeakMap();
|
|
62
|
+
const onResize = debounce(() => callback(target), 250);
|
|
63
|
+
const observer = new ResizeObserver((entries) => {
|
|
64
|
+
if (scope.disposed) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
60
67
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
68
|
+
let changed = false;
|
|
69
|
+
for (const entry of entries) {
|
|
70
|
+
const currentValue = getValue?.(entry);
|
|
71
|
+
if (
|
|
72
|
+
!prevValues.has(entry.target) ||
|
|
73
|
+
undefined === currentValue ||
|
|
74
|
+
currentValue !== prevValues.get(entry.target)
|
|
75
|
+
) {
|
|
76
|
+
changed = true;
|
|
65
77
|
}
|
|
66
|
-
|
|
67
|
-
|
|
78
|
+
prevValues.set(entry.target, currentValue);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (changed) {
|
|
82
|
+
onResize();
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
scope.addCleanup(onResize.cancel);
|
|
86
|
+
scope.addCleanup(() => observer.disconnect());
|
|
68
87
|
|
|
69
88
|
observer.observe(target);
|
|
70
89
|
|
|
@@ -77,22 +96,18 @@ const createResizeObserver = (target, callback, { getValue, delay = 250 } = {})
|
|
|
77
96
|
* @param {Node} target Target node.
|
|
78
97
|
* @param {MutationObserverInit} options Observer options.
|
|
79
98
|
* @param {(entries: MutationRecord[]) => void} callback Callback to run.
|
|
80
|
-
* @
|
|
99
|
+
* @param {ReturnType<typeof createObserverScope>} scope Observer lifetime.
|
|
100
|
+
* @returns {void}
|
|
81
101
|
*/
|
|
82
|
-
const createMutationObserver = (target, options, callback) => {
|
|
102
|
+
const createMutationObserver = (target, options, callback, scope) => {
|
|
83
103
|
const observer = new MutationObserver((entries) => {
|
|
84
|
-
|
|
85
|
-
if (!target?.isConnected) {
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
|
|
104
|
+
if (!scope.disposed && target.isConnected) {
|
|
89
105
|
callback(entries);
|
|
90
|
-
}
|
|
106
|
+
}
|
|
91
107
|
});
|
|
92
108
|
|
|
93
109
|
observer.observe(target, options);
|
|
94
|
-
|
|
95
|
-
return observer;
|
|
110
|
+
scope.addCleanup(() => observer.disconnect());
|
|
96
111
|
};
|
|
97
112
|
|
|
98
113
|
/**
|
|
@@ -100,12 +115,13 @@ const createMutationObserver = (target, options, callback) => {
|
|
|
100
115
|
*
|
|
101
116
|
* @param {Element} target Target element.
|
|
102
117
|
* @param {(entry: IntersectionObserverEntry) => void} callback Callback to run.
|
|
103
|
-
* @
|
|
118
|
+
* @param {ReturnType<typeof createObserverScope>} scope Observer lifetime.
|
|
119
|
+
* @returns {void}
|
|
104
120
|
*/
|
|
105
|
-
const createIntersectionObserver = (target, callback) => {
|
|
121
|
+
const createIntersectionObserver = (target, callback, scope) => {
|
|
106
122
|
const observer = new IntersectionObserver(
|
|
107
123
|
([entry]) => {
|
|
108
|
-
if (entry) {
|
|
124
|
+
if (entry && !scope.disposed) {
|
|
109
125
|
callback(entry);
|
|
110
126
|
}
|
|
111
127
|
},
|
|
@@ -113,8 +129,7 @@ const createIntersectionObserver = (target, callback) => {
|
|
|
113
129
|
);
|
|
114
130
|
|
|
115
131
|
observer.observe(target);
|
|
116
|
-
|
|
117
|
-
return observer;
|
|
132
|
+
scope.addCleanup(() => observer.disconnect());
|
|
118
133
|
};
|
|
119
134
|
|
|
120
135
|
/**
|
|
@@ -122,27 +137,19 @@ const createIntersectionObserver = (target, callback) => {
|
|
|
122
137
|
*
|
|
123
138
|
* @param {Element} target Target element.
|
|
124
139
|
* @param {(target: Element) => void} callback Callback to run.
|
|
140
|
+
* @param {ReturnType<typeof createObserverScope>} scope Observer lifetime.
|
|
125
141
|
* @returns {() => void} Schedule function.
|
|
126
142
|
*/
|
|
127
|
-
const createScheduledTargetCallback = (target, callback) => {
|
|
143
|
+
const createScheduledTargetCallback = (target, callback, scope) => {
|
|
128
144
|
let rafId = 0;
|
|
129
|
-
let defaultView;
|
|
130
145
|
|
|
131
146
|
return () => {
|
|
132
|
-
|
|
133
|
-
if (!defaultView?.requestAnimationFrame) {
|
|
134
|
-
callback(target);
|
|
147
|
+
if (scope.disposed || rafId) {
|
|
135
148
|
return;
|
|
136
149
|
}
|
|
137
150
|
|
|
138
|
-
|
|
139
|
-
return;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
rafId = defaultView.requestAnimationFrame(() => {
|
|
151
|
+
rafId = scope.requestAnimationFrame(() => {
|
|
143
152
|
rafId = 0;
|
|
144
|
-
defaultView = null;
|
|
145
|
-
|
|
146
153
|
if (target?.isConnected) {
|
|
147
154
|
callback(target);
|
|
148
155
|
}
|
|
@@ -151,268 +158,189 @@ const createScheduledTargetCallback = (target, callback) => {
|
|
|
151
158
|
};
|
|
152
159
|
|
|
153
160
|
/**
|
|
154
|
-
*
|
|
161
|
+
* Keeps resize and optional attribute observation in sync with direct children.
|
|
155
162
|
*
|
|
156
163
|
* @param {Element} target Target element.
|
|
164
|
+
* @param {ResizeObserver} resizeObserver Shared size observer.
|
|
157
165
|
* @param {(target: Element) => void} callback Callback to run.
|
|
158
|
-
* @param {
|
|
159
|
-
* @
|
|
166
|
+
* @param {ReturnType<typeof createObserverScope>} scope Observer lifetime.
|
|
167
|
+
* @param {{ attributeFilter: string[], shouldApply: (entry: MutationRecord) => boolean }} [attributes]
|
|
168
|
+
* @param {(entries: MutationRecord[]) => boolean} [shouldApplyChildList] Filters temporary children.
|
|
169
|
+
* @returns {void}
|
|
160
170
|
*/
|
|
161
|
-
const
|
|
171
|
+
const observeLayoutChildren = (
|
|
162
172
|
target,
|
|
173
|
+
resizeObserver,
|
|
163
174
|
callback,
|
|
164
|
-
|
|
175
|
+
scope,
|
|
176
|
+
attributes,
|
|
177
|
+
shouldApplyChildList,
|
|
165
178
|
) => {
|
|
166
|
-
const prevValues = new WeakMap();
|
|
167
179
|
const observedChildren = new Set();
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
if (undefined === currentValue || currentValue !== prevValues.get(entry.target)) {
|
|
181
|
-
shouldApply = true;
|
|
180
|
+
const attributeObserver = attributes
|
|
181
|
+
? new MutationObserver((entries) => {
|
|
182
|
+
if (
|
|
183
|
+
!scope.disposed &&
|
|
184
|
+
target.isConnected &&
|
|
185
|
+
hasAttributeMutation(
|
|
186
|
+
entries,
|
|
187
|
+
(entry) => entry.target.parentElement === target && attributes.shouldApply(entry),
|
|
188
|
+
)
|
|
189
|
+
) {
|
|
190
|
+
callback(target);
|
|
182
191
|
}
|
|
192
|
+
})
|
|
193
|
+
: null;
|
|
194
|
+
scope.addCleanup(() => {
|
|
195
|
+
observedChildren.clear();
|
|
196
|
+
attributeObserver?.disconnect();
|
|
197
|
+
});
|
|
183
198
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
if (shouldApply) {
|
|
188
|
-
callback(target);
|
|
189
|
-
}
|
|
190
|
-
}, delay),
|
|
191
|
-
);
|
|
192
|
-
|
|
193
|
-
const syncObservedChildren = () => {
|
|
194
|
-
Array.from(observedChildren).forEach((child) => {
|
|
199
|
+
const syncChildren = () => {
|
|
200
|
+
for (const child of observedChildren) {
|
|
195
201
|
if (child.parentElement !== target) {
|
|
196
|
-
|
|
202
|
+
resizeObserver.unobserve(child);
|
|
197
203
|
observedChildren.delete(child);
|
|
198
|
-
prevValues.delete(child);
|
|
199
204
|
}
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
Array.from(target?.children ?? []).forEach((child) => {
|
|
203
|
-
if (observedChildren.has(child)) {
|
|
204
|
-
return;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
observer.observe(child);
|
|
208
|
-
observedChildren.add(child);
|
|
209
|
-
});
|
|
210
|
-
};
|
|
211
|
-
|
|
212
|
-
observer.observe(target);
|
|
213
|
-
syncObservedChildren();
|
|
214
|
-
|
|
215
|
-
const mutationObserver = createMutationObserver(target, { childList: true }, (entries) => {
|
|
216
|
-
if (!entries.some((entry) => 'childList' === entry.type)) {
|
|
217
|
-
return;
|
|
218
205
|
}
|
|
219
206
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
return {
|
|
226
|
-
resizeObserver: observer,
|
|
227
|
-
mutationObserver,
|
|
228
|
-
};
|
|
229
|
-
};
|
|
230
|
-
|
|
231
|
-
/**
|
|
232
|
-
* Observes attribute changes on direct children.
|
|
233
|
-
*
|
|
234
|
-
* @param {Element} target Target element.
|
|
235
|
-
* @param {(target: Element) => void} callback Callback to run.
|
|
236
|
-
* @param {{ attributeFilter: string[], shouldApply: (entry: MutationRecord) => boolean, attributeOldValue?: boolean }} options
|
|
237
|
-
* @returns {{ observer: MutationObserver, syncObservedChildren: () => void }}
|
|
238
|
-
*/
|
|
239
|
-
const createDirectChildrenAttributeObserver = (
|
|
240
|
-
target,
|
|
241
|
-
callback,
|
|
242
|
-
{ attributeFilter, shouldApply, attributeOldValue = true } = {},
|
|
243
|
-
) => {
|
|
244
|
-
const observer = new MutationObserver((entries) => {
|
|
245
|
-
requestAnimationFrame(() => {
|
|
246
|
-
if (!target?.isConnected) {
|
|
247
|
-
return;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
if (
|
|
251
|
-
entries.some(
|
|
252
|
-
(entry) =>
|
|
253
|
-
'attributes' === entry.type &&
|
|
254
|
-
entry.target.parentElement === target &&
|
|
255
|
-
shouldApply(entry),
|
|
256
|
-
)
|
|
257
|
-
) {
|
|
258
|
-
callback(target);
|
|
207
|
+
attributeObserver?.disconnect();
|
|
208
|
+
for (const child of target.children) {
|
|
209
|
+
if (!observedChildren.has(child)) {
|
|
210
|
+
resizeObserver.observe(child);
|
|
211
|
+
observedChildren.add(child);
|
|
259
212
|
}
|
|
260
|
-
|
|
261
|
-
});
|
|
262
|
-
|
|
263
|
-
const syncObservedChildren = () => {
|
|
264
|
-
observer.disconnect();
|
|
265
|
-
Array.from(target?.children ?? []).forEach((child) => {
|
|
266
|
-
observer.observe(child, {
|
|
213
|
+
attributeObserver?.observe(child, {
|
|
267
214
|
attributes: true,
|
|
268
|
-
attributeFilter,
|
|
269
|
-
attributeOldValue,
|
|
215
|
+
attributeFilter: attributes.attributeFilter,
|
|
216
|
+
attributeOldValue: true,
|
|
270
217
|
});
|
|
271
|
-
}
|
|
218
|
+
}
|
|
272
219
|
};
|
|
273
220
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
221
|
+
syncChildren();
|
|
222
|
+
createMutationObserver(
|
|
223
|
+
target,
|
|
224
|
+
{ childList: true },
|
|
225
|
+
(entries) => {
|
|
226
|
+
syncChildren();
|
|
227
|
+
if (!shouldApplyChildList || shouldApplyChildList(entries)) callback(target);
|
|
228
|
+
},
|
|
229
|
+
scope,
|
|
230
|
+
);
|
|
280
231
|
};
|
|
281
232
|
|
|
282
233
|
/**
|
|
283
234
|
* Creates a bundled observer setup for layout re-application.
|
|
284
235
|
*
|
|
285
236
|
* @param {Element} target Target element.
|
|
286
|
-
* @param {(target: Element) => void} apply Apply function.
|
|
237
|
+
* @param {(target: Element, scope: ReturnType<typeof createObserverScope>) => void} apply Apply function.
|
|
287
238
|
* @param {{
|
|
288
239
|
* getResizeValue?: (entry: ResizeObserverEntry) => unknown,
|
|
289
|
-
* delay?: number,
|
|
290
|
-
* observeResize?: boolean,
|
|
291
|
-
* observeIntersection?: boolean,
|
|
292
240
|
* observeDirectChildrenResize?: boolean,
|
|
293
|
-
* targetMutation?: { options: MutationObserverInit, shouldApply
|
|
294
|
-
* directChildMutation?: { attributeFilter: string[], shouldApply: (entry: MutationRecord) => boolean
|
|
241
|
+
* targetMutation?: { options: MutationObserverInit, shouldApply: (entries: MutationRecord[]) => boolean },
|
|
242
|
+
* directChildMutation?: { attributeFilter: string[], shouldApply: (entry: MutationRecord) => boolean },
|
|
243
|
+
* shouldApplyChildList?: (entries: MutationRecord[]) => boolean
|
|
295
244
|
* }} [options]
|
|
296
|
-
* @returns {void}
|
|
245
|
+
* @returns {() => void} Stops observation and cancels queued work.
|
|
297
246
|
*/
|
|
298
247
|
const createLayoutObserver = (
|
|
299
248
|
target,
|
|
300
249
|
apply,
|
|
301
250
|
{
|
|
302
251
|
getResizeValue,
|
|
303
|
-
delay = 250,
|
|
304
|
-
observeResize = true,
|
|
305
|
-
observeIntersection = false,
|
|
306
252
|
observeDirectChildrenResize = false,
|
|
307
253
|
targetMutation,
|
|
308
254
|
directChildMutation,
|
|
255
|
+
shouldApplyChildList,
|
|
309
256
|
} = {},
|
|
310
257
|
) => {
|
|
311
|
-
const
|
|
312
|
-
|
|
258
|
+
const scope = createObserverScope(target);
|
|
259
|
+
const shouldObserveIntersection = 'undefined' !== typeof IntersectionObserver;
|
|
313
260
|
let isIntersecting = !shouldObserveIntersection || isNearViewport(target);
|
|
314
|
-
let needsApply =
|
|
261
|
+
let needsApply = !isIntersecting;
|
|
315
262
|
|
|
316
|
-
const runApply = (
|
|
317
|
-
if (!
|
|
263
|
+
const runApply = () => {
|
|
264
|
+
if (scope.disposed || !target.isConnected) {
|
|
318
265
|
return;
|
|
319
266
|
}
|
|
320
267
|
|
|
321
|
-
if (
|
|
268
|
+
if (!isIntersecting) {
|
|
322
269
|
needsApply = true;
|
|
323
270
|
return;
|
|
324
271
|
}
|
|
325
272
|
|
|
326
273
|
needsApply = false;
|
|
327
|
-
apply(
|
|
274
|
+
apply(target, scope);
|
|
328
275
|
};
|
|
329
276
|
|
|
330
|
-
const schedule = createScheduledTargetCallback(target, runApply);
|
|
277
|
+
const schedule = createScheduledTargetCallback(target, runApply, scope);
|
|
331
278
|
const scheduleApply = () => {
|
|
332
|
-
if (!target?.isConnected) {
|
|
333
|
-
return;
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
if (shouldObserveIntersection && !isIntersecting) {
|
|
337
|
-
needsApply = true;
|
|
279
|
+
if (scope.disposed || !target?.isConnected) {
|
|
338
280
|
return;
|
|
339
281
|
}
|
|
340
282
|
|
|
341
283
|
needsApply = true;
|
|
342
|
-
|
|
284
|
+
if (isIntersecting) {
|
|
285
|
+
schedule();
|
|
286
|
+
}
|
|
343
287
|
};
|
|
344
288
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
delay,
|
|
356
|
-
onChildList: () => {
|
|
357
|
-
syncDirectChildAttributes();
|
|
358
|
-
},
|
|
359
|
-
})
|
|
360
|
-
: {
|
|
361
|
-
resizeObserver: createResizeObserver(target, scheduleApply, {
|
|
362
|
-
getValue: getResizeValue,
|
|
363
|
-
delay,
|
|
364
|
-
}),
|
|
365
|
-
};
|
|
366
|
-
|
|
367
|
-
if (shouldObserveIntersection) {
|
|
368
|
-
createIntersectionObserver(target, (entry) => {
|
|
369
|
-
isIntersecting = entry.isIntersecting;
|
|
370
|
-
if (isIntersecting && needsApply) {
|
|
371
|
-
scheduleApply();
|
|
372
|
-
}
|
|
373
|
-
});
|
|
289
|
+
const resizeObserver = createResizeObserver(target, scheduleApply, scope, getResizeValue);
|
|
290
|
+
if (observeDirectChildrenResize) {
|
|
291
|
+
observeLayoutChildren(
|
|
292
|
+
target,
|
|
293
|
+
resizeObserver,
|
|
294
|
+
scheduleApply,
|
|
295
|
+
scope,
|
|
296
|
+
directChildMutation,
|
|
297
|
+
shouldApplyChildList,
|
|
298
|
+
);
|
|
374
299
|
}
|
|
375
300
|
|
|
376
|
-
if (
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
301
|
+
if (shouldObserveIntersection) {
|
|
302
|
+
createIntersectionObserver(
|
|
303
|
+
target,
|
|
304
|
+
(entry) => {
|
|
305
|
+
isIntersecting = entry.isIntersecting;
|
|
306
|
+
if (isIntersecting && needsApply) {
|
|
307
|
+
scheduleApply();
|
|
308
|
+
}
|
|
309
|
+
},
|
|
310
|
+
scope,
|
|
311
|
+
);
|
|
382
312
|
}
|
|
383
313
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
314
|
+
if (targetMutation) {
|
|
315
|
+
createMutationObserver(
|
|
316
|
+
target,
|
|
317
|
+
targetMutation.options,
|
|
318
|
+
(entries) => {
|
|
319
|
+
if (targetMutation.shouldApply(entries)) {
|
|
320
|
+
scheduleApply();
|
|
321
|
+
}
|
|
322
|
+
},
|
|
323
|
+
scope,
|
|
324
|
+
);
|
|
395
325
|
}
|
|
396
326
|
|
|
397
|
-
if (
|
|
398
|
-
|
|
399
|
-
if (!entries.some((entry) => 'childList' === entry.type)) {
|
|
400
|
-
return;
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
syncDirectChildAttributes();
|
|
404
|
-
scheduleApply();
|
|
405
|
-
});
|
|
327
|
+
if (isIntersecting) {
|
|
328
|
+
runApply();
|
|
406
329
|
}
|
|
407
330
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
}
|
|
331
|
+
// Keep the public callable disposer; React also uses its internal refresh operation.
|
|
332
|
+
return Object.assign(scope.dispose, { refresh: runApply });
|
|
411
333
|
};
|
|
412
334
|
|
|
413
335
|
const getBorderBoxInlineSize = (entry) => entry.borderBoxSize?.[0].inlineSize;
|
|
414
336
|
|
|
415
|
-
const
|
|
337
|
+
const getElementStyle = (element) =>
|
|
338
|
+
(element.ownerDocument.defaultView ?? window).getComputedStyle(element);
|
|
339
|
+
|
|
340
|
+
const isInFlow = (element) => {
|
|
341
|
+
const { position, display } = getElementStyle(element);
|
|
342
|
+
return !['absolute', 'fixed'].includes(position) && 'none' !== display;
|
|
343
|
+
};
|
|
416
344
|
|
|
417
345
|
const hasLayoutBox = (element) => !!element?.isConnected && 0 < element.getClientRects().length;
|
|
418
346
|
|
|
@@ -468,32 +396,51 @@ const getNormalizedInlineRect = (
|
|
|
468
396
|
};
|
|
469
397
|
|
|
470
398
|
/**
|
|
471
|
-
*
|
|
399
|
+
* Compares each attribute before the batch with its final value, ignoring intermediate writes.
|
|
472
400
|
*
|
|
473
401
|
* @param {MutationRecord[]} entries Mutation records.
|
|
474
402
|
* @param {(entry: MutationRecord) => boolean} predicate Match predicate.
|
|
475
403
|
* @returns {boolean} Whether a matching attributes mutation exists.
|
|
476
404
|
*/
|
|
477
|
-
const hasAttributeMutation = (entries, predicate) =>
|
|
478
|
-
|
|
405
|
+
const hasAttributeMutation = (entries, predicate) => {
|
|
406
|
+
const seen = new Map();
|
|
407
|
+
return entries.some((entry) => {
|
|
408
|
+
if ('attributes' !== entry.type) {
|
|
409
|
+
return false;
|
|
410
|
+
}
|
|
411
|
+
const attributes = seen.get(entry.target) ?? new Set();
|
|
412
|
+
if (attributes.has(entry.attributeName)) {
|
|
413
|
+
return false;
|
|
414
|
+
}
|
|
415
|
+
attributes.add(entry.attributeName);
|
|
416
|
+
seen.set(entry.target, attributes);
|
|
417
|
+
return predicate(entry);
|
|
418
|
+
});
|
|
419
|
+
};
|
|
479
420
|
|
|
480
421
|
/**
|
|
481
422
|
* Coalesces repeated calls into the final call within the delay window.
|
|
482
423
|
*
|
|
483
424
|
* @param {Function} fn Function to wrap.
|
|
484
425
|
* @param {number} delay Delay in milliseconds.
|
|
485
|
-
* @returns {Function} Debounced function.
|
|
426
|
+
* @returns {Function & { cancel: () => void }} Debounced function with cancellation.
|
|
486
427
|
*/
|
|
487
428
|
export function debounce(fn, delay) {
|
|
488
429
|
let timer;
|
|
489
430
|
|
|
490
|
-
|
|
431
|
+
const debounced = function (...args) {
|
|
491
432
|
const context = this;
|
|
492
433
|
clearTimeout(timer);
|
|
493
434
|
timer = setTimeout(() => {
|
|
435
|
+
timer = undefined;
|
|
494
436
|
fn.apply(context, args);
|
|
495
437
|
}, delay);
|
|
496
438
|
};
|
|
439
|
+
debounced.cancel = () => {
|
|
440
|
+
clearTimeout(timer);
|
|
441
|
+
timer = undefined;
|
|
442
|
+
};
|
|
443
|
+
return debounced;
|
|
497
444
|
}
|
|
498
445
|
|
|
499
446
|
/**
|
|
@@ -502,7 +449,7 @@ export function debounce(fn, delay) {
|
|
|
502
449
|
* @param {Element} target Target element.
|
|
503
450
|
* @returns {void}
|
|
504
451
|
*/
|
|
505
|
-
|
|
452
|
+
const updateDividerLinewrap = (target) => {
|
|
506
453
|
const children = Array.from(target?.children ?? []);
|
|
507
454
|
const currentLayoutArray = withoutLayoutTokens(getLayoutTokens(target), [
|
|
508
455
|
'divider:initialized',
|
|
@@ -515,14 +462,14 @@ export const setDividerLinewrap = (target) => {
|
|
|
515
462
|
layoutTokens: withoutLayoutTokens(getLayoutTokens(child), ['-bol', '-linewrap']),
|
|
516
463
|
}));
|
|
517
464
|
|
|
518
|
-
const
|
|
465
|
+
const applyChildLayouts = () => {
|
|
519
466
|
childLayouts.forEach(({ child, layoutTokens }) => {
|
|
520
467
|
setLayoutTokens(child, layoutTokens);
|
|
521
468
|
});
|
|
522
469
|
};
|
|
523
470
|
|
|
524
471
|
if (!currentLayoutArray.some((value) => value.startsWith('-divider:'))) {
|
|
525
|
-
|
|
472
|
+
applyChildLayouts();
|
|
526
473
|
return;
|
|
527
474
|
}
|
|
528
475
|
|
|
@@ -532,41 +479,34 @@ export const setDividerLinewrap = (target) => {
|
|
|
532
479
|
}
|
|
533
480
|
|
|
534
481
|
if (!hasLayoutBox(target)) {
|
|
535
|
-
|
|
482
|
+
applyChildLayouts();
|
|
536
483
|
return;
|
|
537
484
|
}
|
|
538
485
|
|
|
539
486
|
const defaultView =
|
|
540
487
|
target?.ownerDocument?.defaultView ?? ('undefined' !== typeof window ? window : undefined);
|
|
541
488
|
if (!defaultView?.getComputedStyle) {
|
|
542
|
-
|
|
489
|
+
applyChildLayouts();
|
|
543
490
|
return;
|
|
544
491
|
}
|
|
545
492
|
|
|
546
|
-
const targetStyle =
|
|
493
|
+
const targetStyle = getElementStyle(target);
|
|
547
494
|
const flow = {
|
|
548
495
|
direction: targetStyle.getPropertyValue('direction'),
|
|
549
496
|
flexDirection: targetStyle.getPropertyValue('flex-direction'),
|
|
550
497
|
writingMode: targetStyle.getPropertyValue('writing-mode'),
|
|
551
498
|
};
|
|
552
499
|
|
|
553
|
-
const targetChildren =
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
const rect = child.getBoundingClientRect();
|
|
559
|
-
accumulator.push({
|
|
560
|
-
child,
|
|
561
|
-
layoutTokens,
|
|
562
|
-
inlineRect: getNormalizedInlineRect(rect, flow),
|
|
563
|
-
});
|
|
500
|
+
const targetChildren = [];
|
|
501
|
+
for (const entry of childLayouts) {
|
|
502
|
+
if (isInFlow(entry.child)) {
|
|
503
|
+
entry.inlineRect = getNormalizedInlineRect(entry.child.getBoundingClientRect(), flow);
|
|
504
|
+
targetChildren.push(entry);
|
|
564
505
|
}
|
|
565
|
-
|
|
566
|
-
}, []);
|
|
506
|
+
}
|
|
567
507
|
|
|
568
508
|
if (0 === targetChildren.length) {
|
|
569
|
-
|
|
509
|
+
applyChildLayouts();
|
|
570
510
|
setLayoutTokens(target, [...currentLayoutArray, 'divider:initialized']);
|
|
571
511
|
return;
|
|
572
512
|
}
|
|
@@ -574,15 +514,14 @@ export const setDividerLinewrap = (target) => {
|
|
|
574
514
|
let prevInlineRect;
|
|
575
515
|
let hasWrapped = false;
|
|
576
516
|
let isStack = true;
|
|
577
|
-
|
|
578
|
-
const nextLayoutTokens = [...layoutTokens];
|
|
517
|
+
targetChildren.forEach(({ layoutTokens, inlineRect }, index) => {
|
|
579
518
|
const isBeginningOfLine =
|
|
580
519
|
0 === index ||
|
|
581
520
|
inlineRect.start < prevInlineRect.end - layoutPositionTolerance ||
|
|
582
521
|
inlineRect.start <= prevInlineRect.start + layoutPositionTolerance;
|
|
583
522
|
|
|
584
523
|
if (isBeginningOfLine) {
|
|
585
|
-
|
|
524
|
+
layoutTokens.push('-bol');
|
|
586
525
|
if (0 < index) {
|
|
587
526
|
hasWrapped = true;
|
|
588
527
|
}
|
|
@@ -591,19 +530,12 @@ export const setDividerLinewrap = (target) => {
|
|
|
591
530
|
}
|
|
592
531
|
|
|
593
532
|
if (hasWrapped) {
|
|
594
|
-
|
|
533
|
+
layoutTokens.push('-linewrap');
|
|
595
534
|
}
|
|
596
535
|
|
|
597
536
|
prevInlineRect = inlineRect;
|
|
598
|
-
return { child, layoutTokens: nextLayoutTokens };
|
|
599
|
-
});
|
|
600
|
-
|
|
601
|
-
const nextChildLayoutMap = new Map(
|
|
602
|
-
nextChildLayouts.map(({ child, layoutTokens }) => [child, layoutTokens]),
|
|
603
|
-
);
|
|
604
|
-
childLayouts.forEach(({ child, layoutTokens }) => {
|
|
605
|
-
setLayoutTokens(child, nextChildLayoutMap.get(child) ?? layoutTokens);
|
|
606
537
|
});
|
|
538
|
+
applyChildLayouts();
|
|
607
539
|
|
|
608
540
|
const nextTargetLayout = [...currentLayoutArray];
|
|
609
541
|
if (isStack) {
|
|
@@ -614,56 +546,56 @@ export const setDividerLinewrap = (target) => {
|
|
|
614
546
|
setLayoutTokens(target, nextTargetLayout);
|
|
615
547
|
};
|
|
616
548
|
|
|
549
|
+
const withoutAttributeTokens = (value, ignoredTokens) =>
|
|
550
|
+
withoutLayoutTokens((value ?? '').split(/\s+/).filter(Boolean), ignoredTokens)
|
|
551
|
+
.sort()
|
|
552
|
+
.join(' ');
|
|
553
|
+
|
|
554
|
+
const hasChangedAttribute = (entry) => {
|
|
555
|
+
const current = entry.target.getAttribute(entry.attributeName);
|
|
556
|
+
// Behaviors may append their tokens in different orders on the same element.
|
|
557
|
+
if (['data-unitone-layout', 'class'].includes(entry.attributeName)) {
|
|
558
|
+
return withoutAttributeTokens(current, []) !== withoutAttributeTokens(entry.oldValue, []);
|
|
559
|
+
}
|
|
560
|
+
return (current ?? '') !== (entry.oldValue ?? '');
|
|
561
|
+
};
|
|
562
|
+
|
|
563
|
+
/** Refreshes the layout, deferring React-managed targets to their component. */
|
|
564
|
+
export const setDividerLinewrap = (target) => {
|
|
565
|
+
if (!requestReactLayoutRefresh(target)) updateDividerLinewrap(target);
|
|
566
|
+
};
|
|
567
|
+
|
|
617
568
|
/**
|
|
618
569
|
* Creates the observer bundle for divider layouts.
|
|
619
570
|
*
|
|
620
571
|
* @param {Element} target Target element.
|
|
621
572
|
* @param {{ ignore?: { layout?: string[], className?: string[] } }} [args]
|
|
622
|
-
* @returns {void}
|
|
573
|
+
* @returns {() => void} Stops observation and cancels queued work.
|
|
623
574
|
*/
|
|
624
575
|
export const dividersResizeObserver = (target, args = {}) => {
|
|
576
|
+
if (!args.reactOwned && requestReactLayoutRefresh(target)) return () => {};
|
|
625
577
|
const shouldRecalculateByAttributeMutation = (entry) => {
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
.join(' ');
|
|
641
|
-
|
|
642
|
-
return current !== old;
|
|
578
|
+
const { attributeName, oldValue } = entry;
|
|
579
|
+
const currentValue = entry.target.getAttribute(attributeName);
|
|
580
|
+
const ignoredTokens =
|
|
581
|
+
layoutAttributeName === attributeName
|
|
582
|
+
? [...(args?.ignore?.layout ?? [])]
|
|
583
|
+
: 'class' === attributeName
|
|
584
|
+
? [...(args?.ignore?.className ?? [])]
|
|
585
|
+
: null;
|
|
586
|
+
|
|
587
|
+
if (ignoredTokens) {
|
|
588
|
+
return (
|
|
589
|
+
withoutAttributeTokens(currentValue, ignoredTokens) !==
|
|
590
|
+
withoutAttributeTokens(oldValue, ignoredTokens)
|
|
591
|
+
);
|
|
643
592
|
}
|
|
644
593
|
|
|
645
|
-
|
|
646
|
-
const ignoreClassNames = [...(args?.ignore?.className ?? [])];
|
|
647
|
-
|
|
648
|
-
const current = (entry.target.getAttribute(entry.attributeName) ?? '')
|
|
649
|
-
.split(' ')
|
|
650
|
-
.filter((v) => !ignoreClassNames.includes(v))
|
|
651
|
-
.join(' ');
|
|
652
|
-
|
|
653
|
-
const old = (entry.oldValue ?? '')
|
|
654
|
-
.split(' ')
|
|
655
|
-
.filter((v) => !ignoreClassNames.includes(v))
|
|
656
|
-
.join(' ');
|
|
657
|
-
|
|
658
|
-
return current !== old;
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
return ['style', 'dir'].includes(entry.attributeName);
|
|
594
|
+
return ['style', 'dir'].includes(attributeName) && (currentValue ?? '') !== (oldValue ?? '');
|
|
662
595
|
};
|
|
663
596
|
|
|
664
|
-
createLayoutObserver(target,
|
|
597
|
+
return createLayoutObserver(target, updateDividerLinewrap, {
|
|
665
598
|
getResizeValue: getBorderBoxInlineSize,
|
|
666
|
-
observeIntersection: true,
|
|
667
599
|
observeDirectChildrenResize: true,
|
|
668
600
|
targetMutation: {
|
|
669
601
|
options: {
|
|
@@ -679,7 +611,6 @@ export const dividersResizeObserver = (target, args = {}) => {
|
|
|
679
611
|
},
|
|
680
612
|
directChildMutation: {
|
|
681
613
|
attributeFilter: ['style', 'data-unitone-layout', 'class'],
|
|
682
|
-
attributeOldValue: true,
|
|
683
614
|
shouldApply: shouldRecalculateByAttributeMutation,
|
|
684
615
|
},
|
|
685
616
|
});
|
|
@@ -691,18 +622,17 @@ export const dividersResizeObserver = (target, args = {}) => {
|
|
|
691
622
|
* @param {Element} target Target element.
|
|
692
623
|
* @returns {void}
|
|
693
624
|
*/
|
|
694
|
-
|
|
625
|
+
const updateStairsStep = (target) => {
|
|
695
626
|
const children = Array.from(target.children);
|
|
696
627
|
const currentLayoutArray = withoutLayoutTokens(getLayoutTokens(target), ['stairs:initialized']);
|
|
697
628
|
setLayoutTokens(target, currentLayoutArray);
|
|
698
629
|
|
|
699
|
-
|
|
700
|
-
if (!firstChild) {
|
|
630
|
+
if (0 === children.length) {
|
|
701
631
|
setLayoutTokens(target, [...currentLayoutArray, 'stairs:initialized']);
|
|
702
632
|
return;
|
|
703
633
|
}
|
|
704
634
|
|
|
705
|
-
//
|
|
635
|
+
// Measure the unshifted layout before applying steps and measuring their overflow.
|
|
706
636
|
target.style.removeProperty('--unitone--stairs-step-overflow-volume');
|
|
707
637
|
target.style.removeProperty('--unitone--max-stairs-step');
|
|
708
638
|
children.forEach((child) => {
|
|
@@ -720,12 +650,10 @@ export const setStairsStep = (target) => {
|
|
|
720
650
|
|
|
721
651
|
const isAlternatingStairs = ['up-down', 'down-up'].includes(stairsUp);
|
|
722
652
|
|
|
723
|
-
const direction =
|
|
653
|
+
const direction = getElementStyle(target).getPropertyValue('flex-direction');
|
|
724
654
|
const targetBottom = target.getBoundingClientRect().bottom;
|
|
725
655
|
const filteredChildren = children.reduce((accumulator, child) => {
|
|
726
|
-
|
|
727
|
-
const display = window.getComputedStyle(child).getPropertyValue('display');
|
|
728
|
-
if ('absolute' === position || 'fixed' === position || 'none' === display) {
|
|
656
|
+
if (!isInFlow(child)) {
|
|
729
657
|
return accumulator;
|
|
730
658
|
}
|
|
731
659
|
|
|
@@ -744,7 +672,7 @@ export const setStairsStep = (target) => {
|
|
|
744
672
|
const isBol =
|
|
745
673
|
'row-reverse' === direction ? prevRect?.left <= rect.left : prevRect?.left >= rect.left;
|
|
746
674
|
|
|
747
|
-
if (0 === index ||
|
|
675
|
+
if (0 === index || isBol) {
|
|
748
676
|
stairsStep = 0;
|
|
749
677
|
} else if (isAlternatingStairs) {
|
|
750
678
|
stairsStep = 0 === stairsStep ? 1 : 0;
|
|
@@ -779,16 +707,33 @@ export const setStairsStep = (target) => {
|
|
|
779
707
|
setLayoutTokens(target, [...currentLayoutArray, 'stairs:initialized']);
|
|
780
708
|
};
|
|
781
709
|
|
|
710
|
+
/** Refreshes the layout, deferring React-managed targets to their component. */
|
|
711
|
+
export const setStairsStep = (target) => {
|
|
712
|
+
if (!requestReactLayoutRefresh(target)) updateStairsStep(target);
|
|
713
|
+
};
|
|
714
|
+
|
|
782
715
|
/**
|
|
783
716
|
* Creates the observer bundle for stairs layouts.
|
|
784
717
|
*
|
|
785
718
|
* @param {Element} target Target element.
|
|
786
|
-
* @returns {void}
|
|
719
|
+
* @returns {() => void} Stops observation and cancels queued work.
|
|
787
720
|
*/
|
|
788
|
-
export const stairsResizeObserver = (target) => {
|
|
789
|
-
|
|
790
|
-
|
|
721
|
+
export const stairsResizeObserver = (target, args = {}) => {
|
|
722
|
+
if (!args.reactOwned && requestReactLayoutRefresh(target)) return () => {};
|
|
723
|
+
return createLayoutObserver(target, updateStairsStep, {
|
|
791
724
|
observeDirectChildrenResize: true,
|
|
725
|
+
targetMutation: {
|
|
726
|
+
options: {
|
|
727
|
+
attributes: true,
|
|
728
|
+
attributeFilter: ['style', 'class', 'dir', 'data-unitone-layout'],
|
|
729
|
+
attributeOldValue: true,
|
|
730
|
+
},
|
|
731
|
+
shouldApply: (entries) => hasAttributeMutation(entries, hasChangedAttribute),
|
|
732
|
+
},
|
|
733
|
+
directChildMutation: {
|
|
734
|
+
attributeFilter: ['style', 'class', 'dir', 'data-unitone-layout'],
|
|
735
|
+
shouldApply: hasChangedAttribute,
|
|
736
|
+
},
|
|
792
737
|
});
|
|
793
738
|
};
|
|
794
739
|
|
|
@@ -800,11 +745,7 @@ export const stairsResizeObserver = (target) => {
|
|
|
800
745
|
*/
|
|
801
746
|
const isIgnoredVerticalWritingMutationNode = (node) =>
|
|
802
747
|
node?.nodeType === Node.ELEMENT_NODE &&
|
|
803
|
-
|
|
804
|
-
'vertical-writing__thresholder',
|
|
805
|
-
'vertical-writing:initialized',
|
|
806
|
-
'vertical-writing:safari',
|
|
807
|
-
].includes(node.getAttribute('data-unitone-layout'));
|
|
748
|
+
'vertical-writing__thresholder' === node.getAttribute(layoutAttributeName);
|
|
808
749
|
|
|
809
750
|
/**
|
|
810
751
|
* Returns whether vertical-writing mutations require re-application.
|
|
@@ -813,10 +754,9 @@ const isIgnoredVerticalWritingMutationNode = (node) =>
|
|
|
813
754
|
* @returns {boolean} Whether re-application is required.
|
|
814
755
|
*/
|
|
815
756
|
const shouldApplyVerticalWritingMutation = (entries) =>
|
|
757
|
+
hasAttributeMutation(entries, hasChangedAttribute) ||
|
|
816
758
|
entries.some((entry) => {
|
|
817
|
-
if ('
|
|
818
|
-
return true;
|
|
819
|
-
}
|
|
759
|
+
if ('characterData' === entry.type) return true;
|
|
820
760
|
|
|
821
761
|
if ('childList' !== entry.type) {
|
|
822
762
|
return false;
|
|
@@ -831,9 +771,10 @@ const shouldApplyVerticalWritingMutation = (entries) =>
|
|
|
831
771
|
* Recalculates column count and height for vertical-writing layouts.
|
|
832
772
|
*
|
|
833
773
|
* @param {Element} target Target element.
|
|
774
|
+
* @param {ReturnType<typeof createObserverScope>} [scope] Observer lifetime.
|
|
834
775
|
* @returns {void}
|
|
835
776
|
*/
|
|
836
|
-
|
|
777
|
+
const updateColumnCountForVertical = (target, scope) => {
|
|
837
778
|
if (!target) {
|
|
838
779
|
return;
|
|
839
780
|
}
|
|
@@ -854,10 +795,7 @@ export const setColumnCountForVertical = (target) => {
|
|
|
854
795
|
Array.from(target.children)
|
|
855
796
|
.reverse()
|
|
856
797
|
.some((child) => {
|
|
857
|
-
if (
|
|
858
|
-
!['absolute', 'fixed'].includes(getComputedStyle(child).position) &&
|
|
859
|
-
'none' !== getComputedStyle(child).display
|
|
860
|
-
) {
|
|
798
|
+
if (isInFlow(child)) {
|
|
861
799
|
lastChild = child;
|
|
862
800
|
return true;
|
|
863
801
|
}
|
|
@@ -868,11 +806,11 @@ export const setColumnCountForVertical = (target) => {
|
|
|
868
806
|
return;
|
|
869
807
|
}
|
|
870
808
|
|
|
871
|
-
const computedStyle =
|
|
809
|
+
const computedStyle = getElementStyle(target);
|
|
872
810
|
const threshold = String(computedStyle.getPropertyValue('--unitone--threshold')).trim();
|
|
873
811
|
let forceSwitch = false;
|
|
874
812
|
|
|
875
|
-
if (threshold) {
|
|
813
|
+
if (threshold && !/^[+-]?(?:0+\.?0*|\.0+)(?:[a-z]+|%)?$/i.test(threshold)) {
|
|
876
814
|
const thresholder = target.ownerDocument.createElement('div');
|
|
877
815
|
thresholder.setAttribute(layoutAttributeName, 'vertical-writing__thresholder');
|
|
878
816
|
target.appendChild(thresholder);
|
|
@@ -892,7 +830,10 @@ export const setColumnCountForVertical = (target) => {
|
|
|
892
830
|
|
|
893
831
|
setLayoutTokens(target, [...nextLayoutTokens, 'vertical-writing:initialized']);
|
|
894
832
|
|
|
895
|
-
|
|
833
|
+
const schedule = scope
|
|
834
|
+
? (callback) => scope.requestAnimationFrame(callback)
|
|
835
|
+
: (callback) => target.ownerDocument.defaultView.requestAnimationFrame(callback);
|
|
836
|
+
schedule(() => {
|
|
896
837
|
if (!target?.isConnected) {
|
|
897
838
|
return;
|
|
898
839
|
}
|
|
@@ -915,27 +856,40 @@ export const setColumnCountForVertical = (target) => {
|
|
|
915
856
|
};
|
|
916
857
|
|
|
917
858
|
/**
|
|
918
|
-
*
|
|
859
|
+
* Recalculates vertical-writing columns without creating persistent observers.
|
|
919
860
|
*
|
|
920
861
|
* @param {Element} target Target element.
|
|
921
862
|
* @returns {void}
|
|
922
863
|
*/
|
|
923
|
-
export const
|
|
924
|
-
|
|
864
|
+
export const setColumnCountForVertical = (target) => {
|
|
865
|
+
if (!requestReactLayoutRefresh(target)) updateColumnCountForVertical(target);
|
|
866
|
+
};
|
|
867
|
+
|
|
868
|
+
/**
|
|
869
|
+
* Creates the observer bundle for vertical-writing layouts.
|
|
870
|
+
*
|
|
871
|
+
* @param {Element} target Target element.
|
|
872
|
+
* @returns {() => void} Stops observation and cancels queued work.
|
|
873
|
+
*/
|
|
874
|
+
export const verticalsResizeObserver = (target, args = {}) => {
|
|
875
|
+
if (!args.reactOwned && requestReactLayoutRefresh(target)) return () => {};
|
|
876
|
+
const applyVerticalColumns = (element, scope) => {
|
|
925
877
|
if (element.parentNode?.style) {
|
|
926
878
|
element.parentNode.style.height = '';
|
|
927
879
|
}
|
|
928
880
|
|
|
929
|
-
|
|
881
|
+
updateColumnCountForVertical(element, scope);
|
|
930
882
|
};
|
|
931
883
|
|
|
932
|
-
createLayoutObserver(target, applyVerticalColumns, {
|
|
933
|
-
|
|
934
|
-
|
|
884
|
+
return createLayoutObserver(target, applyVerticalColumns, {
|
|
885
|
+
observeDirectChildrenResize: true,
|
|
886
|
+
shouldApplyChildList: shouldApplyVerticalWritingMutation,
|
|
935
887
|
targetMutation: {
|
|
936
888
|
options: {
|
|
937
889
|
attributes: true,
|
|
938
|
-
attributeFilter: ['style'],
|
|
890
|
+
attributeFilter: ['style', 'class', 'dir', 'data-unitone-layout'],
|
|
891
|
+
attributeOldValue: true,
|
|
892
|
+
characterData: true,
|
|
939
893
|
childList: true,
|
|
940
894
|
subtree: true,
|
|
941
895
|
},
|
|
@@ -944,111 +898,94 @@ export const verticalsResizeObserver = (target) => {
|
|
|
944
898
|
});
|
|
945
899
|
};
|
|
946
900
|
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
901
|
+
const marqueeStates = new WeakMap();
|
|
902
|
+
|
|
903
|
+
/** Updates copied items while keeping the original nodes and animation intact. */
|
|
904
|
+
const updateMarquee = (target) => {
|
|
905
|
+
let parts = getMarqueeParts(target);
|
|
906
|
+
const { marquee, originals } = parts;
|
|
907
|
+
if (!marquee) return;
|
|
908
|
+
const state = marqueeStates.get(target);
|
|
909
|
+
const source = originals.map((item) => item.outerHTML).join('');
|
|
910
|
+
// Snapshots exclude generated nodes, so author additions after copies are detected too.
|
|
911
|
+
if (
|
|
912
|
+
state?.marquee !== marquee ||
|
|
913
|
+
state?.source !== source ||
|
|
914
|
+
originals.some(
|
|
915
|
+
(item) =>
|
|
916
|
+
item.matches('input, textarea, select') || item.querySelector('input, textarea, select'),
|
|
917
|
+
)
|
|
918
|
+
) {
|
|
919
|
+
parts.copies.forEach((copy) => copy.remove());
|
|
920
|
+
parts.copies = [];
|
|
921
|
+
}
|
|
955
922
|
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
923
|
+
let firstClone;
|
|
924
|
+
const renderCopies = (groups) => {
|
|
925
|
+
const count = groups * originals.length;
|
|
926
|
+
parts.copies.splice(count).forEach((copy) => copy.remove());
|
|
927
|
+
const fragment = target.ownerDocument.createDocumentFragment();
|
|
928
|
+
while (parts.copies.length < count) {
|
|
929
|
+
const copy = originals[parts.copies.length % originals.length].cloneNode(true);
|
|
930
|
+
markMarqueeCopy(copy);
|
|
931
|
+
parts.copies.push(copy);
|
|
932
|
+
fragment.append(copy);
|
|
933
|
+
firstClone ??= copy;
|
|
960
934
|
}
|
|
961
|
-
|
|
962
|
-
};
|
|
963
|
-
|
|
964
|
-
const removeInitializedToken = (element) => {
|
|
965
|
-
const layout = element.getAttribute('data-unitone-layout') ?? '';
|
|
966
|
-
const next = layout
|
|
967
|
-
.split(/\s+/)
|
|
968
|
-
.filter((value) => value && 'marquee:initialized' !== value)
|
|
969
|
-
.join(' ');
|
|
970
|
-
element.setAttribute('data-unitone-layout', next);
|
|
935
|
+
if (fragment.childNodes.length) marquee.append(fragment);
|
|
971
936
|
};
|
|
972
937
|
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
938
|
+
let layout = measureMarquee(target, parts);
|
|
939
|
+
if (layout.groups && !parts.copies.length) {
|
|
940
|
+
renderCopies(1);
|
|
941
|
+
layout = measureMarquee(target, parts);
|
|
977
942
|
}
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
943
|
+
renderCopies(layout.groups);
|
|
944
|
+
if (marquee) {
|
|
945
|
+
const travel = `${layout.rtl ? layout.distance : -layout.distance}px`;
|
|
946
|
+
if (marquee.style.getPropertyValue('--unitone--marquee-travel') !== travel) {
|
|
947
|
+
marquee.style.setProperty('--unitone--marquee-travel', travel);
|
|
948
|
+
}
|
|
949
|
+
if (layout.distance > 0) {
|
|
950
|
+
const tokens = getLayoutTokens(marquee);
|
|
951
|
+
if (!tokens.includes('marquee:initialized'))
|
|
952
|
+
setLayoutTokens(marquee, [...tokens, 'marquee:initialized']);
|
|
953
|
+
}
|
|
988
954
|
}
|
|
955
|
+
marqueeStates.set(target, { marquee, source });
|
|
956
|
+
return firstClone?.isConnected ? firstClone : undefined;
|
|
957
|
+
};
|
|
989
958
|
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
959
|
+
/**
|
|
960
|
+
* Refreshes copied items without restarting the animation.
|
|
961
|
+
* React-owned marquees receive a refresh request and render copies through React.
|
|
962
|
+
*
|
|
963
|
+
* @param {Element} target Marquee wrapper.
|
|
964
|
+
* @returns {Element | undefined} The first newly created HTML item copy, if any.
|
|
965
|
+
*/
|
|
966
|
+
export const setMarquee = (target) => {
|
|
967
|
+
if (isReactMarquee(target)) {
|
|
968
|
+
requestMarqueeRefresh(target);
|
|
969
|
+
return;
|
|
997
970
|
}
|
|
998
|
-
|
|
999
|
-
marquees.forEach((marquee) => {
|
|
1000
|
-
removeInitializedToken(marquee);
|
|
1001
|
-
});
|
|
1002
|
-
|
|
1003
|
-
requestAnimationFrame(() => {
|
|
1004
|
-
if (!target?.isConnected) {
|
|
1005
|
-
return;
|
|
1006
|
-
}
|
|
1007
|
-
getMarquees().forEach((marquee) => {
|
|
1008
|
-
addInitializedToken(marquee);
|
|
1009
|
-
});
|
|
1010
|
-
});
|
|
1011
|
-
|
|
1012
|
-
return clonedMarquee;
|
|
971
|
+
return updateMarquee(target);
|
|
1013
972
|
};
|
|
1014
973
|
|
|
1015
974
|
/**
|
|
1016
|
-
*
|
|
975
|
+
* Observes marquee and original item sizes and content changes.
|
|
976
|
+
* React components own their observer lifetime and generated children.
|
|
1017
977
|
*
|
|
1018
|
-
* @param {Element} target
|
|
1019
|
-
* @returns {void}
|
|
978
|
+
* @param {Element} target Marquee wrapper.
|
|
979
|
+
* @returns {() => void} Stops observation and cancels queued work.
|
|
1020
980
|
*/
|
|
1021
981
|
export const marqueeResizeObserver = (target) => {
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
982
|
+
if (isReactMarquee(target)) {
|
|
983
|
+
return observeMarquee(target, () => requestMarqueeRefresh(target), { listenForRefresh: false })
|
|
984
|
+
.dispose;
|
|
985
|
+
}
|
|
986
|
+
const controller = observeMarquee(target, () => updateMarquee(target));
|
|
987
|
+
return () => {
|
|
988
|
+
controller.dispose();
|
|
989
|
+
marqueeStates.delete(target);
|
|
1026
990
|
};
|
|
1027
|
-
|
|
1028
|
-
createLayoutObserver(target, applyMarquee, {
|
|
1029
|
-
observeResize: false,
|
|
1030
|
-
observeIntersection: true,
|
|
1031
|
-
targetMutation: {
|
|
1032
|
-
options: {
|
|
1033
|
-
childList: true,
|
|
1034
|
-
},
|
|
1035
|
-
shouldApply: (entries) => {
|
|
1036
|
-
const addedNodes = entries.flatMap((entry) => Array.from(entry.addedNodes ?? []));
|
|
1037
|
-
const removedNodes = entries.flatMap((entry) => Array.from(entry.removedNodes ?? []));
|
|
1038
|
-
|
|
1039
|
-
if (
|
|
1040
|
-
clonedMarquee?.isConnected &&
|
|
1041
|
-
1 === addedNodes.length &&
|
|
1042
|
-
0 === removedNodes.length &&
|
|
1043
|
-
addedNodes[0] === clonedMarquee
|
|
1044
|
-
) {
|
|
1045
|
-
clonedMarquee = null;
|
|
1046
|
-
return false;
|
|
1047
|
-
}
|
|
1048
|
-
|
|
1049
|
-
clonedMarquee = null;
|
|
1050
|
-
return true;
|
|
1051
|
-
},
|
|
1052
|
-
},
|
|
1053
|
-
});
|
|
1054
991
|
};
|