@boostdev/design-system-components 2.4.1 → 2.5.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.
- package/AGENTS.md +4 -20
- package/README.md +3 -23
- package/dist/client.cjs +128 -62
- package/dist/client.css +537 -537
- package/dist/client.d.cts +11 -1
- package/dist/client.d.ts +11 -1
- package/dist/client.js +128 -62
- package/dist/index.cjs +128 -62
- package/dist/index.css +537 -537
- package/dist/index.d.cts +11 -1
- package/dist/index.d.ts +11 -1
- package/dist/index.js +128 -62
- package/dist/web-components/index.js +36 -4
- package/package.json +2 -10
- package/src/components/layout/Grid/Grid.mdx +20 -2
- package/src/components/layout/Grid/Grid.spec.tsx +206 -4
- package/src/components/layout/Grid/Grid.tsx +15 -1
- package/src/components/layout/Grid/masonry.ts +148 -10
- package/dist/native/index.cjs +0 -4337
- package/dist/native/index.d.cts +0 -574
- package/dist/native/index.d.ts +0 -574
- package/dist/native/index.js +0 -4421
- package/src/components/interaction/Button/Button.native.mdx +0 -91
- package/src/components/interaction/Button/Button.native.spec.tsx +0 -55
- package/src/components/interaction/Button/Button.native.stories.tsx +0 -141
- package/src/components/interaction/Button/Button.native.tsx +0 -105
- package/src/components/interaction/Dialog/Dialog.native.mdx +0 -61
- package/src/components/interaction/Dialog/Dialog.native.spec.tsx +0 -73
- package/src/components/interaction/Dialog/Dialog.native.stories.tsx +0 -53
- package/src/components/interaction/Dialog/Dialog.native.tsx +0 -128
- package/src/components/interaction/Drawer/Drawer.native.mdx +0 -58
- package/src/components/interaction/Drawer/Drawer.native.spec.tsx +0 -81
- package/src/components/interaction/Drawer/Drawer.native.stories.tsx +0 -33
- package/src/components/interaction/Drawer/Drawer.native.tsx +0 -175
- package/src/components/interaction/DropdownMenu/DropdownMenu.native.mdx +0 -74
- package/src/components/interaction/DropdownMenu/DropdownMenu.native.spec.tsx +0 -78
- package/src/components/interaction/DropdownMenu/DropdownMenu.native.stories.tsx +0 -51
- package/src/components/interaction/DropdownMenu/DropdownMenu.native.tsx +0 -254
- package/src/components/interaction/Popover/Popover.native.mdx +0 -61
- package/src/components/interaction/Popover/Popover.native.spec.tsx +0 -73
- package/src/components/interaction/Popover/Popover.native.stories.tsx +0 -44
- package/src/components/interaction/Popover/Popover.native.tsx +0 -87
- package/src/components/interaction/Rating/Rating.native.mdx +0 -55
- package/src/components/interaction/Rating/Rating.native.spec.tsx +0 -38
- package/src/components/interaction/Rating/Rating.native.stories.tsx +0 -37
- package/src/components/interaction/Rating/Rating.native.tsx +0 -50
- package/src/components/interaction/Toast/Toast.native.mdx +0 -81
- package/src/components/interaction/Toast/Toast.native.spec.tsx +0 -80
- package/src/components/interaction/Toast/Toast.native.stories.tsx +0 -47
- package/src/components/interaction/Toast/Toast.native.tsx +0 -202
- package/src/components/interaction/form/Checkbox/Checkbox.native.mdx +0 -74
- package/src/components/interaction/form/Checkbox/Checkbox.native.spec.tsx +0 -42
- package/src/components/interaction/form/Checkbox/Checkbox.native.stories.tsx +0 -34
- package/src/components/interaction/form/Checkbox/Checkbox.native.tsx +0 -80
- package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.native.mdx +0 -78
- package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.native.spec.tsx +0 -57
- package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.native.stories.tsx +0 -96
- package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.native.tsx +0 -61
- package/src/components/interaction/form/Combobox/Combobox.native.mdx +0 -86
- package/src/components/interaction/form/Combobox/Combobox.native.spec.tsx +0 -60
- package/src/components/interaction/form/Combobox/Combobox.native.stories.tsx +0 -99
- package/src/components/interaction/form/Combobox/Combobox.native.tsx +0 -211
- package/src/components/interaction/form/FileInput/FileInput.native.mdx +0 -86
- package/src/components/interaction/form/FileInput/FileInput.native.spec.tsx +0 -73
- package/src/components/interaction/form/FileInput/FileInput.native.stories.tsx +0 -69
- package/src/components/interaction/form/FileInput/FileInput.native.tsx +0 -129
- package/src/components/interaction/form/FormInput/FormInput.native.mdx +0 -81
- package/src/components/interaction/form/FormInput/FormInput.native.spec.tsx +0 -51
- package/src/components/interaction/form/FormInput/FormInput.native.stories.tsx +0 -54
- package/src/components/interaction/form/FormInput/FormInput.native.tsx +0 -122
- package/src/components/interaction/form/NumberInput/NumberInput.native.mdx +0 -87
- package/src/components/interaction/form/NumberInput/NumberInput.native.spec.tsx +0 -63
- package/src/components/interaction/form/NumberInput/NumberInput.native.stories.tsx +0 -63
- package/src/components/interaction/form/NumberInput/NumberInput.native.tsx +0 -198
- package/src/components/interaction/form/Radio/Radio.native.spec.tsx +0 -79
- package/src/components/interaction/form/Radio/Radio.native.stories.tsx +0 -44
- package/src/components/interaction/form/Radio/Radio.native.tsx +0 -79
- package/src/components/interaction/form/RadioGroup/RadioGroup.native.mdx +0 -79
- package/src/components/interaction/form/RadioGroup/RadioGroup.native.spec.tsx +0 -66
- package/src/components/interaction/form/RadioGroup/RadioGroup.native.stories.tsx +0 -96
- package/src/components/interaction/form/RadioGroup/RadioGroup.native.tsx +0 -61
- package/src/components/interaction/form/SegmentedControl/SegmentedControl.native.mdx +0 -78
- package/src/components/interaction/form/SegmentedControl/SegmentedControl.native.spec.tsx +0 -82
- package/src/components/interaction/form/SegmentedControl/SegmentedControl.native.stories.tsx +0 -99
- package/src/components/interaction/form/SegmentedControl/SegmentedControl.native.tsx +0 -121
- package/src/components/interaction/form/Select/Select.native.mdx +0 -85
- package/src/components/interaction/form/Select/Select.native.spec.tsx +0 -64
- package/src/components/interaction/form/Select/Select.native.stories.tsx +0 -75
- package/src/components/interaction/form/Select/Select.native.tsx +0 -252
- package/src/components/interaction/form/Slider/Slider.native.mdx +0 -83
- package/src/components/interaction/form/Slider/Slider.native.spec.tsx +0 -51
- package/src/components/interaction/form/Slider/Slider.native.stories.tsx +0 -58
- package/src/components/interaction/form/Slider/Slider.native.tsx +0 -188
- package/src/components/interaction/form/Switch/Switch.native.spec.tsx +0 -60
- package/src/components/interaction/form/Switch/Switch.native.stories.tsx +0 -33
- package/src/components/interaction/form/Switch/Switch.native.tsx +0 -59
- package/src/components/interaction/form/Textarea/Textarea.native.mdx +0 -76
- package/src/components/interaction/form/Textarea/Textarea.native.spec.tsx +0 -50
- package/src/components/interaction/form/Textarea/Textarea.native.stories.tsx +0 -50
- package/src/components/interaction/form/Textarea/Textarea.native.tsx +0 -108
- package/src/components/interaction/form/atoms/Label.native.spec.tsx +0 -39
- package/src/components/interaction/form/atoms/Label.native.stories.tsx +0 -18
- package/src/components/interaction/form/atoms/Label.native.tsx +0 -29
- package/src/components/interaction/form/atoms/Message.native.spec.tsx +0 -30
- package/src/components/interaction/form/atoms/Message.native.stories.tsx +0 -22
- package/src/components/interaction/form/atoms/Message.native.tsx +0 -34
- package/src/components/layout/ButtonGroup/ButtonGroup.native.spec.tsx +0 -83
- package/src/components/layout/ButtonGroup/ButtonGroup.native.stories.tsx +0 -44
- package/src/components/layout/ButtonGroup/ButtonGroup.native.tsx +0 -44
- package/src/components/layout/Card/Card.native.spec.tsx +0 -102
- package/src/components/layout/Card/Card.native.stories.tsx +0 -51
- package/src/components/layout/Card/Card.native.tsx +0 -89
- package/src/components/layout/IconWrapper/IconWrapper.native.spec.tsx +0 -39
- package/src/components/layout/IconWrapper/IconWrapper.native.stories.tsx +0 -37
- package/src/components/layout/IconWrapper/IconWrapper.native.tsx +0 -20
- package/src/components/layout/SectionHeader/SectionHeader.native.spec.tsx +0 -51
- package/src/components/layout/SectionHeader/SectionHeader.native.stories.tsx +0 -36
- package/src/components/layout/SectionHeader/SectionHeader.native.tsx +0 -79
- package/src/components/ui/Accordion/Accordion.native.mdx +0 -65
- package/src/components/ui/Accordion/Accordion.native.spec.tsx +0 -69
- package/src/components/ui/Accordion/Accordion.native.stories.tsx +0 -52
- package/src/components/ui/Accordion/Accordion.native.tsx +0 -141
- package/src/components/ui/Alert/Alert.native.mdx +0 -62
- package/src/components/ui/Alert/Alert.native.spec.tsx +0 -76
- package/src/components/ui/Alert/Alert.native.stories.tsx +0 -41
- package/src/components/ui/Alert/Alert.native.tsx +0 -94
- package/src/components/ui/Avatar/Avatar.native.mdx +0 -50
- package/src/components/ui/Avatar/Avatar.native.spec.tsx +0 -47
- package/src/components/ui/Avatar/Avatar.native.stories.tsx +0 -32
- package/src/components/ui/Avatar/Avatar.native.tsx +0 -85
- package/src/components/ui/Badge/Badge.native.mdx +0 -54
- package/src/components/ui/Badge/Badge.native.spec.tsx +0 -45
- package/src/components/ui/Badge/Badge.native.stories.tsx +0 -34
- package/src/components/ui/Badge/Badge.native.tsx +0 -50
- package/src/components/ui/Breadcrumb/Breadcrumb.native.mdx +0 -52
- package/src/components/ui/Breadcrumb/Breadcrumb.native.spec.tsx +0 -49
- package/src/components/ui/Breadcrumb/Breadcrumb.native.stories.tsx +0 -46
- package/src/components/ui/Breadcrumb/Breadcrumb.native.tsx +0 -83
- package/src/components/ui/Calendar/Calendar.native.mdx +0 -58
- package/src/components/ui/Calendar/Calendar.native.spec.tsx +0 -92
- package/src/components/ui/Calendar/Calendar.native.stories.tsx +0 -45
- package/src/components/ui/Calendar/Calendar.native.tsx +0 -294
- package/src/components/ui/Carousel/Carousel.native.mdx +0 -55
- package/src/components/ui/Carousel/Carousel.native.spec.tsx +0 -55
- package/src/components/ui/Carousel/Carousel.native.stories.tsx +0 -59
- package/src/components/ui/Carousel/Carousel.native.tsx +0 -184
- package/src/components/ui/Collapsible/Collapsible.native.mdx +0 -53
- package/src/components/ui/Collapsible/Collapsible.native.spec.tsx +0 -68
- package/src/components/ui/Collapsible/Collapsible.native.stories.tsx +0 -48
- package/src/components/ui/Collapsible/Collapsible.native.tsx +0 -139
- package/src/components/ui/DescriptionList/DescriptionList.native.mdx +0 -50
- package/src/components/ui/DescriptionList/DescriptionList.native.spec.tsx +0 -37
- package/src/components/ui/DescriptionList/DescriptionList.native.stories.tsx +0 -48
- package/src/components/ui/DescriptionList/DescriptionList.native.tsx +0 -54
- package/src/components/ui/Link/Link.native.mdx +0 -53
- package/src/components/ui/Link/Link.native.spec.tsx +0 -35
- package/src/components/ui/Link/Link.native.stories.tsx +0 -46
- package/src/components/ui/Link/Link.native.tsx +0 -48
- package/src/components/ui/Loading/Loading.native.spec.tsx +0 -24
- package/src/components/ui/Loading/Loading.native.stories.tsx +0 -31
- package/src/components/ui/Loading/Loading.native.tsx +0 -29
- package/src/components/ui/NotificationBanner/NotificationBanner.native.spec.tsx +0 -85
- package/src/components/ui/NotificationBanner/NotificationBanner.native.stories.tsx +0 -37
- package/src/components/ui/NotificationBanner/NotificationBanner.native.tsx +0 -76
- package/src/components/ui/Pagination/Pagination.native.mdx +0 -62
- package/src/components/ui/Pagination/Pagination.native.spec.tsx +0 -86
- package/src/components/ui/Pagination/Pagination.native.stories.tsx +0 -47
- package/src/components/ui/Pagination/Pagination.native.tsx +0 -157
- package/src/components/ui/Progress/Progress.native.spec.tsx +0 -56
- package/src/components/ui/Progress/Progress.native.stories.tsx +0 -34
- package/src/components/ui/Progress/Progress.native.tsx +0 -84
- package/src/components/ui/ProgressCircle/ProgressCircle.native.mdx +0 -63
- package/src/components/ui/ProgressCircle/ProgressCircle.native.spec.tsx +0 -39
- package/src/components/ui/ProgressCircle/ProgressCircle.native.stories.tsx +0 -36
- package/src/components/ui/ProgressCircle/ProgressCircle.native.tsx +0 -176
- package/src/components/ui/Separator/Separator.native.spec.tsx +0 -39
- package/src/components/ui/Separator/Separator.native.stories.tsx +0 -38
- package/src/components/ui/Separator/Separator.native.tsx +0 -32
- package/src/components/ui/Skeleton/Skeleton.native.spec.tsx +0 -39
- package/src/components/ui/Skeleton/Skeleton.native.stories.tsx +0 -31
- package/src/components/ui/Skeleton/Skeleton.native.tsx +0 -42
- package/src/components/ui/Table/Table.native.mdx +0 -72
- package/src/components/ui/Table/Table.native.spec.tsx +0 -83
- package/src/components/ui/Table/Table.native.stories.tsx +0 -55
- package/src/components/ui/Table/Table.native.tsx +0 -196
- package/src/components/ui/Tabs/Tabs.native.mdx +0 -65
- package/src/components/ui/Tabs/Tabs.native.spec.tsx +0 -65
- package/src/components/ui/Tabs/Tabs.native.stories.tsx +0 -57
- package/src/components/ui/Tabs/Tabs.native.tsx +0 -133
- package/src/components/ui/Tooltip/Tooltip.native.mdx +0 -47
- package/src/components/ui/Tooltip/Tooltip.native.spec.tsx +0 -37
- package/src/components/ui/Tooltip/Tooltip.native.stories.tsx +0 -38
- package/src/components/ui/Tooltip/Tooltip.native.tsx +0 -57
- package/src/components/ui/Typography/Typography.native.mdx +0 -56
- package/src/components/ui/Typography/Typography.native.spec.tsx +0 -49
- package/src/components/ui/Typography/Typography.native.stories.tsx +0 -36
- package/src/components/ui/Typography/Typography.native.tsx +0 -65
- package/src/native/ThemeContext.tsx +0 -28
- package/src/native/tokens.ts +0 -13
- package/src/native.ts +0 -76
- package/src/stories/ReactNative.mdx +0 -156
|
@@ -65,7 +65,15 @@ function resolvePreferredColumns(
|
|
|
65
65
|
* `grid-template-rows: masonry` value, the polyfill yields to CSS.
|
|
66
66
|
*/
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
// Target ceiling on the implicit grid row count produced by the polyfill.
|
|
69
|
+
// Browsers cap implicit grid tracks for resource-protection — historically
|
|
70
|
+
// Chromium at 1000 and Firefox at ~10000; past the cap, the layout engine
|
|
71
|
+
// clamps/wraps items and they overlap visually (e.g. bottom thumbnails of a
|
|
72
|
+
// long masonry stack on top of each other in Firefox). The auto-tuner picks
|
|
73
|
+
// a `rowUnitPx` so the projected row count stays under this number. Lower
|
|
74
|
+
// is safer; higher gives finer (more pixel-accurate) row gaps. 5000 leaves
|
|
75
|
+
// 2× headroom under the strictest reasonable cap.
|
|
76
|
+
const TARGET_MAX_IMPLICIT_ROWS = 5000;
|
|
69
77
|
|
|
70
78
|
export function supportsNativeMasonry(): boolean {
|
|
71
79
|
if (typeof CSS === 'undefined' || typeof CSS.supports !== 'function') return false;
|
|
@@ -87,6 +95,17 @@ export type MasonryOptions = {
|
|
|
87
95
|
* budget for the demotion calculation.
|
|
88
96
|
*/
|
|
89
97
|
preferredColumnSpan?: Exclude<GridItemSpanValue, 'auto'>;
|
|
98
|
+
/**
|
|
99
|
+
* Manual override for the row-track unit (in pixels) the polyfill uses for
|
|
100
|
+
* `grid-auto-rows` and as the ceil() granularity of each item's row span.
|
|
101
|
+
* When omitted (recommended), the polyfill auto-picks the smallest unit that
|
|
102
|
+
* keeps projected implicit row count under `TARGET_MAX_IMPLICIT_ROWS`.
|
|
103
|
+
*
|
|
104
|
+
* Trade-off: smaller = more pixel-accurate row gaps; larger = more headroom
|
|
105
|
+
* under browser implicit-grid-track caps for very long galleries. Set this
|
|
106
|
+
* only if you've confirmed the auto-tuner is wrong for your case.
|
|
107
|
+
*/
|
|
108
|
+
rowUnitPx?: number;
|
|
90
109
|
};
|
|
91
110
|
|
|
92
111
|
// Data attribute used to remember an auto item's inline `grid-column` before the
|
|
@@ -95,6 +114,24 @@ export type MasonryOptions = {
|
|
|
95
114
|
// items pinned to their polyfill-assigned span.
|
|
96
115
|
const ORIGINAL_COL_ATTR = 'data-bds-masonry-original-column';
|
|
97
116
|
|
|
117
|
+
// Data attribute caching the last applied row-span. Used to short-circuit no-op
|
|
118
|
+
// re-runs: when every child's newly-measured span matches the cached value, the
|
|
119
|
+
// polyfill skips all writes. This prevents `grid-auto-flow: dense` from
|
|
120
|
+
// re-packing on spurious ResizeObserver fires (subpixel fluctuations, `<img>`
|
|
121
|
+
// decode in Firefox where offsetHeight briefly wobbles). Dense re-packing
|
|
122
|
+
// during fast scroll in Firefox is visible as images overlapping, because
|
|
123
|
+
// Firefox's compositor paints cached tiles from the pre-shuffle layout on top
|
|
124
|
+
// of the newly laid out items for a frame or two.
|
|
125
|
+
const LAST_SPAN_ATTR = 'data-bds-masonry-span';
|
|
126
|
+
|
|
127
|
+
// Container attribute caching the last applied row-unit (px). When the
|
|
128
|
+
// auto-tuner picks a different unit on a re-run (e.g. children added pushed
|
|
129
|
+
// the projected row count past the threshold), every per-child cached span
|
|
130
|
+
// becomes stale because they were computed against the old unit — we clear
|
|
131
|
+
// them so the next diff check forces a full rewrite. Cleared in
|
|
132
|
+
// clearMasonryLayout.
|
|
133
|
+
const UNIT_ATTR = 'data-bds-masonry-unit';
|
|
134
|
+
|
|
98
135
|
export function applyMasonryLayout(
|
|
99
136
|
container: HTMLElement,
|
|
100
137
|
rawChildren: HTMLElement[],
|
|
@@ -107,7 +144,6 @@ export function applyMasonryLayout(
|
|
|
107
144
|
const cs = window.getComputedStyle(container);
|
|
108
145
|
const targetGap = parseFloat(cs.columnGap) || 0;
|
|
109
146
|
|
|
110
|
-
container.style.gridAutoRows = `${ROW_UNIT_PX}px`;
|
|
111
147
|
container.style.rowGap = '0px';
|
|
112
148
|
container.style.gridAutoFlow = 'dense';
|
|
113
149
|
// `dense` + per-child `ResizeObserver` can fight the browser's scroll anchoring:
|
|
@@ -180,11 +216,66 @@ export function applyMasonryLayout(
|
|
|
180
216
|
// O(N) forced-reflow cost of a read-then-write loop.
|
|
181
217
|
const heights = children.map((child) => child.offsetHeight);
|
|
182
218
|
|
|
219
|
+
// Pick the row-track unit. Manual override wins; otherwise auto-tune.
|
|
220
|
+
//
|
|
221
|
+
// Auto-tune uses the projected implicit row count (assuming ideal `dense`
|
|
222
|
+
// packing across the current grid columns) and picks the smallest unit such
|
|
223
|
+
// that the projection stays under TARGET_MAX_IMPLICIT_ROWS. Galleries that
|
|
224
|
+
// don't risk hitting any browser cap stay at unit=1 and get pixel-perfect
|
|
225
|
+
// gaps; only galleries large enough to threaten the cap pay the rounding
|
|
226
|
+
// cost. The unit always grows monotonically with content, so once a gallery
|
|
227
|
+
// is above the threshold its layout is stable across runs.
|
|
228
|
+
const cols = countGridColumns(container) || 1;
|
|
229
|
+
const totalRowSum = heights.reduce(
|
|
230
|
+
(s, h) => s + Math.max(1, Math.ceil(h + targetGap)),
|
|
231
|
+
0,
|
|
232
|
+
);
|
|
233
|
+
const projectedRows = totalRowSum / cols;
|
|
234
|
+
const autoUnit = Math.max(
|
|
235
|
+
1,
|
|
236
|
+
Math.ceil(projectedRows / TARGET_MAX_IMPLICIT_ROWS),
|
|
237
|
+
);
|
|
238
|
+
const rowUnit =
|
|
239
|
+
options.rowUnitPx !== undefined && options.rowUnitPx > 0
|
|
240
|
+
? Math.floor(options.rowUnitPx)
|
|
241
|
+
: autoUnit;
|
|
242
|
+
|
|
243
|
+
// Apply the chosen unit. Setting `gridAutoRows` here (not before measurement)
|
|
244
|
+
// is safe because `align-self: start` makes offsetHeight return the content
|
|
245
|
+
// height regardless of the cell size — heights are independent of the unit.
|
|
246
|
+
container.style.gridAutoRows = `${rowUnit}px`;
|
|
247
|
+
|
|
248
|
+
// If the unit changed since the last run, every cached span is stale.
|
|
249
|
+
// Force a re-write by clearing the cache attributes before the diff check.
|
|
250
|
+
const prevUnit = container.getAttribute(UNIT_ATTR);
|
|
251
|
+
if (prevUnit !== String(rowUnit)) {
|
|
252
|
+
for (const child of children) child.removeAttribute(LAST_SPAN_ATTR);
|
|
253
|
+
container.setAttribute(UNIT_ATTR, String(rowUnit));
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Compute spans; skip the entire write pass if every span matches the
|
|
257
|
+
// previously-applied value. ResizeObserver fires freely in Firefox during
|
|
258
|
+
// image decode and scroll — without this guard, each fire triggers a dense
|
|
259
|
+
// re-pack that's visible as overlap on fast scroll (the compositor hasn't
|
|
260
|
+
// caught up with the newly shuffled positions).
|
|
261
|
+
const spans = heights.map((h) =>
|
|
262
|
+
Math.max(1, Math.ceil((h + targetGap) / rowUnit)),
|
|
263
|
+
);
|
|
264
|
+
let changed = false;
|
|
265
|
+
for (let i = 0; i < children.length; i++) {
|
|
266
|
+
const prev = children[i].getAttribute(LAST_SPAN_ATTR);
|
|
267
|
+
if (prev !== String(spans[i])) {
|
|
268
|
+
changed = true;
|
|
269
|
+
break;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
if (!changed) return;
|
|
273
|
+
|
|
183
274
|
// Pass 3 — batch all row-span writes. No reads in between, so the browser can
|
|
184
275
|
// coalesce style invalidation and do layout once for the whole batch.
|
|
185
276
|
for (let i = 0; i < children.length; i++) {
|
|
186
|
-
|
|
187
|
-
children[i].
|
|
277
|
+
children[i].style.gridRowEnd = `span ${spans[i]}`;
|
|
278
|
+
children[i].setAttribute(LAST_SPAN_ATTR, String(spans[i]));
|
|
188
279
|
}
|
|
189
280
|
}
|
|
190
281
|
|
|
@@ -223,6 +314,7 @@ export function clearMasonryLayout(container: HTMLElement, rawChildren: HTMLElem
|
|
|
223
314
|
container.style.gridAutoFlow = '';
|
|
224
315
|
container.style.rowGap = '';
|
|
225
316
|
container.style.overflowAnchor = '';
|
|
317
|
+
container.removeAttribute(UNIT_ATTR);
|
|
226
318
|
for (const child of rawChildren) {
|
|
227
319
|
if (child.nodeType !== 1) continue;
|
|
228
320
|
child.style.gridRow = '';
|
|
@@ -230,6 +322,7 @@ export function clearMasonryLayout(container: HTMLElement, rawChildren: HTMLElem
|
|
|
230
322
|
child.style.gridRowEnd = '';
|
|
231
323
|
child.style.alignSelf = '';
|
|
232
324
|
child.style.overflowAnchor = '';
|
|
325
|
+
child.removeAttribute(LAST_SPAN_ATTR);
|
|
233
326
|
// Restore any `gridColumn` value the polyfill overwrote. Items the
|
|
234
327
|
// polyfill never touched have no ORIGINAL_COL_ATTR and are left alone.
|
|
235
328
|
restoreOriginalColumn(child);
|
|
@@ -252,24 +345,66 @@ export function useMasonry(
|
|
|
252
345
|
enabled: boolean,
|
|
253
346
|
options: MasonryOptions = {},
|
|
254
347
|
): void {
|
|
255
|
-
const { preferredColumnSpan } = options;
|
|
348
|
+
const { preferredColumnSpan, rowUnitPx } = options;
|
|
256
349
|
useLayoutEffect(() => {
|
|
257
350
|
const container = ref.current;
|
|
258
351
|
if (!container || !enabled) return;
|
|
259
352
|
if (supportsNativeMasonry()) return;
|
|
260
353
|
|
|
261
354
|
let frame = 0;
|
|
262
|
-
|
|
263
|
-
|
|
355
|
+
let scrollTimer: ReturnType<typeof setTimeout> | null = null;
|
|
356
|
+
let scrolling = false;
|
|
357
|
+
let pending = false;
|
|
358
|
+
|
|
359
|
+
const apply = () => {
|
|
264
360
|
applyMasonryLayout(container, Array.from(container.children) as HTMLElement[], {
|
|
265
361
|
preferredColumnSpan,
|
|
362
|
+
rowUnitPx,
|
|
266
363
|
});
|
|
267
364
|
};
|
|
365
|
+
const runNow = () => {
|
|
366
|
+
cancelAnimationFrame(frame);
|
|
367
|
+
apply();
|
|
368
|
+
};
|
|
268
369
|
const schedule = () => {
|
|
269
370
|
cancelAnimationFrame(frame);
|
|
270
371
|
frame = requestAnimationFrame(runNow);
|
|
271
372
|
};
|
|
272
373
|
|
|
374
|
+
// RO-triggered re-runs (resizes of existing items — image decode, font swap) are
|
|
375
|
+
// the ones that cause the Firefox compositor race: `grid-auto-flow: dense` re-packs
|
|
376
|
+
// already-laid-out items, and cached tiles from the pre-shuffle layout paint over
|
|
377
|
+
// the new positions for a frame or two, looking like overlap. Deferring *those*
|
|
378
|
+
// writes until scroll stops hides the race. Initial mount and MutationObserver-
|
|
379
|
+
// triggered runs (new children added) must NOT defer — a skeleton mounting during
|
|
380
|
+
// active scroll would otherwise stay at `grid-auto-rows: 1px` height, invisibly
|
|
381
|
+
// collapsed, until scroll idles.
|
|
382
|
+
const onResize = () => {
|
|
383
|
+
if (scrolling) {
|
|
384
|
+
pending = true;
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
apply();
|
|
388
|
+
};
|
|
389
|
+
|
|
390
|
+
const SCROLL_IDLE_MS = 150;
|
|
391
|
+
const onScroll = () => {
|
|
392
|
+
scrolling = true;
|
|
393
|
+
if (scrollTimer) clearTimeout(scrollTimer);
|
|
394
|
+
scrollTimer = setTimeout(() => {
|
|
395
|
+
scrolling = false;
|
|
396
|
+
scrollTimer = null;
|
|
397
|
+
if (pending) {
|
|
398
|
+
pending = false;
|
|
399
|
+
apply();
|
|
400
|
+
}
|
|
401
|
+
}, SCROLL_IDLE_MS);
|
|
402
|
+
};
|
|
403
|
+
// `capture: true` catches scroll events from any nested scroll container, since
|
|
404
|
+
// scroll doesn't bubble. `passive: true` tells the browser we won't preventDefault —
|
|
405
|
+
// critical for not blocking scroll performance.
|
|
406
|
+
window.addEventListener('scroll', onScroll, { passive: true, capture: true });
|
|
407
|
+
|
|
273
408
|
schedule();
|
|
274
409
|
|
|
275
410
|
// ResizeObserver fires during the "update the rendering" step, AFTER layout and
|
|
@@ -278,9 +413,10 @@ export function useMasonry(
|
|
|
278
413
|
// defer to rAF instead, there's a visible frame where a grown item (e.g. a
|
|
279
414
|
// lazy-loaded image whose dimensions just resolved) overflows its stale cell
|
|
280
415
|
// and visually overlaps the next item in its column — easy to see on fast
|
|
281
|
-
// scroll through a long gallery.
|
|
416
|
+
// scroll through a long gallery. (onResize itself still defers writes when the
|
|
417
|
+
// user is actively scrolling — see its `scrolling` guard above.)
|
|
282
418
|
const ro =
|
|
283
|
-
typeof ResizeObserver !== 'undefined' ? new ResizeObserver(
|
|
419
|
+
typeof ResizeObserver !== 'undefined' ? new ResizeObserver(onResize) : null;
|
|
284
420
|
const observeDescendants = (child: Element) => {
|
|
285
421
|
if (!ro) return;
|
|
286
422
|
ro.observe(child);
|
|
@@ -315,9 +451,11 @@ export function useMasonry(
|
|
|
315
451
|
|
|
316
452
|
return () => {
|
|
317
453
|
cancelAnimationFrame(frame);
|
|
454
|
+
if (scrollTimer) clearTimeout(scrollTimer);
|
|
455
|
+
window.removeEventListener('scroll', onScroll, { capture: true } as EventListenerOptions);
|
|
318
456
|
ro?.disconnect();
|
|
319
457
|
mo?.disconnect();
|
|
320
458
|
clearMasonryLayout(container, Array.from(container.children) as HTMLElement[]);
|
|
321
459
|
};
|
|
322
|
-
}, [ref, enabled, preferredColumnSpan]);
|
|
460
|
+
}, [ref, enabled, preferredColumnSpan, rowUnitPx]);
|
|
323
461
|
}
|