@inc2734/unitone-css 0.94.3 → 0.95.0

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.
Files changed (163) hide show
  1. package/dist/app.css +1 -1
  2. package/dist/app.js +1 -1
  3. package/dist/behaviors/dividers.js +1 -0
  4. package/dist/behaviors/index.js +1 -0
  5. package/dist/behaviors/stairs.js +1 -0
  6. package/dist/compatibility/fluid-typography.js +1 -0
  7. package/dist/compatibility/index.js +1 -0
  8. package/dist/layout-primitives/both-sides/react.js +1 -0
  9. package/dist/layout-primitives/center/react.js +1 -0
  10. package/dist/layout-primitives/cluster/react.js +1 -0
  11. package/dist/layout-primitives/container/react.js +1 -0
  12. package/dist/layout-primitives/cover/react.js +1 -0
  13. package/dist/layout-primitives/decorator/react.js +1 -0
  14. package/dist/layout-primitives/float/react.js +1 -0
  15. package/dist/layout-primitives/frame/react.js +1 -0
  16. package/dist/layout-primitives/gutters/react.js +1 -0
  17. package/dist/layout-primitives/index.js +1 -0
  18. package/dist/layout-primitives/layers/react.js +1 -0
  19. package/dist/layout-primitives/marquee/behavior.js +1 -0
  20. package/dist/layout-primitives/marquee/react.js +1 -0
  21. package/dist/layout-primitives/masonry/react.js +1 -0
  22. package/dist/layout-primitives/reel/react.js +1 -0
  23. package/dist/layout-primitives/responsive-grid/react.js +1 -0
  24. package/dist/layout-primitives/stack/react.js +1 -0
  25. package/dist/layout-primitives/switcher/react.js +1 -0
  26. package/dist/layout-primitives/text/react.js +1 -0
  27. package/dist/layout-primitives/texture/react.js +1 -0
  28. package/dist/layout-primitives/vertical-writing/behavior.js +1 -0
  29. package/dist/layout-primitives/vertical-writing/react.js +1 -0
  30. package/dist/layout-primitives/with-sidebar/react.js +1 -0
  31. package/dist/library.js +1 -1
  32. package/package.json +24 -19
  33. package/src/app.js +3 -110
  34. package/src/app.scss +4 -4
  35. package/src/behaviors/_index.scss +43 -0
  36. package/src/{helper → behaviors}/_typography.scss +1 -1
  37. package/src/behaviors/dividers.js +8 -0
  38. package/src/behaviors/index.js +2 -0
  39. package/src/behaviors/stairs.js +8 -0
  40. package/src/compatibility/fluid-typography.js +18 -0
  41. package/src/compatibility/index.js +1 -0
  42. package/src/foundation/_foundation-core.scss +107 -0
  43. package/src/foundation/_foundation.scss +2 -106
  44. package/src/foundation/_index.scss +1 -0
  45. package/src/helper/_helper.scss +2 -42
  46. package/src/layout-primitives/_index.scss +1 -0
  47. package/src/layout-primitives/_layout-primitives-core.scss +41 -0
  48. package/src/layout-primitives/_layout-primitives.scss +2 -42
  49. package/src/layout-primitives/both-sides/_both-sides-core.scss +31 -0
  50. package/src/layout-primitives/both-sides/_both-sides.scss +2 -30
  51. package/src/layout-primitives/both-sides/_index.scss +1 -0
  52. package/src/layout-primitives/both-sides/react.jsx +1 -0
  53. package/src/layout-primitives/center/_center-core.scss +22 -0
  54. package/src/layout-primitives/center/_center.scss +2 -21
  55. package/src/layout-primitives/center/_index.scss +1 -0
  56. package/src/layout-primitives/center/react.jsx +1 -0
  57. package/src/layout-primitives/cluster/_cluster-core.scss +126 -0
  58. package/src/layout-primitives/cluster/_cluster.scss +2 -125
  59. package/src/layout-primitives/cluster/_index.scss +1 -0
  60. package/src/layout-primitives/cluster/react.jsx +3 -0
  61. package/src/layout-primitives/container/_container-core.scss +18 -0
  62. package/src/layout-primitives/container/_container.scss +2 -17
  63. package/src/layout-primitives/container/_index.scss +1 -0
  64. package/src/layout-primitives/container/react.jsx +1 -0
  65. package/src/layout-primitives/cover/_cover-core.scss +80 -0
  66. package/src/layout-primitives/cover/_cover.scss +2 -79
  67. package/src/layout-primitives/cover/_index.scss +1 -0
  68. package/src/layout-primitives/cover/react.jsx +1 -0
  69. package/src/layout-primitives/decorator/_decorator-core.scss +104 -0
  70. package/src/layout-primitives/decorator/_decorator.scss +2 -103
  71. package/src/layout-primitives/decorator/_index.scss +1 -0
  72. package/src/layout-primitives/decorator/react.jsx +1 -0
  73. package/src/layout-primitives/float/_float-core.scss +29 -0
  74. package/src/layout-primitives/float/_float.scss +2 -28
  75. package/src/layout-primitives/float/_index.scss +1 -0
  76. package/src/layout-primitives/float/react.jsx +1 -0
  77. package/src/layout-primitives/frame/_frame-core.scss +36 -0
  78. package/src/layout-primitives/frame/_frame.scss +2 -35
  79. package/src/layout-primitives/frame/_index.scss +1 -0
  80. package/src/layout-primitives/frame/react.jsx +1 -0
  81. package/src/layout-primitives/gutters/_gutters-core.scss +12 -0
  82. package/src/layout-primitives/gutters/_gutters.scss +2 -11
  83. package/src/layout-primitives/gutters/_index.scss +1 -0
  84. package/src/layout-primitives/gutters/react.jsx +1 -0
  85. package/src/layout-primitives/index.js +2 -20
  86. package/src/layout-primitives/layers/_index.scss +1 -0
  87. package/src/layout-primitives/layers/_layers-core.scss +139 -0
  88. package/src/layout-primitives/layers/_layers.scss +2 -138
  89. package/src/layout-primitives/layers/react.jsx +1 -0
  90. package/src/layout-primitives/marquee/_index.scss +1 -0
  91. package/src/layout-primitives/marquee/_marquee-core.scss +73 -0
  92. package/src/layout-primitives/marquee/_marquee.scss +2 -72
  93. package/src/layout-primitives/marquee/behavior.js +8 -0
  94. package/src/layout-primitives/marquee/react.jsx +3 -0
  95. package/src/layout-primitives/masonry/_index.scss +1 -0
  96. package/src/layout-primitives/masonry/_masonry-core.scss +26 -0
  97. package/src/layout-primitives/masonry/_masonry.scss +2 -25
  98. package/src/layout-primitives/masonry/react.jsx +1 -0
  99. package/src/layout-primitives/reel/_index.scss +1 -0
  100. package/src/layout-primitives/reel/_reel-core.scss +55 -0
  101. package/src/layout-primitives/reel/_reel.scss +2 -54
  102. package/src/layout-primitives/reel/react.jsx +1 -0
  103. package/src/layout-primitives/responsive-grid/_index.scss +1 -0
  104. package/src/layout-primitives/responsive-grid/_responsive-grid-core.scss +249 -0
  105. package/src/layout-primitives/responsive-grid/_responsive-grid.scss +2 -248
  106. package/src/layout-primitives/responsive-grid/react.jsx +4 -0
  107. package/src/layout-primitives/stack/_index.scss +1 -0
  108. package/src/layout-primitives/stack/_stack-core.scss +201 -0
  109. package/src/layout-primitives/stack/_stack.scss +2 -200
  110. package/src/layout-primitives/stack/react.jsx +3 -0
  111. package/src/layout-primitives/switcher/_index.scss +1 -0
  112. package/src/layout-primitives/switcher/_switcher-core.scss +70 -0
  113. package/src/layout-primitives/switcher/_switcher.scss +2 -69
  114. package/src/layout-primitives/switcher/react.jsx +3 -0
  115. package/src/layout-primitives/text/_index.scss +1 -0
  116. package/src/layout-primitives/text/_text-core.scss +169 -0
  117. package/src/layout-primitives/text/_text.scss +2 -168
  118. package/src/layout-primitives/text/react.jsx +1 -0
  119. package/src/layout-primitives/texture/_index.scss +1 -0
  120. package/src/layout-primitives/texture/_texture-core.scss +235 -0
  121. package/src/layout-primitives/texture/_texture.scss +2 -234
  122. package/src/layout-primitives/texture/react.jsx +1 -0
  123. package/src/layout-primitives/vertical-writing/_index.scss +1 -0
  124. package/src/layout-primitives/vertical-writing/_vertical-writing-core.scss +118 -0
  125. package/src/layout-primitives/vertical-writing/_vertical-writing.scss +2 -117
  126. package/src/layout-primitives/vertical-writing/behavior.js +8 -0
  127. package/src/layout-primitives/vertical-writing/react.jsx +3 -0
  128. package/src/layout-primitives/with-sidebar/_index.scss +1 -0
  129. package/src/layout-primitives/with-sidebar/_with-sidebar-core.scss +337 -0
  130. package/src/layout-primitives/with-sidebar/_with-sidebar.scss +2 -336
  131. package/src/layout-primitives/with-sidebar/react.jsx +3 -0
  132. package/src/library.js +653 -219
  133. package/src/register-layout-initializer.js +132 -0
  134. package/src/settings/_html.scss +1 -1
  135. package/src/settings/_index.scss +1 -0
  136. package/src/settings/_root.scss +1 -1
  137. package/src/settings/_settings-core.scss +3 -0
  138. package/src/settings/_settings.scss +3 -3
  139. package/src/variables/_index.scss +1 -0
  140. package/src/variables/_variables-core.scss +78 -0
  141. package/src/variables/_variables.scss +2 -77
  142. package/dist/index.js +0 -1
  143. package/src/index.js +0 -1
  144. /package/src/{helper → behaviors}/_align-content.scss +0 -0
  145. /package/src/{helper → behaviors}/_align-items.scss +0 -0
  146. /package/src/{helper → behaviors}/_align-self.scss +0 -0
  147. /package/src/{helper → behaviors}/_align.scss +0 -0
  148. /package/src/{helper → behaviors}/_auto-phrase.scss +0 -0
  149. /package/src/{helper → behaviors}/_auto-repeat.scss +0 -0
  150. /package/src/{helper → behaviors}/_background-clip.scss +0 -0
  151. /package/src/{helper → behaviors}/_gap.scss +0 -0
  152. /package/src/{helper → behaviors}/_gutters.scss +0 -0
  153. /package/src/{helper → behaviors}/_justify-content.scss +0 -0
  154. /package/src/{helper → behaviors}/_justify-items.scss +0 -0
  155. /package/src/{helper → behaviors}/_justify-self.scss +0 -0
  156. /package/src/{helper → behaviors}/_link-decoration.scss +0 -0
  157. /package/src/{helper → behaviors}/_mix-blend-mode.scss +0 -0
  158. /package/src/{helper → behaviors}/_negative-gap.scss +0 -0
  159. /package/src/{helper → behaviors}/_overflow.scss +0 -0
  160. /package/src/{helper → behaviors}/_padding.scss +0 -0
  161. /package/src/{helper → behaviors}/_position.scss +0 -0
  162. /package/src/{helper → behaviors}/_stairs.scss +0 -0
  163. /package/src/{helper → behaviors}/_text-orientation.scss +0 -0
