@jsenv/dom 0.6.0 → 0.7.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 (109) hide show
  1. package/dist/jsenv_dom.js +262 -330
  2. package/package.json +2 -4
  3. package/index.js +0 -124
  4. package/src/attr/add_attribute_effect.js +0 -93
  5. package/src/attr/attributes.js +0 -32
  6. package/src/color/color_constrast.js +0 -69
  7. package/src/color/color_parsing.js +0 -319
  8. package/src/color/color_scheme.js +0 -28
  9. package/src/color/pick_light_or_dark.js +0 -34
  10. package/src/color/resolve_css_color.js +0 -60
  11. package/src/demos/3_columns_resize_demo.html +0 -84
  12. package/src/demos/3_rows_resize_demo.html +0 -89
  13. package/src/demos/aside_and_main_demo.html +0 -93
  14. package/src/demos/coordinates_demo.html +0 -450
  15. package/src/demos/document_autoscroll_demo.html +0 -517
  16. package/src/demos/drag_gesture_constraints_demo.html +0 -701
  17. package/src/demos/drag_gesture_demo.html +0 -1047
  18. package/src/demos/drag_gesture_element_to_impact_demo.html +0 -445
  19. package/src/demos/drag_reference_element_demo.html +0 -480
  20. package/src/demos/flex_details_set_demo.html +0 -302
  21. package/src/demos/flex_details_set_demo_2.html +0 -315
  22. package/src/demos/visible_rect_demo.html +0 -525
  23. package/src/element_signature.js +0 -100
  24. package/src/interaction/drag/constraint_feedback_line.js +0 -92
  25. package/src/interaction/drag/drag_constraint.js +0 -659
  26. package/src/interaction/drag/drag_debug_markers.js +0 -635
  27. package/src/interaction/drag/drag_element_positioner.js +0 -382
  28. package/src/interaction/drag/drag_gesture.js +0 -566
  29. package/src/interaction/drag/drag_resize_demo.html +0 -571
  30. package/src/interaction/drag/drag_to_move.js +0 -301
  31. package/src/interaction/drag/drag_to_resize_gesture.js +0 -68
  32. package/src/interaction/drag/drop_target_detection.js +0 -148
  33. package/src/interaction/drag/sticky_frontiers.js +0 -160
  34. package/src/interaction/event_marker.js +0 -14
  35. package/src/interaction/focus/active_element.js +0 -33
  36. package/src/interaction/focus/arrow_navigation.js +0 -599
  37. package/src/interaction/focus/element_is_focusable.js +0 -57
  38. package/src/interaction/focus/element_visibility.js +0 -111
  39. package/src/interaction/focus/find_focusable.js +0 -21
  40. package/src/interaction/focus/focus_group.js +0 -91
  41. package/src/interaction/focus/focus_group_registry.js +0 -12
  42. package/src/interaction/focus/focus_nav.js +0 -12
  43. package/src/interaction/focus/focus_nav_event_marker.js +0 -14
  44. package/src/interaction/focus/focus_trap.js +0 -105
  45. package/src/interaction/focus/tab_navigation.js +0 -128
  46. package/src/interaction/focus/tests/focus_group_skip_tab_test.html +0 -206
  47. package/src/interaction/focus/tests/tree_focus_test.html +0 -304
  48. package/src/interaction/focus/tests/tree_focus_test.jsx +0 -261
  49. package/src/interaction/focus/tests/tree_focus_test_preact.html +0 -13
  50. package/src/interaction/isolate_interactions.js +0 -161
  51. package/src/interaction/keyboard.js +0 -26
  52. package/src/interaction/scroll/capture_scroll.js +0 -47
  53. package/src/interaction/scroll/is_scrollable.js +0 -159
  54. package/src/interaction/scroll/scroll_container.js +0 -110
  55. package/src/interaction/scroll/scroll_trap.js +0 -44
  56. package/src/interaction/scroll/scrollbar_size.js +0 -20
  57. package/src/interaction/scroll/wheel_through.js +0 -138
  58. package/src/iterable_weak_set.js +0 -66
  59. package/src/position/dom_coords.js +0 -340
  60. package/src/position/offset_parent.js +0 -15
  61. package/src/position/position_fixed.js +0 -15
  62. package/src/position/position_sticky.js +0 -213
  63. package/src/position/sticky_rect.js +0 -79
  64. package/src/position/visible_rect.js +0 -486
  65. package/src/pub_sub.js +0 -31
  66. package/src/size/can_take_size.js +0 -11
  67. package/src/size/details_content_full_height.js +0 -63
  68. package/src/size/flex_details_set.js +0 -974
  69. package/src/size/get_available_height.js +0 -22
  70. package/src/size/get_available_width.js +0 -22
  71. package/src/size/get_border_sizes.js +0 -14
  72. package/src/size/get_height.js +0 -4
  73. package/src/size/get_inner_height.js +0 -15
  74. package/src/size/get_inner_width.js +0 -15
  75. package/src/size/get_margin_sizes.js +0 -10
  76. package/src/size/get_max_height.js +0 -57
  77. package/src/size/get_max_width.js +0 -47
  78. package/src/size/get_min_height.js +0 -14
  79. package/src/size/get_min_width.js +0 -14
  80. package/src/size/get_padding_sizes.js +0 -10
  81. package/src/size/get_width.js +0 -4
  82. package/src/size/hooks/use_available_height.js +0 -27
  83. package/src/size/hooks/use_available_width.js +0 -27
  84. package/src/size/hooks/use_max_height.js +0 -10
  85. package/src/size/hooks/use_max_width.js +0 -10
  86. package/src/size/hooks/use_resize_status.js +0 -62
  87. package/src/size/resize.js +0 -695
  88. package/src/size/resolve_css_size.js +0 -32
  89. package/src/style/dom_styles.js +0 -97
  90. package/src/style/style_composition.js +0 -121
  91. package/src/style/style_controller.js +0 -345
  92. package/src/style/style_default.js +0 -153
  93. package/src/style/style_default_demo.html +0 -128
  94. package/src/style/style_parsing.js +0 -375
  95. package/src/transition/demos/animation_resumption_test.xhtml +0 -500
  96. package/src/transition/demos/height_toggle_test.xhtml +0 -515
  97. package/src/transition/dom_transition.js +0 -254
  98. package/src/transition/easing.js +0 -48
  99. package/src/transition/group_transition.js +0 -261
  100. package/src/transition/transform_style_parser.js +0 -32
  101. package/src/transition/transition_playback.js +0 -366
  102. package/src/transition/transition_timeline.js +0 -79
  103. package/src/traversal.js +0 -247
  104. package/src/ui_transition/demos/content_states_transition_demo.html +0 -628
  105. package/src/ui_transition/demos/smooth_height_transition_demo.html +0 -149
  106. package/src/ui_transition/demos/transition_testing.html +0 -354
  107. package/src/ui_transition/ui_transition.js +0 -1491
  108. package/src/utils.js +0 -69
  109. package/src/value_effect.js +0 -35
@@ -1,974 +0,0 @@
1
- /**
2
- *
3
- *
4
- */
5
-
6
- import { startDragToResizeGesture } from "../interaction/drag/drag_to_resize_gesture.js";
7
- import { captureScrollState } from "../interaction/scroll/capture_scroll.js";
8
- import { forceStyles } from "../style/dom_styles.js";
9
- import { createHeightTransition } from "../transition/dom_transition.js";
10
- import { createGroupTransitionController } from "../transition/group_transition.js";
11
- import { getHeight } from "./get_height.js";
12
- import { getInnerHeight } from "./get_inner_height.js";
13
- import { getMarginSizes } from "./get_margin_sizes.js";
14
- import { getMinHeight } from "./get_min_height.js";
15
- import { resolveCSSSize } from "./resolve_css_size.js";
16
-
17
- const HEIGHT_TRANSITION_DURATION = 300;
18
- const ANIMATE_TOGGLE = true;
19
- const ANIMATE_RESIZE_AFTER_MUTATION = true;
20
- const ANIMATION_THRESHOLD_PX = 10; // Don't animate changes smaller than this
21
- const DEBUG = false;
22
-
23
- export const initFlexDetailsSet = (
24
- container,
25
- {
26
- onSizeChange,
27
- onResizableDetailsChange,
28
- onMouseResizeEnd,
29
- onRequestedSizeChange,
30
- debug = DEBUG,
31
- } = {},
32
- ) => {
33
- const flexDetailsSet = {
34
- cleanup: null,
35
- };
36
-
37
- // Create animation controller for managing height animations
38
- const transitionController = createGroupTransitionController();
39
-
40
- const cleanupCallbackSet = new Set();
41
- const cleanup = () => {
42
- // Cancel any ongoing animations
43
- transitionController.cancel();
44
-
45
- for (const cleanupCallback of cleanupCallbackSet) {
46
- cleanupCallback();
47
- }
48
- cleanupCallbackSet.clear();
49
- };
50
- flexDetailsSet.cleanup = cleanup;
51
-
52
- const spaceMap = new Map();
53
- const marginSizeMap = new Map();
54
- const requestedSpaceMap = new Map();
55
- const minSpaceMap = new Map();
56
- let allocatedSpaceMap = new Map();
57
- const canGrowSet = new Set();
58
- const canShrinkSet = new Set();
59
- let availableSpace;
60
- let remainingSpace;
61
- let lastChild;
62
- const openedDetailsArray = [];
63
- const spaceToSize = (space, element) => {
64
- const marginSize = marginSizeMap.get(element);
65
- return space - marginSize;
66
- };
67
- const sizeToSpace = (size, element) => {
68
- const marginSize = marginSizeMap.get(element);
69
- return size + marginSize;
70
- };
71
- const prepareSpaceDistribution = () => {
72
- spaceMap.clear();
73
- marginSizeMap.clear();
74
- requestedSpaceMap.clear();
75
- minSpaceMap.clear();
76
- allocatedSpaceMap.clear();
77
- canGrowSet.clear();
78
- canShrinkSet.clear();
79
- availableSpace = getInnerHeight(container);
80
- remainingSpace = availableSpace;
81
- openedDetailsArray.length = 0;
82
- lastChild = null;
83
- if (debug) {
84
- console.debug(`📐 Container space: ${availableSpace}px`);
85
- }
86
-
87
- for (const child of container.children) {
88
- lastChild = child;
89
- const marginSizes = getMarginSizes(child);
90
- const marginSize = marginSizes.top + marginSizes.bottom;
91
- marginSizeMap.set(child, marginSize);
92
-
93
- if (!isDetailsElement(child)) {
94
- const size = getHeight(child);
95
- spaceMap.set(child, size + marginSize);
96
- requestedSpaceMap.set(child, size + marginSize);
97
- minSpaceMap.set(child, size + marginSize);
98
- continue;
99
- }
100
- const details = child;
101
- let size;
102
- let requestedSize;
103
- let requestedSizeSource;
104
- let minSize;
105
- const summary = details.querySelector("summary");
106
- const summaryHeight = getHeight(summary);
107
-
108
- size = getHeight(details);
109
-
110
- if (details.open) {
111
- openedDetailsArray.push(details);
112
- canGrowSet.add(details);
113
- canShrinkSet.add(details);
114
- const detailsContent = summary.nextElementSibling;
115
- let detailsHeight;
116
- if (detailsContent) {
117
- const preserveScroll = captureScrollState(detailsContent);
118
- const restoreSizeStyle = forceStyles(detailsContent, {
119
- height: "auto",
120
- });
121
- const detailsContentHeight = getHeight(detailsContent);
122
- restoreSizeStyle();
123
- // Preserve scroll position after height manipulation
124
- preserveScroll();
125
- detailsHeight = summaryHeight + detailsContentHeight;
126
- } else {
127
- // empty details content like
128
- // <details><summary>...</summary></details>
129
- // or textual content like
130
- // <details><summary>...</summary>textual content</details>
131
- detailsHeight = size;
132
- }
133
-
134
- if (details.hasAttribute("data-requested-height")) {
135
- const requestedHeightAttribute = details.getAttribute(
136
- "data-requested-height",
137
- );
138
- requestedSize = resolveCSSSize(requestedHeightAttribute);
139
- if (isNaN(requestedSize) || !isFinite(requestedSize)) {
140
- console.warn(
141
- `details ${details.id} has invalid data-requested-height attribute: ${requestedHeightAttribute}`,
142
- );
143
- }
144
- requestedSizeSource = "data-requested-height attribute";
145
- } else {
146
- requestedSize = detailsHeight;
147
- requestedSizeSource = "summary and content height";
148
- }
149
-
150
- const dataMinHeight = details.getAttribute("data-min-height");
151
- if (dataMinHeight) {
152
- minSize = parseFloat(dataMinHeight, 10);
153
- } else {
154
- minSize = getMinHeight(details, availableSpace);
155
- }
156
- } else {
157
- requestedSize = summaryHeight;
158
- requestedSizeSource = "summary height";
159
- minSize = summaryHeight;
160
- }
161
- spaceMap.set(details, size + marginSize);
162
- requestedSpaceMap.set(details, requestedSize + marginSize);
163
- minSpaceMap.set(details, minSize + marginSize);
164
- if (debug) {
165
- const currentSizeFormatted = spaceToSize(size + marginSize, details);
166
- const requestedSizeFormatted = spaceToSize(
167
- requestedSize + marginSize,
168
- details,
169
- );
170
- const minSizeFormatted = spaceToSize(minSize + marginSize, details);
171
- console.debug(
172
- ` ${details.id}: ${currentSizeFormatted}px → wants ${requestedSizeFormatted}px (min: ${minSizeFormatted}px) [${requestedSizeSource}]`,
173
- );
174
- }
175
- }
176
- };
177
-
178
- const applyAllocatedSpaces = (resizeDetails) => {
179
- const changeSet = new Set();
180
- let maxChange = 0;
181
-
182
- for (const child of container.children) {
183
- const allocatedSpace = allocatedSpaceMap.get(child);
184
- const allocatedSize = spaceToSize(allocatedSpace, child);
185
- const space = spaceMap.get(child);
186
- const size = spaceToSize(space, child);
187
- const sizeChange = Math.abs(size - allocatedSize);
188
-
189
- if (size === allocatedSize) {
190
- continue;
191
- }
192
-
193
- // Track the maximum change to decide if animation is worth it
194
- maxChange = Math.max(maxChange, sizeChange);
195
-
196
- if (isDetailsElement(child) && child.open) {
197
- const syncDetailsContentHeight = prepareSyncDetailsContentHeight(child);
198
- changeSet.add({
199
- element: child,
200
- target: allocatedSize,
201
- sideEffect: (height, { isAnimationEnd } = {}) => {
202
- syncDetailsContentHeight(height, {
203
- isAnimation: true,
204
- isAnimationEnd,
205
- });
206
- },
207
- });
208
- } else {
209
- changeSet.add({
210
- element: child,
211
- target: allocatedSize,
212
- });
213
- }
214
- }
215
-
216
- if (changeSet.size === 0) {
217
- return;
218
- }
219
-
220
- // Don't animate if changes are too small (avoids imperceptible animations that hide scrollbars)
221
- const shouldAnimate =
222
- resizeDetails.animated && maxChange >= ANIMATION_THRESHOLD_PX;
223
-
224
- if (debug && resizeDetails.animated && !shouldAnimate) {
225
- console.debug(
226
- `🚫 Skipping animation: max change ${maxChange.toFixed(2)}px < ${ANIMATION_THRESHOLD_PX}px threshold`,
227
- );
228
- }
229
-
230
- if (!shouldAnimate) {
231
- const sizeChangeEntries = [];
232
- for (const { element, target, sideEffect } of changeSet) {
233
- element.style.height = `${target}px`;
234
- spaceMap.set(element, sizeToSpace(target, element));
235
- if (sideEffect) {
236
- sideEffect(target);
237
- }
238
- sizeChangeEntries.push({ element, value: target });
239
- }
240
- onSizeChange?.(sizeChangeEntries, resizeDetails);
241
- return;
242
- }
243
-
244
- // Create height animations for each element in changeSet
245
- const transitions = Array.from(changeSet).map(({ element, target }) => {
246
- const transition = createHeightTransition(element, target, {
247
- duration: HEIGHT_TRANSITION_DURATION,
248
- });
249
- return transition;
250
- });
251
-
252
- const transition = transitionController.animate(transitions, {
253
- onChange: (changeEntries, isLast) => {
254
- // Apply side effects for each animated element
255
- for (const { transition, value } of changeEntries) {
256
- for (const change of changeSet) {
257
- if (change.element === transition.key) {
258
- if (change.sideEffect) {
259
- change.sideEffect(value, { isAnimationEnd: isLast });
260
- }
261
- break;
262
- }
263
- }
264
- }
265
-
266
- if (onSizeChange) {
267
- // Convert animation entries to the expected format
268
- const sizeChangeEntries = changeEntries.map(
269
- ({ transition, value }) => ({
270
- element: transition.key, // targetKey is the element
271
- value,
272
- }),
273
- );
274
- onSizeChange(
275
- sizeChangeEntries,
276
- isLast ? { ...resizeDetails, animated: false } : resizeDetails,
277
- );
278
- }
279
- },
280
- });
281
- transition.play();
282
- };
283
-
284
- const allocateSpace = (child, spaceToAllocate, requestSource) => {
285
- const requestedSpace = requestedSpaceMap.get(child);
286
- const canShrink = canShrinkSet.has(child);
287
- const canGrow = canGrowSet.has(child);
288
-
289
- let allocatedSpace;
290
- let allocatedSpaceSource;
291
- allocate: {
292
- const minSpace = minSpaceMap.get(child);
293
- if (spaceToAllocate > remainingSpace) {
294
- if (remainingSpace < minSpace) {
295
- allocatedSpace = minSpace;
296
- allocatedSpaceSource = "min space";
297
- break allocate;
298
- }
299
- allocatedSpace = remainingSpace;
300
- allocatedSpaceSource = "remaining space";
301
- break allocate;
302
- }
303
- if (spaceToAllocate < minSpace) {
304
- allocatedSpace = minSpace;
305
- allocatedSpaceSource = "min space";
306
- break allocate;
307
- }
308
- allocatedSpace = spaceToAllocate;
309
- allocatedSpaceSource = requestSource;
310
- break allocate;
311
- }
312
-
313
- if (allocatedSpace < requestedSpace) {
314
- if (!canShrink) {
315
- allocatedSpace = requestedSpace;
316
- allocatedSpaceSource = `${requestSource} + cannot shrink`;
317
- }
318
- } else if (allocatedSpace > requestedSpace) {
319
- if (!canGrow) {
320
- allocatedSpace = requestedSpace;
321
- allocatedSpaceSource = `${requestSource} + cannot grow`;
322
- }
323
- }
324
-
325
- remainingSpace -= allocatedSpace;
326
- if (debug) {
327
- const allocatedSize = spaceToSize(allocatedSpace, child);
328
- const sourceInfo =
329
- allocatedSpaceSource === requestSource
330
- ? ""
331
- : ` (${allocatedSpaceSource})`;
332
- if (allocatedSpace === spaceToAllocate) {
333
- console.debug(
334
- ` → ${allocatedSize}px to "${child.id}"${sourceInfo} | ${remainingSpace}px remaining`,
335
- );
336
- } else {
337
- const requestedSize = spaceToSize(spaceToAllocate, child);
338
- console.debug(
339
- ` → ${allocatedSize}px -out of ${requestedSize}px wanted- to "${child.id}"${sourceInfo} | ${remainingSpace}px remaining`,
340
- );
341
- }
342
- }
343
- allocatedSpaceMap.set(child, allocatedSpace);
344
-
345
- const space = spaceMap.get(child);
346
- return allocatedSpace - space;
347
- };
348
- const applyDiffOnAllocatedSpace = (child, diff, source) => {
349
- if (diff === 0) {
350
- return 0;
351
- }
352
- const allocatedSpace = allocatedSpaceMap.get(child);
353
- remainingSpace += allocatedSpace;
354
- const spaceToAllocate = allocatedSpace + diff;
355
- if (debug) {
356
- console.debug(
357
- `🔄 ${child.id}: ${allocatedSpace}px + ${diff}px = ${spaceToAllocate}px (${source})`,
358
- );
359
- }
360
- allocateSpace(child, spaceToAllocate, source);
361
- const reallocatedSpace = allocatedSpaceMap.get(child);
362
- return reallocatedSpace - allocatedSpace;
363
- };
364
- const distributeAvailableSpace = (source) => {
365
- if (debug) {
366
- console.debug(
367
- `📦 Distributing ${availableSpace}px among ${container.children.length} children:`,
368
- );
369
- }
370
- for (const child of container.children) {
371
- allocateSpace(child, requestedSpaceMap.get(child), source);
372
- }
373
- if (debug) {
374
- console.debug(`📦 After distribution: ${remainingSpace}px remaining`);
375
- }
376
- };
377
- const distributeRemainingSpace = ({ childToGrow, childToShrinkFrom }) => {
378
- if (!remainingSpace) {
379
- return;
380
- }
381
- if (remainingSpace < 0) {
382
- const spaceToSteal = -remainingSpace;
383
- if (debug) {
384
- console.debug(
385
- `⚠️ Deficit: ${remainingSpace}px, stealing ${spaceToSteal}px from elements before ${childToShrinkFrom.id}`,
386
- );
387
- }
388
- updatePreviousSiblingsAllocatedSpace(
389
- childToShrinkFrom,
390
- -spaceToSteal,
391
- `remaining space is negative: ${remainingSpace}px`,
392
- );
393
- return;
394
- }
395
- if (childToGrow) {
396
- if (debug) {
397
- console.debug(
398
- `✨ Bonus: giving ${remainingSpace}px to ${childToGrow.id}`,
399
- );
400
- }
401
- applyDiffOnAllocatedSpace(
402
- childToGrow,
403
- remainingSpace,
404
- `remaining space is positive: ${remainingSpace}px`,
405
- );
406
- }
407
- };
408
-
409
- const updatePreviousSiblingsAllocatedSpace = (
410
- child,
411
- diffToApply,
412
- source,
413
- mapRemainingDiffToApply,
414
- ) => {
415
- let spaceDiffSum = 0;
416
- let remainingDiffToApply = diffToApply;
417
- let previousSibling = child.previousElementSibling;
418
- while (previousSibling) {
419
- if (mapRemainingDiffToApply) {
420
- remainingDiffToApply = mapRemainingDiffToApply(
421
- previousSibling,
422
- remainingDiffToApply,
423
- );
424
- }
425
- const spaceDiff = applyDiffOnAllocatedSpace(
426
- previousSibling,
427
- remainingDiffToApply,
428
- source,
429
- );
430
- if (spaceDiff) {
431
- spaceDiffSum += spaceDiff;
432
- remainingDiffToApply -= spaceDiff;
433
- if (!remainingDiffToApply) {
434
- break;
435
- }
436
- }
437
- previousSibling = previousSibling.previousElementSibling;
438
- }
439
- return spaceDiffSum;
440
- };
441
- const updateNextSiblingsAllocatedSpace = (
442
- child,
443
- diffToApply,
444
- reason,
445
- mapRemainingDiffToApply,
446
- ) => {
447
- let spaceDiffSum = 0;
448
- let remainingDiffToApply = diffToApply;
449
- let nextSibling = child.nextElementSibling;
450
- while (nextSibling) {
451
- if (mapRemainingDiffToApply) {
452
- remainingDiffToApply = mapRemainingDiffToApply(
453
- nextSibling,
454
- remainingDiffToApply,
455
- );
456
- }
457
- const spaceDiff = applyDiffOnAllocatedSpace(
458
- nextSibling,
459
- remainingDiffToApply,
460
- reason,
461
- );
462
- if (spaceDiff) {
463
- spaceDiffSum += spaceDiff;
464
- remainingDiffToApply -= spaceDiff;
465
- if (!remainingDiffToApply) {
466
- break;
467
- }
468
- }
469
- nextSibling = nextSibling.nextElementSibling;
470
- }
471
- return spaceDiffSum;
472
- };
473
- const updateSiblingAllocatedSpace = (child, diff, reason) => {
474
- let nextSibling = child.nextElementSibling;
475
- while (nextSibling) {
476
- if (!isDetailsElement(nextSibling)) {
477
- nextSibling = nextSibling.nextElementSibling;
478
- continue;
479
- }
480
- const spaceDiff = applyDiffOnAllocatedSpace(nextSibling, diff, reason);
481
- if (spaceDiff) {
482
- return spaceDiff;
483
- }
484
- nextSibling = nextSibling.nextElementSibling;
485
- }
486
- if (debug) {
487
- console.debug(
488
- "coult not update next sibling allocated space, try on previous siblings",
489
- );
490
- }
491
- let previousSibling = child.previousElementSibling;
492
- while (previousSibling) {
493
- if (!isDetailsElement(previousSibling)) {
494
- previousSibling = previousSibling.previousElementSibling;
495
- continue;
496
- }
497
- const spaceDiff = applyDiffOnAllocatedSpace(
498
- previousSibling,
499
- diff,
500
- reason,
501
- );
502
- if (spaceDiff) {
503
- return spaceDiff;
504
- }
505
- previousSibling = previousSibling.previousElementSibling;
506
- }
507
- return 0;
508
- };
509
-
510
- const saveCurrentSizeAsRequestedSizes = ({
511
- replaceExistingAttributes,
512
- } = {}) => {
513
- for (const child of container.children) {
514
- if (canGrowSet.has(child) || canShrinkSet.has(child)) {
515
- if (
516
- child.hasAttribute("data-requested-height") &&
517
- !replaceExistingAttributes
518
- ) {
519
- continue;
520
- }
521
- const allocatedSpace = allocatedSpaceMap.get(child);
522
- child.setAttribute("data-requested-height", allocatedSpace);
523
- }
524
- }
525
- };
526
-
527
- const updateSpaceDistribution = (resizeDetails) => {
528
- if (debug) {
529
- console.group(`updateSpaceDistribution: ${resizeDetails.reason}`);
530
- }
531
- prepareSpaceDistribution();
532
- distributeAvailableSpace(resizeDetails.reason);
533
- distributeRemainingSpace({
534
- childToGrow: openedDetailsArray[openedDetailsArray.length - 1],
535
- childToShrinkFrom: lastChild,
536
- });
537
- if (
538
- resizeDetails.reason === "initial_space_distribution" ||
539
- resizeDetails.reason === "content_change"
540
- ) {
541
- spaceMap.clear(); // force to set size at start
542
- }
543
- applyAllocatedSpaces(resizeDetails);
544
- saveCurrentSizeAsRequestedSizes();
545
- if (debug) {
546
- console.groupEnd();
547
- }
548
- };
549
-
550
- const resizableDetailsIdSet = new Set();
551
- const updateResizableDetails = () => {
552
- const currentResizableDetailsIdSet = new Set();
553
- let hasPreviousOpen = false;
554
- for (const child of container.children) {
555
- if (!isDetailsElement(child)) {
556
- continue;
557
- }
558
- if (!child.open) {
559
- continue;
560
- }
561
- if (hasPreviousOpen) {
562
- currentResizableDetailsIdSet.add(child.id);
563
- }
564
- if (!hasPreviousOpen && child.open) {
565
- hasPreviousOpen = true;
566
- }
567
- }
568
-
569
- let someNew;
570
- let someOld;
571
- for (const currentId of currentResizableDetailsIdSet) {
572
- if (!resizableDetailsIdSet.has(currentId)) {
573
- resizableDetailsIdSet.add(currentId);
574
- someNew = true;
575
- }
576
- }
577
- for (const id of resizableDetailsIdSet) {
578
- if (!currentResizableDetailsIdSet.has(id)) {
579
- resizableDetailsIdSet.delete(id);
580
- someOld = true;
581
- }
582
- }
583
- if (someNew || someOld) {
584
- onResizableDetailsChange?.(resizableDetailsIdSet);
585
- }
586
- };
587
-
588
- initial_size: {
589
- updateSpaceDistribution({
590
- reason: "initial_space_distribution",
591
- });
592
- updateResizableDetails();
593
- }
594
-
595
- update_on_toggle: {
596
- const distributeSpaceAfterToggle = (details) => {
597
- const reason = details.open
598
- ? `${details.id} just opened`
599
- : `${details.id} just closed`;
600
- if (debug) {
601
- console.group(`distributeSpaceAfterToggle: ${reason}`);
602
- }
603
- prepareSpaceDistribution();
604
- distributeAvailableSpace(reason);
605
-
606
- const requestedSpace = requestedSpaceMap.get(details);
607
- const allocatedSpace = allocatedSpaceMap.get(details);
608
- const spaceToSteal = requestedSpace - allocatedSpace - remainingSpace;
609
- if (spaceToSteal === 0) {
610
- distributeRemainingSpace({
611
- childToGrow: openedDetailsArray[openedDetailsArray.length - 1],
612
- childToShrinkFrom: lastChild,
613
- });
614
- return;
615
- }
616
- if (debug) {
617
- console.debug(
618
- `${details.id} would like to take ${requestedSpace}px (${reason}). Trying to steal ${spaceToSteal}px from sibling, remaining space: ${remainingSpace}px`,
619
- );
620
- }
621
- const spaceStolenFromSibling = -updateSiblingAllocatedSpace(
622
- details,
623
- -spaceToSteal,
624
- reason,
625
- );
626
- if (spaceStolenFromSibling) {
627
- if (debug) {
628
- console.debug(
629
- `${spaceStolenFromSibling}px space stolen from sibling`,
630
- );
631
- }
632
- applyDiffOnAllocatedSpace(details, requestedSpace, reason);
633
- } else {
634
- if (debug) {
635
- console.debug(
636
- `no space could be stolen from sibling, remaining space: ${remainingSpace}px`,
637
- );
638
- }
639
- distributeRemainingSpace({
640
- childToGrow: openedDetailsArray[0],
641
- childToShrinkFrom: lastChild,
642
- });
643
- }
644
- if (debug) {
645
- console.groupEnd();
646
- }
647
- };
648
-
649
- for (const child of container.children) {
650
- if (!isDetailsElement(child)) {
651
- continue;
652
- }
653
- const details = child;
654
- const ontoggle = () => {
655
- distributeSpaceAfterToggle(details);
656
- applyAllocatedSpaces({
657
- reason: details.open ? "details_opened" : "details_closed",
658
- animated: ANIMATE_TOGGLE,
659
- });
660
- updateResizableDetails();
661
- };
662
- if (details.open) {
663
- setTimeout(() => {
664
- details.addEventListener("toggle", ontoggle);
665
- });
666
- } else {
667
- details.addEventListener("toggle", ontoggle);
668
- }
669
- cleanupCallbackSet.add(() => {
670
- details.removeEventListener("toggle", ontoggle);
671
- });
672
- }
673
- }
674
-
675
- resize_with_mouse: {
676
- const prepareResize = () => {
677
- let resizedElement;
678
- // let startSpaceMap;
679
- let startAllocatedSpaceMap;
680
- let currentAllocatedSpaceMap;
681
-
682
- const start = (element) => {
683
- updateSpaceDistribution({
684
- reason: "mouse_resize_start",
685
- });
686
- resizedElement = element;
687
- // startSpaceMap = new Map(spaceMap);
688
- startAllocatedSpaceMap = new Map(allocatedSpaceMap);
689
- };
690
-
691
- const applyMoveDiffToSizes = (moveDiff, reason) => {
692
- let spaceDiff = 0;
693
- let remainingMoveToApply;
694
- if (moveDiff > 0) {
695
- remainingMoveToApply = moveDiff;
696
- next_siblings_grow: {
697
- // alors ici on veut grow pour tenter de restaurer la diff
698
- // entre requestedMap et spaceMap
699
- // s'il n'y en a pas alors on aura pas appliquer ce move
700
- const spaceGivenToNextSiblings = updateNextSiblingsAllocatedSpace(
701
- resizedElement,
702
- remainingMoveToApply,
703
- reason,
704
- (nextSibling) => {
705
- const requestedSpace = requestedSpaceMap.get(nextSibling);
706
- const space = spaceMap.get(nextSibling);
707
- return requestedSpace - space;
708
- },
709
- );
710
- if (spaceGivenToNextSiblings) {
711
- spaceDiff -= spaceGivenToNextSiblings;
712
- remainingMoveToApply -= spaceGivenToNextSiblings;
713
- if (debug) {
714
- console.debug(
715
- `${spaceGivenToNextSiblings}px given to previous siblings`,
716
- );
717
- }
718
- }
719
- }
720
- previous_siblings_shrink: {
721
- const spaceStolenFromPreviousSiblings =
722
- -updatePreviousSiblingsAllocatedSpace(
723
- resizedElement,
724
- -remainingMoveToApply,
725
- reason,
726
- );
727
- if (spaceStolenFromPreviousSiblings) {
728
- spaceDiff += spaceStolenFromPreviousSiblings;
729
- remainingMoveToApply -= spaceStolenFromPreviousSiblings;
730
- if (debug) {
731
- console.debug(
732
- `${spaceStolenFromPreviousSiblings}px stolen from previous siblings`,
733
- );
734
- }
735
- }
736
- }
737
- self_grow: {
738
- applyDiffOnAllocatedSpace(resizedElement, spaceDiff, reason);
739
- }
740
- }
741
-
742
- remainingMoveToApply = -moveDiff;
743
- self_shrink: {
744
- const selfShrink = -applyDiffOnAllocatedSpace(
745
- resizedElement,
746
- -remainingMoveToApply,
747
- reason,
748
- );
749
- remainingMoveToApply -= selfShrink;
750
- spaceDiff += selfShrink;
751
- }
752
- next_siblings_shrink: {
753
- const nextSiblingsShrink = -updateNextSiblingsAllocatedSpace(
754
- resizedElement,
755
- -remainingMoveToApply,
756
- reason,
757
- );
758
- if (nextSiblingsShrink) {
759
- remainingMoveToApply -= nextSiblingsShrink;
760
- spaceDiff += nextSiblingsShrink;
761
- }
762
- }
763
- previous_sibling_grow: {
764
- updatePreviousSiblingsAllocatedSpace(
765
- resizedElement,
766
- spaceDiff,
767
- reason,
768
- );
769
- }
770
- };
771
-
772
- const move = (yMove, gesture) => {
773
- // if (isNaN(moveRequestedSize) || !isFinite(moveRequestedSize)) {
774
- // console.warn(
775
- // `requestResize called with invalid size: ${moveRequestedSize}`,
776
- // );
777
- // return;
778
- // }
779
- const reason = `applying ${yMove}px move on ${resizedElement.id}`;
780
- if (debug) {
781
- console.group(reason);
782
- }
783
-
784
- const moveDiff = -yMove;
785
- applyMoveDiffToSizes(moveDiff, reason);
786
- applyAllocatedSpaces({
787
- reason: gesture.isMouseUp ? "mouse_resize_end" : "mouse_resize",
788
- });
789
- currentAllocatedSpaceMap = new Map(allocatedSpaceMap);
790
- allocatedSpaceMap = new Map(startAllocatedSpaceMap);
791
- if (debug) {
792
- console.groupEnd();
793
- }
794
- };
795
-
796
- const end = () => {
797
- if (currentAllocatedSpaceMap) {
798
- allocatedSpaceMap = currentAllocatedSpaceMap;
799
- saveCurrentSizeAsRequestedSizes({ replaceExistingAttributes: true });
800
- if (onRequestedSizeChange) {
801
- for (const [child, allocatedSpace] of allocatedSpaceMap) {
802
- const size = spaceToSize(allocatedSpace, child);
803
- onRequestedSizeChange(child, size);
804
- }
805
- }
806
- onMouseResizeEnd?.();
807
- }
808
- };
809
-
810
- return { start, move, end };
811
- };
812
-
813
- const onmousedown = (event) => {
814
- const { start, move, end } = prepareResize();
815
-
816
- startDragToResizeGesture(event, {
817
- onDragStart: (gesture) => {
818
- start(gesture.element);
819
- },
820
- onDrag: (gesture) => {
821
- const yMove = gesture.yMove;
822
- move(yMove, gesture);
823
- },
824
- onRelease: () => {
825
- end();
826
- },
827
- constrainedFeedbackLine: false,
828
- });
829
- };
830
- container.addEventListener("mousedown", onmousedown);
831
- cleanupCallbackSet.add(() => {
832
- container.removeEventListener("mousedown", onmousedown);
833
- });
834
- }
835
-
836
- update_on_container_resize: {
837
- /**
838
- * In the following HTML browser will set `<div>` height as if it was "auto"
839
- *
840
- * ```html
841
- * <details style="height: 100px;">
842
- * <summary>...</summary>
843
- * <div style="height: 100%"></div>
844
- * </details>
845
- * ```
846
- *
847
- * So we always maintain a precise px height for the details content to ensure
848
- * it takes 100% of the details height (minus the summay)
849
- *
850
- * To achieve this we need to update these px heights when the container size changes
851
- */
852
- const resizeObserver = new ResizeObserver(() => {
853
- updateSpaceDistribution({
854
- reason: "container_resize",
855
- });
856
- });
857
- resizeObserver.observe(container);
858
- cleanupCallbackSet.add(() => {
859
- resizeObserver.disconnect();
860
- });
861
- }
862
-
863
- update_on_content_change: {
864
- // Track when the DOM structure changes inside the container
865
- // This detects when:
866
- // - Details elements are added/removed
867
- // - The content inside details elements changes
868
- const mutationObserver = new MutationObserver((mutations) => {
869
- for (const mutation of mutations) {
870
- if (mutation.type === "childList") {
871
- updateSpaceDistribution({
872
- reason: "content_change",
873
- animated: ANIMATE_RESIZE_AFTER_MUTATION,
874
- });
875
- return;
876
- }
877
- if (mutation.type === "characterData") {
878
- updateSpaceDistribution({
879
- reason: "content_change",
880
- animated: ANIMATE_RESIZE_AFTER_MUTATION,
881
- });
882
- return;
883
- }
884
- }
885
- });
886
- mutationObserver.observe(container, {
887
- childList: true,
888
- subtree: true,
889
- characterData: true,
890
- });
891
- cleanupCallbackSet.add(() => {
892
- mutationObserver.disconnect();
893
- });
894
- }
895
-
896
- return flexDetailsSet;
897
- };
898
-
899
- const prepareSyncDetailsContentHeight = (details) => {
900
- const getHeightCssValue = (height) => {
901
- return `${height}px`;
902
- };
903
-
904
- const summary = details.querySelector("summary");
905
- const summaryHeight = getHeight(summary);
906
- details.style.setProperty(
907
- "--summary-height",
908
- getHeightCssValue(summaryHeight),
909
- );
910
-
911
- const content = summary.nextElementSibling;
912
- if (!content) {
913
- return (detailsHeight) => {
914
- details.style.setProperty(
915
- "--details-height",
916
- getHeightCssValue(detailsHeight),
917
- );
918
- details.style.setProperty(
919
- "--content-height",
920
- getHeightCssValue(detailsHeight - summaryHeight),
921
- );
922
- };
923
- }
924
-
925
- // Capture scroll state at the beginning before any DOM manipulation
926
- const preserveScroll = captureScrollState(content);
927
- content.style.height = "var(--content-height)";
928
-
929
- const contentComputedStyle = getComputedStyle(content);
930
- const scrollbarMightTakeHorizontalSpace =
931
- contentComputedStyle.overflowY === "auto" &&
932
- contentComputedStyle.scrollbarGutter !== "stable";
933
-
934
- return (detailsHeight, { isAnimation, isAnimationEnd } = {}) => {
935
- const contentHeight = detailsHeight - summaryHeight;
936
- details.style.setProperty(
937
- "--details-height",
938
- getHeightCssValue(detailsHeight),
939
- );
940
- details.style.setProperty(
941
- "--content-height",
942
- getHeightCssValue(contentHeight),
943
- );
944
-
945
- if (!isAnimation || isAnimationEnd) {
946
- if (scrollbarMightTakeHorizontalSpace) {
947
- // Fix scrollbar induced overflow:
948
- //
949
- // 1. browser displays a scrollbar because there is an overflow inside overflow: auto
950
- // 2. we set height exactly to the natural height required to prevent overflow
951
- //
952
- // actual: browser keeps scrollbar displayed
953
- // expected: scrollbar is hidden
954
- //
955
- // Solution: Temporarily prevent scrollbar to display
956
- // force layout recalculation, then restore
957
- const restoreOverflow = forceStyles(content, {
958
- "overflow-y": "hidden",
959
- });
960
- // eslint-disable-next-line no-unused-expressions
961
- content.offsetHeight;
962
- restoreOverflow();
963
- }
964
- }
965
-
966
- // Preserve scroll position at the end after all DOM manipulations
967
- // The captureScrollState function is smart enough to handle new dimensions
968
- preserveScroll();
969
- };
970
- };
971
-
972
- const isDetailsElement = (element) => {
973
- return element && element.tagName === "DETAILS";
974
- };