package/src/library.js CHANGED
@@ -1,11 +1,33 @@
1
1
  const layoutAttributeName = 'data-unitone-layout';
2
-
2
+ const layoutIntersectionMargin = 200;
3
+ const layoutIntersectionRootMargin = `${layoutIntersectionMargin}px 0px`;
4
+
5
+ /**
6
+ * Returns layout tokens from the target element.
7
+ *
8
+ * @param {Element | null | undefined} element Target element.
9
+ * @returns {string[]} Layout tokens.
10
+ */
3
11
  const getLayoutTokens = (element) =>
4
12
  (element.getAttribute(layoutAttributeName) ?? '').split(/\s+/).filter(Boolean);
5
13
 
14
+ /**
15
+ * Returns tokens with the specified values removed.
16
+ *
17
+ * @param {string[]} tokens Source tokens.
18
+ * @param {string[]} removedTokens Tokens to remove.
19
+ * @returns {string[]} Filtered tokens.
20
+ */
6
21
  const withoutLayoutTokens = (tokens, removedTokens) =>
7
22
  tokens.filter((value) => !removedTokens.includes(value));
8
23
 
24
+ /**
25
+ * Updates the layout token attribute on the element.
26
+ *
27
+ * @param {Element} element Target element.
28
+ * @param {string[]} tokens Tokens to set.
29
+ * @returns {void}
30
+ */
9
31
  const setLayoutTokens = (element, tokens) => {
10
32
  const nextValue = tokens.filter(Boolean).join(' ');
11
33
  if ((element.getAttribute(layoutAttributeName) ?? '') !== nextValue) {
@@ -13,9 +35,15 @@ const setLayoutTokens = (element, tokens) => {
13
35
  }
14
36
  };
15
37
 
38
+ /**
39
+ * Observes target resizes and invokes the callback when a relevant change is detected.
40
+ *
41
+ * @param {Element} target Target element.
42
+ * @param {(target: Element, entry?: ResizeObserverEntry) => void} callback Callback to run.
43
+ * @param {{ getValue?: (entry: ResizeObserverEntry) => unknown, delay?: number }} [options]
44
+ * @returns {ResizeObserver} ResizeObserver instance.
45
+ */
16
46
  const createResizeObserver = (target, callback, { getValue, delay = 250 } = {}) => {
17
- callback(target);
18
-
19
47
  let prevValue;
20
48
  let isFirstEntry = true;
21
49
 
@@ -42,6 +70,14 @@ const createResizeObserver = (target, callback, { getValue, delay = 250 } = {})
42
70
  return observer;
43
71
  };
44
72
 
73
+ /**
74
+ * Creates a MutationObserver for the target node.
75
+ *
76
+ * @param {Node} target Target node.
77
+ * @param {MutationObserverInit} options Observer options.
78
+ * @param {(entries: MutationRecord[]) => void} callback Callback to run.
79
+ * @returns {MutationObserver} MutationObserver instance.
80
+ */
45
81
  const createMutationObserver = (target, options, callback) => {
46
82
  const observer = new MutationObserver((entries) => {
47
83
  requestAnimationFrame(() => {
@@ -58,12 +94,366 @@ const createMutationObserver = (target, options, callback) => {
58
94
  return observer;
59
95
  };
60
96
 
97
+ /**
98
+ * Creates an IntersectionObserver for the target element.
99
+ *
100
+ * @param {Element} target Target element.
101
+ * @param {(entry: IntersectionObserverEntry) => void} callback Callback to run.
102
+ * @returns {IntersectionObserver} IntersectionObserver instance.
103
+ */
104
+ const createIntersectionObserver = (target, callback) => {
105
+ const observer = new IntersectionObserver(
106
+ ([entry]) => {
107
+ if (entry) {
108
+ callback(entry);
109
+ }
110
+ },
111
+ { rootMargin: layoutIntersectionRootMargin },
112
+ );
113
+
114
+ observer.observe(target);
115
+
116
+ return observer;
117
+ };
118
+
119
+ /**
120
+ * Returns a scheduler that coalesces re-application work into a single frame.
121
+ *
122
+ * @param {Element} target Target element.
123
+ * @param {(target: Element) => void} callback Callback to run.
124
+ * @returns {() => void} Schedule function.
125
+ */
126
+ const createScheduledTargetCallback = (target, callback) => {
127
+ let rafId = 0;
128
+ let defaultView;
129
+
130
+ return () => {
131
+ defaultView = target?.ownerDocument?.defaultView;
132
+ if (!defaultView?.requestAnimationFrame) {
133
+ callback(target);
134
+ return;
135
+ }
136
+
137
+ if (rafId) {
138
+ return;
139
+ }
140
+
141
+ rafId = defaultView.requestAnimationFrame(() => {
142
+ rafId = 0;
143
+ defaultView = null;
144
+
145
+ if (target?.isConnected) {
146
+ callback(target);
147
+ }
148
+ });
149
+ };
150
+ };
151
+
152
+ /**
153
+ * Observes resizes on the target and its direct children.
154
+ *
155
+ * @param {Element} target Target element.
156
+ * @param {(target: Element) => void} callback Callback to run.
157
+ * @param {{ getValue?: (entry: ResizeObserverEntry) => unknown, delay?: number, onChildList?: (entries: MutationRecord[]) => void }} [options]
158
+ * @returns {{ resizeObserver: ResizeObserver, mutationObserver: MutationObserver }}
159
+ */
160
+ const createDirectChildrenResizeObserver = (
161
+ target,
162
+ callback,
163
+ { getValue, delay = 250, onChildList } = {},
164
+ ) => {
165
+ const prevValues = new WeakMap();
166
+ const observedChildren = new Set();
167
+
168
+ const observer = new ResizeObserver(
169
+ debounce((entries) => {
170
+ let shouldApply = false;
171
+
172
+ for (const entry of entries) {
173
+ const currentValue = getValue?.(entry);
174
+ if (!prevValues.has(entry.target)) {
175
+ prevValues.set(entry.target, currentValue);
176
+ continue;
177
+ }
178
+
179
+ if (undefined === currentValue || currentValue !== prevValues.get(entry.target)) {
180
+ shouldApply = true;
181
+ }
182
+
183
+ prevValues.set(entry.target, currentValue);
184
+ }
185
+
186
+ if (shouldApply) {
187
+ callback(target);
188
+ }
189
+ }, delay),
190
+ );
191
+
192
+ const syncObservedChildren = () => {
193
+ Array.from(observedChildren).forEach((child) => {
194
+ if (child.parentElement !== target) {
195
+ observer.unobserve(child);
196
+ observedChildren.delete(child);
197
+ prevValues.delete(child);
198
+ }
199
+ });
200
+
201
+ Array.from(target?.children ?? []).forEach((child) => {
202
+ if (observedChildren.has(child)) {
203
+ return;
204
+ }
205
+
206
+ observer.observe(child);
207
+ observedChildren.add(child);
208
+ });
209
+ };
210
+
211
+ observer.observe(target);
212
+ syncObservedChildren();
213
+
214
+ const mutationObserver = createMutationObserver(target, { childList: true }, (entries) => {
215
+ if (!entries.some((entry) => 'childList' === entry.type)) {
216
+ return;
217
+ }
218
+
219
+ syncObservedChildren();
220
+ onChildList?.(entries);
221
+ callback(target);
222
+ });
223
+
224
+ return {
225
+ resizeObserver: observer,
226
+ mutationObserver,
227
+ };
228
+ };
229
+
230
+ /**
231
+ * Observes attribute changes on direct children.
232
+ *
233
+ * @param {Element} target Target element.
234
+ * @param {(target: Element) => void} callback Callback to run.
235
+ * @param {{ attributeFilter: string[], shouldApply: (entry: MutationRecord) => boolean, attributeOldValue?: boolean }} options
236
+ * @returns {{ observer: MutationObserver, syncObservedChildren: () => void }}
237
+ */
238
+ const createDirectChildrenAttributeObserver = (
239
+ target,
240
+ callback,
241
+ { attributeFilter, shouldApply, attributeOldValue = true } = {},
242
+ ) => {
243
+ const observer = new MutationObserver((entries) => {
244
+ requestAnimationFrame(() => {
245
+ if (!target?.isConnected) {
246
+ return;
247
+ }
248
+
249
+ if (
250
+ entries.some(
251
+ (entry) =>
252
+ 'attributes' === entry.type &&
253
+ entry.target.parentElement === target &&
254
+ shouldApply(entry),
255
+ )
256
+ ) {
257
+ callback(target);
258
+ }
259
+ });
260
+ });
261
+
262
+ const syncObservedChildren = () => {
263
+ observer.disconnect();
264
+ Array.from(target?.children ?? []).forEach((child) => {
265
+ observer.observe(child, {
266
+ attributes: true,
267
+ attributeFilter,
268
+ attributeOldValue,
269
+ });
270
+ });
271
+ };
272
+
273
+ syncObservedChildren();
274
+
275
+ return {
276
+ observer,
277
+ syncObservedChildren,
278
+ };
279
+ };
280
+
281
+ /**
282
+ * Creates a bundled observer setup for layout re-application.
283
+ *
284
+ * @param {Element} target Target element.
285
+ * @param {(target: Element) => void} apply Apply function.
286
+ * @param {{
287
+ * getResizeValue?: (entry: ResizeObserverEntry) => unknown,
288
+ * delay?: number,
289
+ * observeResize?: boolean,
290
+ * observeIntersection?: boolean,
291
+ * observeDirectChildrenResize?: boolean,
292
+ * targetMutation?: { options: MutationObserverInit, shouldApply?: (entries: MutationRecord[]) => boolean },
293
+ * directChildMutation?: { attributeFilter: string[], shouldApply: (entry: MutationRecord) => boolean, attributeOldValue?: boolean }
294
+ * }} [options]
295
+ * @returns {void}
296
+ */
297
+ const createLayoutObserver = (
298
+ target,
299
+ apply,
300
+ {
301
+ getResizeValue,
302
+ delay = 250,
303
+ observeResize = true,
304
+ observeIntersection = false,
305
+ observeDirectChildrenResize = false,
306
+ targetMutation,
307
+ directChildMutation,
308
+ } = {},
309
+ ) => {
310
+ const shouldObserveIntersection =
311
+ observeIntersection && 'undefined' !== typeof IntersectionObserver;
312
+ let isIntersecting = !shouldObserveIntersection || isNearViewport(target);
313
+ let needsApply = shouldObserveIntersection && !isIntersecting;
314
+
315
+ const runApply = (element = target) => {
316
+ if (!element?.isConnected) {
317
+ return;
318
+ }
319
+
320
+ if (shouldObserveIntersection && !isIntersecting) {
321
+ needsApply = true;
322
+ return;
323
+ }
324
+
325
+ needsApply = false;
326
+ apply(element);
327
+ };
328
+
329
+ const schedule = createScheduledTargetCallback(target, runApply);
330
+ const scheduleApply = () => {
331
+ if (!target?.isConnected) {
332
+ return;
333
+ }
334
+
335
+ if (shouldObserveIntersection && !isIntersecting) {
336
+ needsApply = true;
337
+ return;
338
+ }
339
+
340
+ needsApply = true;
341
+ schedule();
342
+ };
343
+
344
+ let syncDirectChildAttributes = () => {};
345
+
346
+ const resizeBundle = !observeResize
347
+ ? {
348
+ resizeObserver: null,
349
+ mutationObserver: null,
350
+ }
351
+ : observeDirectChildrenResize
352
+ ? createDirectChildrenResizeObserver(target, scheduleApply, {
353
+ getValue: getResizeValue,
354
+ delay,
355
+ onChildList: () => {
356
+ syncDirectChildAttributes();
357
+ },
358
+ })
359
+ : {
360
+ resizeObserver: createResizeObserver(target, scheduleApply, {
361
+ getValue: getResizeValue,
362
+ delay,
363
+ }),
364
+ };
365
+
366
+ if (shouldObserveIntersection) {
367
+ createIntersectionObserver(target, (entry) => {
368
+ isIntersecting = entry.isIntersecting;
369
+ if (isIntersecting && needsApply) {
370
+ scheduleApply();
371
+ }
372
+ });
373
+ }
374
+
375
+ if (targetMutation?.options) {
376
+ createMutationObserver(target, targetMutation.options, (entries) => {
377
+ if (targetMutation.shouldApply?.(entries) ?? 0 < entries.length) {
378
+ scheduleApply();
379
+ }
380
+ });
381
+ }
382
+
383
+ const directChildAttributeBundle =
384
+ directChildMutation?.attributeFilter && directChildMutation.shouldApply
385
+ ? createDirectChildrenAttributeObserver(target, scheduleApply, {
386
+ attributeFilter: directChildMutation.attributeFilter,
387
+ shouldApply: directChildMutation.shouldApply,
388
+ attributeOldValue: directChildMutation.attributeOldValue,
389
+ })
390
+ : null;
391
+
392
+ if (directChildAttributeBundle) {
393
+ syncDirectChildAttributes = directChildAttributeBundle.syncObservedChildren;
394
+ }
395
+
396
+ if (!resizeBundle.mutationObserver && directChildAttributeBundle) {
397
+ createMutationObserver(target, { childList: true }, (entries) => {
398
+ if (!entries.some((entry) => 'childList' === entry.type)) {
399
+ return;
400
+ }
401
+
402
+ syncDirectChildAttributes();
403
+ scheduleApply();
404
+ });
405
+ }
406
+
407
+ if (!shouldObserveIntersection || isIntersecting) {
408
+ runApply(target);
409
+ }
410
+ };
411
+
61
412
  const getBorderBoxInlineSize = (entry) => entry.borderBoxSize?.[0].inlineSize;
62
413
 
63
414
  const getContentRectWidth = (entry) => parseInt(entry.contentRect?.width);
64
415
 
65
416
  const hasLayoutBox = (element) => !!element?.isConnected && 0 < element.getClientRects().length;
66
417
 
418
+ const isNearViewport = (element) => {
419
+ if (!hasLayoutBox(element)) {
420
+ return false;
421
+ }
422
+
423
+ const defaultView = element?.ownerDocument?.defaultView;
424
+ if (!defaultView) {
425
+ return true;
426
+ }
427
+
428
+ const rect = element.getBoundingClientRect();
429
+ const viewportWidth = defaultView.innerWidth;
430
+ const viewportHeight = defaultView.innerHeight;
431
+
432
+ return (
433
+ rect.bottom > -layoutIntersectionMargin &&
434
+ rect.right > 0 &&
435
+ rect.top < viewportHeight + layoutIntersectionMargin &&
436
+ rect.left < viewportWidth
437
+ );
438
+ };
439
+
440
+ /**
441
+ * Returns whether the mutation list contains a matching attributes record.
442
+ *
443
+ * @param {MutationRecord[]} entries Mutation records.
444
+ * @param {(entry: MutationRecord) => boolean} predicate Match predicate.
445
+ * @returns {boolean} Whether a matching attributes mutation exists.
446
+ */
447
+ const hasAttributeMutation = (entries, predicate) =>
448
+ entries.some((entry) => 'attributes' === entry.type && predicate(entry));
449
+
450
+ /**
451
+ * Coalesces repeated calls into the final call within the delay window.
452
+ *
453
+ * @param {Function} fn Function to wrap.
454
+ * @param {number} delay Delay in milliseconds.
455
+ * @returns {Function} Debounced function.
456
+ */
67
457
  export function debounce(fn, delay) {
68
458
  let timer;
69
459
 
@@ -76,6 +466,12 @@ export function debounce(fn, delay) {
76
466
  };
77
467
  }
78
468
 
469
+ /**
470
+ * Recalculates line wrapping state for divider layouts.
471
+ *
472
+ * @param {Element} target Target element.
473
+ * @returns {void}
474
+ */
79
475
  export const setDividerLinewrap = (target) => {
80
476
  const children = Array.from(target?.children ?? []);
81
477
  const firstChild = children[0];
@@ -144,68 +540,83 @@ export const setDividerLinewrap = (target) => {
144
540
  setLayoutTokens(target, nextTargetLayout);
145
541
  };
146
542
 
543
+ /**
544
+ * Creates the observer bundle for divider layouts.
545
+ *
546
+ * @param {Element} target Target element.
547
+ * @param {{ ignore?: { layout?: string[], className?: string[] } }} [args]
548
+ * @returns {void}
549
+ */
147
550
  export const dividersResizeObserver = (target, args = {}) => {
148
- const mObserverArgs = {
149
- attributes: true,
150
- attributeFilter: ['style', 'data-unitone-layout', 'class'],
151
- attributeOldValue: true,
152
- subtree: true,
153
- characterData: true,
154
- };
155
-
156
- const observer = createResizeObserver(target, setDividerLinewrap, {
157
- getValue: getBorderBoxInlineSize,
158
- });
551
+ const shouldRecalculateByAttributeMutation = (entry) => {
552
+ if ('data-unitone-layout' === entry.attributeName) {
553
+ const ignoreUnitoneLayouts = [
554
+ ...(args?.ignore?.layout ?? []),
555
+ ...['divider:initialized', '-bol', '-linewrap', '-stack'],
556
+ ];
557
+
558
+ const current = (entry.target.getAttribute(entry.attributeName) ?? '')
559
+ .split(' ')
560
+ .filter((v) => !ignoreUnitoneLayouts.includes(v))
561
+ .join(' ');
562
+
563
+ const old = (entry.oldValue ?? '')
564
+ .split(' ')
565
+ .filter((v) => !ignoreUnitoneLayouts.includes(v))
566
+ .join(' ');
567
+
568
+ return current !== old;
569
+ }
159
570
 
160
- const mObserver = createMutationObserver(target, mObserverArgs, (entries) => {
161
- for (const entry of entries) {
162
- if ('attributes' === entry.type && 'data-unitone-layout' === entry.attributeName) {
163
- const ignoreUnitoneLayouts = [
164
- ...(args?.ignore?.layout ?? []),
165
- ...['divider:initialized', '-bol', '-linewrap', '-stack'],
166
- ];
167
-
168
- const current = (entry.target.getAttribute(entry.attributeName) ?? '')
169
- .split(' ')
170
- .filter((v) => !ignoreUnitoneLayouts.includes(v))
171
- .join(' ');
172
-
173
- const old = (entry.oldValue ?? '')
174
- .split(' ')
175
- .filter((v) => !ignoreUnitoneLayouts.includes(v))
176
- .join(' ');
177
-
178
- if (current !== old) {
179
- setDividerLinewrap(target);
180
- }
181
- } else if ('attributes' === entry.type && 'class' === entry.attributeName) {
182
- const ignoreClassNames = [...(args?.ignore?.className ?? [])];
571
+ if ('class' === entry.attributeName) {
572
+ const ignoreClassNames = [...(args?.ignore?.className ?? [])];
183
573
 
184
- const current = (entry.target.getAttribute(entry.attributeName) ?? '')
185
- .split(' ')
186
- .filter((v) => !ignoreClassNames.includes(v))
187
- .join(' ');
574
+ const current = (entry.target.getAttribute(entry.attributeName) ?? '')
575
+ .split(' ')
576
+ .filter((v) => !ignoreClassNames.includes(v))
577
+ .join(' ');
188
578
 
189
- const old = (entry.oldValue ?? '')
190
- .split(' ')
191
- .filter((v) => !ignoreClassNames.includes(v))
192
- .join(' ');
579
+ const old = (entry.oldValue ?? '')
580
+ .split(' ')
581
+ .filter((v) => !ignoreClassNames.includes(v))
582
+ .join(' ');
193
583
 
194
- if (current !== old) {
195
- setDividerLinewrap(target);
196
- }
197
- } else if ('attributes' === entry.type && 'style' === entry.attributeName) {
198
- setDividerLinewrap(target);
199
- }
584
+ return current !== old;
200
585
  }
201
- });
202
586
 
203
- return {
204
- resizeObserver: observer,
205
- mutationObserver: mObserver,
587
+ return 'style' === entry.attributeName;
206
588
  };
589
+
590
+ createLayoutObserver(target, setDividerLinewrap, {
591
+ getResizeValue: getBorderBoxInlineSize,
592
+ observeIntersection: true,
593
+ observeDirectChildrenResize: true,
594
+ targetMutation: {
595
+ options: {
596
+ attributes: true,
597
+ attributeFilter: ['style', 'data-unitone-layout', 'class'],
598
+ attributeOldValue: true,
599
+ },
600
+ shouldApply: (entries) =>
601
+ hasAttributeMutation(
602
+ entries,
603
+ (entry) => entry.target === target && shouldRecalculateByAttributeMutation(entry),
604
+ ),
605
+ },
606
+ directChildMutation: {
607
+ attributeFilter: ['style', 'data-unitone-layout', 'class'],
608
+ attributeOldValue: true,
609
+ shouldApply: shouldRecalculateByAttributeMutation,
610
+ },
611
+ });
207
612
  };
208
613
 
614
+ /**
615
+ * Recalculates step values for stairs layouts.
616
+ *
617
+ * @param {Element} target Target element.
618
+ * @returns {void}
619
+ */
209
620
  export const setStairsStep = (target) => {
210
621
  const children = Array.from(target.children);
211
622
  const currentLayoutArray = withoutLayoutTokens(getLayoutTokens(target), ['stairs:initialized']);
@@ -294,33 +705,79 @@ export const setStairsStep = (target) => {
294
705
  setLayoutTokens(target, [...currentLayoutArray, 'stairs:initialized']);
295
706
  };
296
707
 
708
+ /**
709
+ * Creates the observer bundle for stairs layouts.
710
+ *
711
+ * @param {Element} target Target element.
712
+ * @returns {void}
713
+ */
297
714
  export const stairsResizeObserver = (target) => {
298
- return createResizeObserver(target, setStairsStep);
715
+ createLayoutObserver(target, setStairsStep, {
716
+ observeIntersection: true,
717
+ observeDirectChildrenResize: true,
718
+ });
299
719
  };
300
720
 
721
+ /**
722
+ * Returns whether the node should be ignored by vertical-writing mutation handling.
723
+ *
724
+ * @param {Node | null | undefined} node Target node.
725
+ * @returns {boolean} Whether the node should be ignored.
726
+ */
727
+ const isIgnoredVerticalWritingMutationNode = (node) =>
728
+ node?.nodeType === Node.ELEMENT_NODE &&
729
+ [
730
+ 'vertical-writing__thresholder',
731
+ 'vertical-writing:initialized',
732
+ 'vertical-writing:safari',
733
+ ].includes(node.getAttribute('data-unitone-layout'));
734
+
735
+ /**
736
+ * Returns whether vertical-writing mutations require re-application.
737
+ *
738
+ * @param {MutationRecord[]} entries Mutation records.
739
+ * @returns {boolean} Whether re-application is required.
740
+ */
741
+ const shouldApplyVerticalWritingMutation = (entries) =>
742
+ entries.some((entry) => {
743
+ if ('attributes' === entry.type) {
744
+ return true;
745
+ }
746
+
747
+ if ('childList' !== entry.type) {
748
+ return false;
749
+ }
750
+
751
+ return [...entry.addedNodes, ...entry.removedNodes].some(
752
+ (node) => !isIgnoredVerticalWritingMutationNode(node),
753
+ );
754
+ });
755
+
756
+ /**
757
+ * Recalculates column count and height for vertical-writing layouts.
758
+ *
759
+ * @param {Element} target Target element.
760
+ * @returns {void}
761
+ */
301
762
  export const setColumnCountForVertical = (target) => {
302
763
  if (!target) {
303
764
  return;
304
765
  }
305
766
 
306
- let currentLayoutArray = (target.getAttribute('data-unitone-layout') ?? '').split(/\s+/);
307
- if (
308
- currentLayoutArray.some((value) =>
309
- ['vertical-writing:initialized', 'vertical-writing:safari', '-force-switch'].includes(value),
310
- )
311
- ) {
312
- currentLayoutArray = currentLayoutArray.filter(
313
- (value) =>
314
- !['vertical-writing:initialized', 'vertical-writing:safari', '-force-switch'].includes(
315
- value,
316
- ),
317
- );
318
- target.setAttribute('data-unitone-layout', currentLayoutArray.join(' '));
319
- }
767
+ const currentLayoutTokens = getLayoutTokens(target);
768
+ const baseLayoutTokens = withoutLayoutTokens(currentLayoutTokens, [
769
+ 'vertical-writing:safari',
770
+ '-force-switch',
771
+ ]);
772
+ const nextLayoutTokens = withoutLayoutTokens(currentLayoutTokens, [
773
+ 'vertical-writing:initialized',
774
+ 'vertical-writing:safari',
775
+ '-force-switch',
776
+ ]);
777
+ setLayoutTokens(target, baseLayoutTokens);
320
778
 
321
779
  let lastChild;
322
- [].slice
323
- .call(target.children)
780
+ Array.from(target.children)
324
781
  .reverse()
325
782
  .some((child) => {
326
783
  if (
@@ -333,130 +790,92 @@ export const setColumnCountForVertical = (target) => {
333
790
  });
334
791
 
335
792
  if (!lastChild) {
336
- currentLayoutArray.push('vertical-writing:initialized');
337
- target.setAttribute('data-unitone-layout', currentLayoutArray.join(' '));
793
+ setLayoutTokens(target, [...nextLayoutTokens, 'vertical-writing:initialized']);
338
794
  return;
339
795
  }
340
796
 
341
- // Process of the threshold
342
797
  const computedStyle = getComputedStyle(target);
343
798
  const threshold = String(computedStyle.getPropertyValue('--unitone--threshold')).trim();
344
799
  let forceSwitch = false;
345
800
 
346
- setTimeout(() => {
347
- if (!target?.isConnected) {
348
- return;
801
+ if (threshold) {
802
+ const thresholder = target.ownerDocument.createElement('div');
803
+ thresholder.setAttribute(layoutAttributeName, 'vertical-writing__thresholder');
804
+ target.appendChild(thresholder);
805
+ forceSwitch = thresholder.offsetWidth >= target.offsetWidth;
806
+ thresholder.remove();
807
+ }
808
+
809
+ if (forceSwitch) {
810
+ nextLayoutTokens.push('-force-switch');
811
+ } else {
812
+ const maybeSafari =
813
+ target.getBoundingClientRect().left > lastChild.getBoundingClientRect().left;
814
+ if (maybeSafari) {
815
+ nextLayoutTokens.push('vertical-writing:safari');
349
816
  }
817
+ }
350
818
 
351
- if (!!threshold) {
352
- const thresholder = document.createElement('div');
353
- thresholder.setAttribute('data-unitone-layout', 'vertical-writing__thresholder');
354
- target.appendChild(thresholder);
355
- forceSwitch = thresholder.offsetWidth >= target.offsetWidth;
356
- if (thresholder.parentNode) {
357
- thresholder.parentNode.removeChild(thresholder);
358
- }
819
+ setLayoutTokens(target, [...nextLayoutTokens, 'vertical-writing:initialized']);
820
+
821
+ requestAnimationFrame(() => {
822
+ if (!target?.isConnected) {
823
+ return;
359
824
  }
360
825
 
361
- if (forceSwitch) {
362
- currentLayoutArray.push('-force-switch');
363
- if (target.parentNode?.style) {
826
+ if (target.parentNode?.style) {
827
+ if (forceSwitch) {
364
828
  target.parentNode.style.height = '';
365
- }
366
- } else {
367
- // For Safari
368
- const maybeSafari =
369
- target.getBoundingClientRect().left > lastChild.getBoundingClientRect().left;
370
- if (maybeSafari) {
371
- currentLayoutArray.push('vertical-writing:safari');
829
+ return;
372
830
  }
373
831
 
374
- target.setAttribute('data-unitone-layout', currentLayoutArray.join(' '));
375
-
376
- requestAnimationFrame(() => {
377
- if (!target?.isConnected) {
378
- return;
379
- }
832
+ const targetRect = target.getBoundingClientRect();
833
+ const lastChildRect = lastChild.getBoundingClientRect();
380
834
 
381
- const targetRect = target.getBoundingClientRect();
382
- const lastChildRect = lastChild.getBoundingClientRect();
383
-
384
- const targetY = targetRect.top + targetRect.height;
385
- const lastChildY = lastChildRect.top + lastChildRect.height;
386
- if (targetY !== lastChildY) {
387
- if (target.parentNode?.style) {
388
- target.parentNode.style.height = `${Math.ceil(lastChildY - targetRect.top)}px`;
389
- }
390
- }
391
- });
835
+ const targetY = targetRect.top + targetRect.height;
836
+ const lastChildY = lastChildRect.top + lastChildRect.height;
837
+ target.parentNode.style.height =
838
+ targetY !== lastChildY ? `${Math.ceil(lastChildY - targetRect.top)}px` : '';
392
839
  }
393
-
394
- currentLayoutArray.push('vertical-writing:initialized');
395
- target.setAttribute('data-unitone-layout', currentLayoutArray.join(' '));
396
- }, 250);
840
+ });
397
841
  };
398
842
 
843
+ /**
844
+ * Creates the observer bundle for vertical-writing layouts.
845
+ *
846
+ * @param {Element} target Target element.
847
+ * @returns {void}
848
+ */
399
849
  export const verticalsResizeObserver = (target) => {
400
- let prevWidth = 0;
401
-
402
- const observer = new ResizeObserver(
403
- debounce((entries) => {
404
- for (const entry of entries) {
405
- const width = entry.contentRect?.width;
406
- if (parseInt(width) !== parseInt(prevWidth)) {
407
- prevWidth = width;
408
- entry.target.parentNode.style.height = '';
409
- setColumnCountForVertical(entry.target);
410
- }
411
- }
412
- }, 250),
413
- );
414
-
415
- observer.observe(target);
850
+ const applyVerticalColumns = (element) => {
851
+ if (element.parentNode?.style) {
852
+ element.parentNode.style.height = '';
853
+ }
416
854
 
417
- const mObserverArgs = {
418
- attributes: true,
419
- attributeFilter: ['style'],
420
- childList: true,
421
- subtree: true,
855
+ setColumnCountForVertical(element);
422
856
  };
423
857
 
424
- const mObserver = new MutationObserver((entries) => {
425
- requestAnimationFrame(() => {
426
- if (0 < entries.length) {
427
- const entry = entries[0];
428
- const addedNode = entry.addedNodes?.[0];
429
- const removedNode = entry.removedNodes?.[0];
430
- if (
431
- (addedNode?.nodeType === Node.ELEMENT_NODE &&
432
- 'vertical-writing__thresholder' === addedNode.getAttribute('data-unitone-layout')) ||
433
- (removedNode?.nodeType === Node.ELEMENT_NODE &&
434
- 'vertical-writing__thresholder' === removedNode.getAttribute('data-unitone-layout')) ||
435
- (addedNode?.nodeType === Node.ELEMENT_NODE &&
436
- 'vertical-writing:initialized' === addedNode.getAttribute('data-unitone-layout')) ||
437
- (removedNode?.nodeType === Node.ELEMENT_NODE &&
438
- 'vertical-writing:initialized' === removedNode.getAttribute('data-unitone-layout')) ||
439
- (addedNode?.nodeType === Node.ELEMENT_NODE &&
440
- 'vertical-writing:safari' === addedNode.getAttribute('data-unitone-layout')) ||
441
- (removedNode?.nodeType === Node.ELEMENT_NODE &&
442
- 'vertical-writing:safari' === removedNode.getAttribute('data-unitone-layout'))
443
- ) {
444
- return;
445
- }
446
-
447
- setColumnCountForVertical(target);
448
- }
449
- });
858
+ createLayoutObserver(target, applyVerticalColumns, {
859
+ getResizeValue: getContentRectWidth,
860
+ observeIntersection: true,
861
+ targetMutation: {
862
+ options: {
863
+ attributes: true,
864
+ attributeFilter: ['style'],
865
+ childList: true,
866
+ subtree: true,
867
+ },
868
+ shouldApply: shouldApplyVerticalWritingMutation,
869
+ },
450
870
  });
451
-
452
- mObserver.observe(target, mObserverArgs);
453
-
454
- return {
455
- resizeObserver: observer,
456
- mutationObserver: mObserver,
457
- };
458
871
  };
459
872
 
873
+ /**
874
+ * Writes the computed 1em value to a CSS custom property for Firefox.
875
+ *
876
+ * @param {HTMLElement} target Target element.
877
+ * @returns {void}
878
+ */
460
879
  export const setResult1emPxForFireFox = (target) => {
461
880
  const ownerDocument = target.ownerDocument;
462
881
  const defaultView = ownerDocument.defaultView;
@@ -471,6 +890,12 @@ export const setResult1emPxForFireFox = (target) => {
471
890
  target.style.setProperty('--unitone--result--1em-px', fontSize);
472
891
  };
473
892
 
893
+ /**
894
+ * Creates the observer bundle for the Firefox 1em measurement workaround.
895
+ *
896
+ * @param {HTMLElement} target Target element.
897
+ * @returns {void}
898
+ */
474
899
  export const result1emPxForFireFoxObserver = (target) => {
475
900
  const ownerDocument = target.ownerDocument;
476
901
  const defaultView = ownerDocument.defaultView;
@@ -480,26 +905,25 @@ export const result1emPxForFireFoxObserver = (target) => {
480
905
  return;
481
906
  }
482
907
 
483
- const mObserverArgs = {
484
- attributes: true,
485
- attributeFilter: ['style', 'data-unitone-layout', 'class'],
486
- characterData: true,
487
- };
488
-
489
- const observer = createResizeObserver(target, setResult1emPxForFireFox, {
490
- getValue: getBorderBoxInlineSize,
908
+ createLayoutObserver(target, setResult1emPxForFireFox, {
909
+ getResizeValue: getBorderBoxInlineSize,
910
+ targetMutation: {
911
+ options: {
912
+ attributes: true,
913
+ attributeFilter: ['style', 'data-unitone-layout', 'class'],
914
+ characterData: true,
915
+ },
916
+ shouldApply: () => true,
917
+ },
491
918
  });
492
-
493
- const mObserver = createMutationObserver(target, mObserverArgs, () => {
494
- setResult1emPxForFireFox(target);
495
- });
496
-
497
- return {
498
- resizeObserver: observer,
499
- mutationObserver: mObserver,
500
- };
501
919
  };
502
920
 
921
+ /**
922
+ * Creates duplicated marquee content and refreshes initialization markers.
923
+ *
924
+ * @param {Element} target Target element.
925
+ * @returns {Element | undefined} The duplicated marquee element, if created.
926
+ */
503
927
  export const setMarquee = (target) => {
504
928
  let clonedMarquee;
505
929
 
@@ -526,6 +950,10 @@ export const setMarquee = (target) => {
526
950
  return;
527
951
  }
528
952
 
953
+ const shouldRestartAnimation = Array.from(marquees).some((marquee) =>
954
+ (marquee.getAttribute(layoutAttributeName) ?? '').split(/\s+/).includes('marquee:initialized'),
955
+ );
956
+
529
957
  if (1 === target.childElementCount && 1 === marquees.length) {
530
958
  const marquee = marquees[0];
531
959
  clonedMarquee = marquee.cloneNode(true);
@@ -534,6 +962,14 @@ export const setMarquee = (target) => {
534
962
  }
535
963
 
536
964
  marquees = getMarquees();
965
+
966
+ if (!shouldRestartAnimation) {
967
+ marquees.forEach((marquee) => {
968
+ addInitializedToken(marquee);
969
+ });
970
+ return clonedMarquee;
971
+ }
972
+
537
973
  marquees.forEach((marquee) => {
538
974
  removeInitializedToken(marquee);
539
975
  });
@@ -550,6 +986,12 @@ export const setMarquee = (target) => {
550
986
  return clonedMarquee;
551
987
  };
552
988
 
989
+ /**
990
+ * Creates the observer bundle for marquee layouts.
991
+ *
992
+ * @param {Element} target Target element.
993
+ * @returns {void}
994
+ */
553
995
  export const marqueeResizeObserver = (target) => {
554
996
  let clonedMarquee;
555
997
 
@@ -557,38 +999,30 @@ export const marqueeResizeObserver = (target) => {
557
999
  clonedMarquee = setMarquee(element);
558
1000
  };
559
1001
 
560
- const observer = createResizeObserver(target, applyMarquee, {
561
- getValue: getContentRectWidth,
562
- });
563
-
564
- const mObserverArgs = {
565
- childList: true,
566
- };
567
-
568
- const mObserver = createMutationObserver(target, mObserverArgs, (entries) => {
569
- if (!target?.isConnected) {
570
- return;
571
- }
572
-
573
- const addedNodes = entries.flatMap((entry) => Array.from(entry.addedNodes ?? []));
574
- const removedNodes = entries.flatMap((entry) => Array.from(entry.removedNodes ?? []));
1002
+ createLayoutObserver(target, applyMarquee, {
1003
+ observeResize: false,
1004
+ observeIntersection: true,
1005
+ targetMutation: {
1006
+ options: {
1007
+ childList: true,
1008
+ },
1009
+ shouldApply: (entries) => {
1010
+ const addedNodes = entries.flatMap((entry) => Array.from(entry.addedNodes ?? []));
1011
+ const removedNodes = entries.flatMap((entry) => Array.from(entry.removedNodes ?? []));
575
1012
 
576
- if (
577
- clonedMarquee?.isConnected &&
578
- 1 === addedNodes.length &&
579
- 0 === removedNodes.length &&
580
- addedNodes[0] === clonedMarquee
581
- ) {
582
- clonedMarquee = null;
583
- return;
584
- }
1013
+ if (
1014
+ clonedMarquee?.isConnected &&
1015
+ 1 === addedNodes.length &&
1016
+ 0 === removedNodes.length &&
1017
+ addedNodes[0] === clonedMarquee
1018
+ ) {
1019
+ clonedMarquee = null;
1020
+ return false;
1021
+ }
585
1022
 
586
- clonedMarquee = null;
587
- applyMarquee(target);
1023
+ clonedMarquee = null;
1024
+ return true;
1025
+ },
1026
+ },
588
1027
  });
589
-
590
- return {
591
- resizeObserver: observer,
592
- mutationObserver: mObserver,
593
- };
594
1028
  };