@astryxdesign/core 0.1.4-canary.37c19d3 → 0.1.4-canary.39ffd21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Banner/Banner.d.ts.map +1 -1
- package/dist/Banner/Banner.js +1 -8
- package/dist/Calendar/Calendar.d.ts.map +1 -1
- package/dist/Calendar/Calendar.js +11 -31
- package/dist/Calendar/styles.d.ts +2 -2
- package/dist/Calendar/styles.d.ts.map +1 -1
- package/dist/Calendar/styles.js +2 -2
- package/dist/Carousel/Carousel.d.ts.map +1 -1
- package/dist/Carousel/Carousel.js +0 -1
- package/dist/Chat/ChatToolCalls.d.ts.map +1 -1
- package/dist/Chat/ChatToolCalls.js +1 -8
- package/dist/CheckboxInput/CheckboxInput.d.ts +1 -1
- package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
- package/dist/CheckboxInput/CheckboxInput.js +1 -3
- package/dist/ClickableCard/ClickableCard.d.ts.map +1 -1
- package/dist/ClickableCard/ClickableCard.js +3 -26
- package/dist/CodeBlock/CodeBlock.d.ts +1 -10
- package/dist/CodeBlock/CodeBlock.d.ts.map +1 -1
- package/dist/CodeBlock/CodeBlock.js +5 -23
- package/dist/Collapsible/Collapsible.d.ts +3 -9
- package/dist/Collapsible/Collapsible.d.ts.map +1 -1
- package/dist/Collapsible/Collapsible.js +13 -101
- package/dist/Collapsible/CollapsibleGroup.d.ts +7 -39
- package/dist/Collapsible/CollapsibleGroup.d.ts.map +1 -1
- package/dist/Collapsible/CollapsibleGroup.js +9 -70
- package/dist/Collapsible/CollapsibleGroupContext.d.ts +0 -31
- package/dist/Collapsible/CollapsibleGroupContext.d.ts.map +1 -1
- package/dist/Collapsible/CollapsibleGroupContext.js +3 -33
- package/dist/Collapsible/index.d.ts +0 -1
- package/dist/Collapsible/index.d.ts.map +1 -1
- package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
- package/dist/ContextMenu/ContextMenu.js +0 -2
- package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
- package/dist/DateTimeInput/DateTimeInput.js +0 -2
- package/dist/Grid/Grid.d.ts +3 -4
- package/dist/Grid/Grid.d.ts.map +1 -1
- package/dist/Grid/Grid.js +9 -26
- package/dist/Icon/Icon.d.ts.map +1 -1
- package/dist/Icon/Icon.js +1 -5
- package/dist/Lightbox/Lightbox.d.ts.map +1 -1
- package/dist/Lightbox/Lightbox.js +2 -26
- package/dist/NumberInput/NumberInput.d.ts.map +1 -1
- package/dist/NumberInput/NumberInput.js +16 -32
- package/dist/Pagination/Pagination.d.ts.map +1 -1
- package/dist/Pagination/Pagination.js +36 -87
- package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearch.js +21 -39
- package/dist/ProgressBar/ProgressBar.d.ts.map +1 -1
- package/dist/ProgressBar/ProgressBar.js +1 -2
- package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
- package/dist/Resizable/ResizeHandle.js +2 -10
- package/dist/SideNav/SideNav.d.ts.map +1 -1
- package/dist/SideNav/SideNav.js +2 -6
- package/dist/TabList/TabMenu.d.ts.map +1 -1
- package/dist/TabList/TabMenu.js +7 -34
- package/dist/Table/BaseTable.d.ts.map +1 -1
- package/dist/Table/BaseTable.js +2 -12
- package/dist/Table/plugins/columnResize/useTableColumnResize.d.ts.map +1 -1
- package/dist/Table/plugins/columnResize/useTableColumnResize.js +2 -5
- package/dist/Table/plugins/pagination/paginateData.d.ts.map +1 -1
- package/dist/Table/plugins/pagination/paginateData.js +1 -5
- package/dist/Table/plugins/selection/useTableSelectionState.d.ts.map +1 -1
- package/dist/Table/plugins/selection/useTableSelectionState.js +1 -11
- package/dist/Table/plugins/sortable/useTableSortableState.d.ts.map +1 -1
- package/dist/Table/plugins/sortable/useTableSortableState.js +4 -8
- package/dist/Thumbnail/Thumbnail.d.ts +2 -3
- package/dist/Thumbnail/Thumbnail.d.ts.map +1 -1
- package/dist/Thumbnail/Thumbnail.js +0 -1
- package/dist/TimeInput/TimeInput.d.ts.map +1 -1
- package/dist/TimeInput/TimeInput.js +1 -6
- package/dist/Toast/Toast.d.ts.map +1 -1
- package/dist/Toast/Toast.js +3 -11
- package/dist/Toast/ToastViewport.d.ts.map +1 -1
- package/dist/Toast/ToastViewport.js +0 -12
- package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
- package/dist/Typeahead/BaseTypeahead.js +2 -5
- package/dist/astryx.css +0 -7
- package/dist/astryx.umd.js +48 -48
- package/dist/astryx.umd.js.map +4 -4
- package/dist/hooks/focusableSelector.d.ts +1 -1
- package/dist/hooks/focusableSelector.d.ts.map +1 -1
- package/dist/hooks/focusableSelector.js +1 -1
- package/dist/hooks/useFocusTrap.d.ts +0 -3
- package/dist/hooks/useFocusTrap.d.ts.map +1 -1
- package/dist/hooks/useFocusTrap.js +1 -38
- package/dist/theme/defineTheme.d.ts +1 -1
- package/dist/theme/defineTheme.d.ts.map +1 -1
- package/dist/theme/expandColorScale.d.ts.map +1 -1
- package/dist/theme/expandColorScale.js +3 -10
- package/dist/theme/hct.d.ts +15 -0
- package/dist/theme/hct.d.ts.map +1 -1
- package/dist/theme/hct.js +9 -9
- package/dist/theme/tokens.d.ts +2 -10
- package/dist/theme/tokens.d.ts.map +1 -1
- package/dist/theme/tokens.js +4 -220
- package/dist/utils/index.d.ts +0 -2
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -2
- package/dist/utils/timeParser.d.ts.map +1 -1
- package/dist/utils/timeParser.js +5 -8
- package/package.json +1 -1
- package/src/Avatar/Avatar.doc.mjs +0 -11
- package/src/Banner/Banner.doc.mjs +0 -8
- package/src/Banner/Banner.test.tsx +0 -38
- package/src/Banner/Banner.tsx +1 -8
- package/src/Calendar/Calendar.doc.mjs +1 -1
- package/src/Calendar/Calendar.test.tsx +4 -112
- package/src/Calendar/Calendar.tsx +5 -26
- package/src/Calendar/styles.ts +2 -12
- package/src/Carousel/Carousel.doc.mjs +12 -12
- package/src/Carousel/Carousel.test.tsx +0 -13
- package/src/Carousel/Carousel.tsx +0 -1
- package/src/Chat/Chat.doc.mjs +1 -1
- package/src/Chat/ChatToolCalls.test.tsx +0 -55
- package/src/Chat/ChatToolCalls.tsx +12 -10
- package/src/CheckboxInput/CheckboxInput.doc.mjs +3 -3
- package/src/CheckboxInput/CheckboxInput.test.tsx +4 -71
- package/src/CheckboxInput/CheckboxInput.tsx +0 -2
- package/src/Citation/Citation.doc.mjs +0 -5
- package/src/ClickableCard/ClickableCard.tsx +2 -45
- package/src/CodeBlock/CodeBlock.doc.mjs +2 -7
- package/src/CodeBlock/CodeBlock.test.tsx +3 -87
- package/src/CodeBlock/CodeBlock.tsx +3 -43
- package/src/Collapsible/Collapsible.doc.mjs +4 -8
- package/src/Collapsible/Collapsible.tsx +10 -86
- package/src/Collapsible/CollapsibleGroup.doc.mjs +3 -27
- package/src/Collapsible/CollapsibleGroup.test.tsx +0 -288
- package/src/Collapsible/CollapsibleGroup.tsx +9 -107
- package/src/Collapsible/CollapsibleGroupContext.tsx +2 -42
- package/src/Collapsible/index.ts +0 -4
- package/src/CommandPalette/CommandPaletteEmpty.doc.mjs +0 -5
- package/src/CommandPalette/CommandPaletteGroup.doc.mjs +0 -10
- package/src/CommandPalette/CommandPaletteInput.doc.mjs +0 -6
- package/src/CommandPalette/CommandPaletteItem.doc.mjs +0 -6
- package/src/CommandPalette/CommandPaletteList.doc.mjs +0 -9
- package/src/ContextMenu/ContextMenu.doc.mjs +0 -15
- package/src/ContextMenu/ContextMenu.test.tsx +0 -15
- package/src/ContextMenu/ContextMenu.tsx +0 -2
- package/src/ContextMenu/ContextMenuItem.doc.mjs +0 -6
- package/src/DateTimeInput/DateTimeInput.test.tsx +0 -70
- package/src/DateTimeInput/DateTimeInput.tsx +0 -2
- package/src/Dialog/DialogHeader.doc.mjs +0 -45
- package/src/DropdownMenu/DropdownMenu.doc.mjs +1 -1
- package/src/Field/FieldLabel.test.tsx +2 -2
- package/src/FormLayout/__snapshots__/FormLayout.test.tsx.snap +3 -0
- package/src/Grid/Grid.test.tsx +18 -43
- package/src/Grid/Grid.tsx +9 -26
- package/src/Icon/Icon.test.tsx +29 -41
- package/src/Icon/Icon.tsx +2 -6
- package/src/Layout/LayoutContent.doc.mjs +0 -12
- package/src/Layout/LayoutPanel.doc.mjs +0 -40
- package/src/Lightbox/Lightbox.doc.mjs +0 -5
- package/src/Lightbox/Lightbox.test.tsx +2 -123
- package/src/Lightbox/Lightbox.tsx +1 -29
- package/src/NavMenu/NavMenu.doc.mjs +0 -15
- package/src/NumberInput/NumberInput.test.tsx +3 -48
- package/src/NumberInput/NumberInput.tsx +15 -32
- package/src/Overlay/Overlay.doc.mjs +0 -6
- package/src/Pagination/Pagination.test.tsx +0 -143
- package/src/Pagination/Pagination.tsx +23 -75
- package/src/Popover/Popover.test.tsx +0 -65
- package/src/PowerSearch/PowerSearch.doc.mjs +2 -2
- package/src/PowerSearch/PowerSearch.test.tsx +1 -109
- package/src/PowerSearch/PowerSearch.tsx +16 -37
- package/src/ProgressBar/ProgressBar.test.tsx +0 -10
- package/src/ProgressBar/ProgressBar.tsx +1 -2
- package/src/Resizable/ResizeHandle.tsx +2 -10
- package/src/SegmentedControl/SegmentedControl.doc.mjs +1 -1
- package/src/SelectableCard/SelectableCard.doc.mjs +1 -1
- package/src/SideNav/SideNav.doc.mjs +1 -7
- package/src/SideNav/SideNav.test.tsx +0 -20
- package/src/SideNav/SideNav.tsx +2 -6
- package/src/TabList/TabList.test.tsx +1 -185
- package/src/TabList/TabMenu.tsx +9 -45
- package/src/Table/BaseTable.tsx +1 -11
- package/src/Table/Table.test.tsx +0 -51
- package/src/Table/plugins/columnResize/useTableColumnResize.test.tsx +0 -13
- package/src/Table/plugins/columnResize/useTableColumnResize.tsx +1 -3
- package/src/Table/plugins/pagination/paginateData.ts +1 -5
- package/src/Table/plugins/pagination/useTablePagination.test.tsx +0 -13
- package/src/Table/plugins/selection/useTableSelectionState.test.tsx +0 -23
- package/src/Table/plugins/selection/useTableSelectionState.tsx +6 -11
- package/src/Table/plugins/sortable/useTableSortableState.test.tsx +1 -35
- package/src/Table/plugins/sortable/useTableSortableState.tsx +4 -8
- package/src/TextArea/TextArea.test.tsx +3 -3
- package/src/TextInput/TextInput.test.tsx +3 -3
- package/src/Thumbnail/Thumbnail.test.tsx +2 -36
- package/src/Thumbnail/Thumbnail.tsx +2 -4
- package/src/TimeInput/TimeInput.test.tsx +0 -37
- package/src/TimeInput/TimeInput.tsx +1 -11
- package/src/Timestamp/Timestamp.doc.mjs +1 -1
- package/src/Toast/Toast.tsx +3 -11
- package/src/Toast/ToastViewport.test.tsx +0 -64
- package/src/Toast/ToastViewport.tsx +0 -12
- package/src/ToggleButton/ToggleButton.doc.mjs +0 -1
- package/src/Tokenizer/Tokenizer.doc.mjs +2 -2
- package/src/TopNav/TopNav.doc.mjs +1 -1
- package/src/TreeList/TreeList.doc.mjs +2 -2
- package/src/Typeahead/BaseTypeahead.doc.mjs +0 -19
- package/src/Typeahead/BaseTypeahead.tsx +2 -5
- package/src/Typeahead/Typeahead.doc.mjs +1 -1
- package/src/Typeahead/Typeahead.test.tsx +1 -45
- package/src/hooks/focusableSelector.ts +1 -1
- package/src/hooks/useFocusTrap.doc.mjs +2 -4
- package/src/hooks/useFocusTrap.test.tsx +1 -149
- package/src/hooks/useFocusTrap.ts +1 -50
- package/src/theme/SyntaxTheme.doc.mjs +4 -4
- package/src/theme/defineTheme.ts +13 -4
- package/src/theme/derivedVarRegistry.test.ts +2 -2
- package/src/theme/expandColorScale.test.ts +0 -29
- package/src/theme/expandColorScale.ts +6 -11
- package/src/theme/hct.ts +17 -9
- package/src/theme/tokens.test.ts +9 -182
- package/src/theme/tokens.ts +4 -239
- package/src/theme/useTheme.test.tsx +0 -20
- package/src/utils/index.ts +0 -3
- package/src/utils/timeParser.test.ts +0 -13
- package/src/utils/timeParser.ts +5 -8
- package/dist/utils/color.d.ts +0 -42
- package/dist/utils/color.d.ts.map +0 -1
- package/dist/utils/color.js +0 -150
- package/src/Resizable/ResizeHandle.test.tsx +0 -180
- package/src/__tests__/TestIcon.tsx +0 -26
- package/src/docPropReferences.test.ts +0 -178
- package/src/utils/color.test.ts +0 -133
- package/src/utils/color.ts +0 -148
package/src/Table/BaseTable.tsx
CHANGED
|
@@ -284,12 +284,6 @@ function areRowPropsEqual<T extends Record<string, unknown>>(
|
|
|
284
284
|
const nextItem = nextProps.item;
|
|
285
285
|
const keys = Object.keys(nextItem);
|
|
286
286
|
|
|
287
|
-
// A key deleted from nextItem would never be visited by the loop below,
|
|
288
|
-
// and the row would keep rendering the removed field's stale value.
|
|
289
|
-
if (Object.keys(prevItem).length !== keys.length) {
|
|
290
|
-
return false;
|
|
291
|
-
}
|
|
292
|
-
|
|
293
287
|
for (const key of keys) {
|
|
294
288
|
if (prevItem[key] !== nextItem[key]) {
|
|
295
289
|
return false;
|
|
@@ -371,13 +365,9 @@ function BaseTableInner<T extends Record<string, unknown>>({
|
|
|
371
365
|
const headerCells = resolvedColumns.map((col, columnIndex) => {
|
|
372
366
|
const headerContent = col.header ?? col.key;
|
|
373
367
|
|
|
374
|
-
// Build initial htmlProps with column alignment if specified
|
|
375
|
-
// `scope: 'col'` is the default so every column header associates its
|
|
376
|
-
// data cells with the correct column for screen readers; a plugin's
|
|
377
|
-
// transformHeaderCell can override it via htmlProps.scope.
|
|
368
|
+
// Build initial htmlProps with column alignment if specified
|
|
378
369
|
const initialHeaderHtmlProps: Record<string, unknown> = {
|
|
379
370
|
'data-column-key': col.key,
|
|
380
|
-
scope: 'col',
|
|
381
371
|
};
|
|
382
372
|
if (col.align) {
|
|
383
373
|
initialHeaderHtmlProps.style = {textAlign: col.align};
|
package/src/Table/Table.test.tsx
CHANGED
|
@@ -229,34 +229,6 @@ describe('BaseTable', () => {
|
|
|
229
229
|
expect(headers[2]).toHaveTextContent('Email');
|
|
230
230
|
});
|
|
231
231
|
|
|
232
|
-
it('renders every column header with scope="col"', () => {
|
|
233
|
-
render(<BaseTable data={users} columns={columns} />);
|
|
234
|
-
const headers = screen.getAllByRole('columnheader');
|
|
235
|
-
expect(headers).toHaveLength(3);
|
|
236
|
-
for (const header of headers) {
|
|
237
|
-
expect(header).toHaveAttribute('scope', 'col');
|
|
238
|
-
}
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
it('lets a consumer override scope via header html props', () => {
|
|
242
|
-
// A `<th scope="row">` maps to the `rowheader` role, so query the DOM
|
|
243
|
-
// directly to assert the attribute regardless of the resolved ARIA role.
|
|
244
|
-
const plugin: TablePlugin<User> = {
|
|
245
|
-
transformHeaderCell: (props, column) =>
|
|
246
|
-
column.key === 'name'
|
|
247
|
-
? {...props, htmlProps: {...props.htmlProps, scope: 'row'}}
|
|
248
|
-
: props,
|
|
249
|
-
};
|
|
250
|
-
const {container} = render(
|
|
251
|
-
<BaseTable data={users} columns={columns} plugins={[plugin]} />,
|
|
252
|
-
);
|
|
253
|
-
const headerCells = container.querySelectorAll('thead th');
|
|
254
|
-
// columns fixture order: name, age, email — plugin overrides name → 'row'
|
|
255
|
-
expect(headerCells[0]).toHaveAttribute('scope', 'row');
|
|
256
|
-
expect(headerCells[1]).toHaveAttribute('scope', 'col');
|
|
257
|
-
expect(headerCells[2]).toHaveAttribute('scope', 'col');
|
|
258
|
-
});
|
|
259
|
-
|
|
260
232
|
it('renders data cells', () => {
|
|
261
233
|
render(<BaseTable data={users} columns={columns} />);
|
|
262
234
|
const cells = screen.getAllByRole('cell');
|
|
@@ -645,29 +617,6 @@ describe('BaseTable', () => {
|
|
|
645
617
|
// =============================================================================
|
|
646
618
|
|
|
647
619
|
describe('Table', () => {
|
|
648
|
-
it('clears a cell when the field is removed from the row object (#3595)', () => {
|
|
649
|
-
const cols: TableColumn<Record<string, unknown>>[] = [
|
|
650
|
-
{key: 'name', header: 'Name'},
|
|
651
|
-
{key: 'status', header: 'Status'},
|
|
652
|
-
];
|
|
653
|
-
const {rerender} = render(
|
|
654
|
-
<Table
|
|
655
|
-
data={[{id: '1', name: 'Alice', status: 'active'}]}
|
|
656
|
-
columns={cols}
|
|
657
|
-
idKey="id"
|
|
658
|
-
/>,
|
|
659
|
-
);
|
|
660
|
-
expect(screen.getByText('active')).toBeInTheDocument();
|
|
661
|
-
|
|
662
|
-
// Clearing a field by omitting it (optimistic update / server response)
|
|
663
|
-
// must re-render the row — the memo previously only compared the keys of
|
|
664
|
-
// the NEW item, so the deleted field's stale value kept rendering.
|
|
665
|
-
rerender(
|
|
666
|
-
<Table data={[{id: '1', name: 'Alice'}]} columns={cols} idKey="id" />,
|
|
667
|
-
);
|
|
668
|
-
expect(screen.queryByText('active')).not.toBeInTheDocument();
|
|
669
|
-
});
|
|
670
|
-
|
|
671
620
|
it('renders a table with correct structure', () => {
|
|
672
621
|
render(<Table data={users} columns={columns} />);
|
|
673
622
|
expect(screen.getByRole('table')).toBeInTheDocument();
|
|
@@ -551,19 +551,6 @@ describe('useTableColumnResize', () => {
|
|
|
551
551
|
expect(handle).toHaveAttribute('aria-valuenow', '200');
|
|
552
552
|
});
|
|
553
553
|
|
|
554
|
-
it('handle has a numeric aria-valuenow on initial render (before any width is measured)', () => {
|
|
555
|
-
// A focusable role="separator" requires aria-valuenow per the ARIA spec.
|
|
556
|
-
// Before the column width has been measured there is no override width, so
|
|
557
|
-
// the handle must fall back to a defined numeric value (the column
|
|
558
|
-
// minWidth) rather than omitting the attribute.
|
|
559
|
-
render(<ResizeTable minWidth={80} />);
|
|
560
|
-
const handle = getResizeHandles()[0];
|
|
561
|
-
const valueNow = handle.getAttribute('aria-valuenow');
|
|
562
|
-
expect(valueNow).not.toBeNull();
|
|
563
|
-
expect(Number.isNaN(Number(valueNow))).toBe(false);
|
|
564
|
-
expect(valueNow).toBe('80');
|
|
565
|
-
});
|
|
566
|
-
|
|
567
554
|
it('handle has aria-label with column header text', () => {
|
|
568
555
|
render(<ResizeTable />);
|
|
569
556
|
const handle = getResizeHandles()[0];
|
|
@@ -676,9 +676,7 @@ function ResizeHandle({
|
|
|
676
676
|
<div
|
|
677
677
|
role="separator"
|
|
678
678
|
aria-orientation="vertical"
|
|
679
|
-
|
|
680
|
-
// back to the column's minWidth before the width has been measured.
|
|
681
|
-
aria-valuenow={currentWidth ?? minWidth}
|
|
679
|
+
aria-valuenow={currentWidth ?? undefined}
|
|
682
680
|
aria-valuemin={minWidth}
|
|
683
681
|
aria-valuemax={maxWidth === Infinity ? undefined : maxWidth}
|
|
684
682
|
aria-label={ariaLabel}
|
|
@@ -38,10 +38,6 @@ export function paginateData<T>(
|
|
|
38
38
|
const size = Number.isFinite(pageSize)
|
|
39
39
|
? Math.max(1, Math.floor(pageSize))
|
|
40
40
|
: 10;
|
|
41
|
-
|
|
42
|
-
// count from the END of the data (page -1 returns the tail dressed up as a
|
|
43
|
-
// page), and a fractional page returns a slice straddling two pages.
|
|
44
|
-
const p = Number.isFinite(page) ? Math.max(1, Math.floor(page)) : 1;
|
|
45
|
-
const start = (p - 1) * size;
|
|
41
|
+
const start = (page - 1) * size;
|
|
46
42
|
return data.slice(start, start + size);
|
|
47
43
|
}
|
|
@@ -132,19 +132,6 @@ describe('paginateData', () => {
|
|
|
132
132
|
expect(paginateData([], 1, 10)).toEqual([]);
|
|
133
133
|
});
|
|
134
134
|
|
|
135
|
-
it('clamps invalid page numbers to the first page (#3593)', () => {
|
|
136
|
-
const data = generateItems(30);
|
|
137
|
-
// Negative pages previously fed a negative index to Array.slice, which
|
|
138
|
-
// counts from the END of the data — the tail dressed up as a page.
|
|
139
|
-
expect(paginateData(data, -1, 10)[0].id).toBe('1');
|
|
140
|
-
expect(paginateData(data, 0, 10)[0].id).toBe('1');
|
|
141
|
-
expect(paginateData(data, NaN, 10)[0].id).toBe('1');
|
|
142
|
-
// Fractional pages floor to the containing page instead of straddling two.
|
|
143
|
-
expect(paginateData(data, 1.5, 10).map(i => i.id)).toEqual(
|
|
144
|
-
paginateData(data, 1, 10).map(i => i.id),
|
|
145
|
-
);
|
|
146
|
-
});
|
|
147
|
-
|
|
148
135
|
it('returns empty array when page exceeds data', () => {
|
|
149
136
|
const data = generateItems(10);
|
|
150
137
|
expect(paginateData(data, 5, 10)).toEqual([]);
|
|
@@ -302,29 +302,6 @@ describe('useTableSelectionState with filtered data', () => {
|
|
|
302
302
|
expect(screen.getByTestId('selected-keys')).toHaveTextContent('1,3');
|
|
303
303
|
});
|
|
304
304
|
|
|
305
|
-
it('select-all is unchecked when the filter matches nothing (#3591)', async () => {
|
|
306
|
-
const user = userEvent.setup();
|
|
307
|
-
render(<FilteredSelectionTable />);
|
|
308
|
-
|
|
309
|
-
// Select Bob, then filter to an empty result set.
|
|
310
|
-
const checkboxes = screen.getAllByLabelText('Select row');
|
|
311
|
-
await user.click(checkboxes[1]);
|
|
312
|
-
expect(screen.getByTestId('selected-keys')).toHaveTextContent('2');
|
|
313
|
-
|
|
314
|
-
const input = screen.getByTestId('filter-input');
|
|
315
|
-
await user.clear(input);
|
|
316
|
-
await user.type(input, 'zzz');
|
|
317
|
-
|
|
318
|
-
// Zero visible rows must not read as "all selected" — previously the
|
|
319
|
-
// header checkbox rendered checked here and deselect-all was a no-op.
|
|
320
|
-
const selectAll = screen.getByLabelText('Select all rows');
|
|
321
|
-
expect(selectAll).not.toBeChecked();
|
|
322
|
-
|
|
323
|
-
// The hidden selection itself is preserved (frozen), as designed.
|
|
324
|
-
await user.clear(input);
|
|
325
|
-
expect(screen.getByTestId('selected-keys')).toHaveTextContent('2');
|
|
326
|
-
});
|
|
327
|
-
|
|
328
305
|
it('selections persist when filter changes', async () => {
|
|
329
306
|
const user = userEvent.setup();
|
|
330
307
|
render(<FilteredSelectionTable />);
|
|
@@ -125,17 +125,12 @@ export function useTableSelectionState<T extends Record<string, unknown>>(
|
|
|
125
125
|
[selectedKeys, selectableIDs],
|
|
126
126
|
);
|
|
127
127
|
|
|
128
|
-
const getAllSelected = useCallback(
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
if (selectableIDs.size === 0) {
|
|
135
|
-
return false;
|
|
136
|
-
}
|
|
137
|
-
return allSelectableIDs.size === selectedKeys.size;
|
|
138
|
-
}, [allSelectableIDs, selectableIDs, selectedKeys]);
|
|
128
|
+
const getAllSelected = useCallback(
|
|
129
|
+
() =>
|
|
130
|
+
allSelectableIDs.size === selectedKeys.size &&
|
|
131
|
+
allSelectableIDs.size !== 0,
|
|
132
|
+
[allSelectableIDs, selectedKeys],
|
|
133
|
+
);
|
|
139
134
|
|
|
140
135
|
// Use refs to keep onSelectAll stable
|
|
141
136
|
const frozenSelectedIDsRef = useRef(frozenSelectedIDs);
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import {describe, it, expect, vi} from 'vitest';
|
|
11
|
-
import {render,
|
|
11
|
+
import {render, screen, within} from '@testing-library/react';
|
|
12
12
|
import userEvent from '@testing-library/user-event';
|
|
13
13
|
import {useState} from 'react';
|
|
14
14
|
import {Table} from '../../Table';
|
|
@@ -421,40 +421,6 @@ describe('useTableSortableState', () => {
|
|
|
421
421
|
});
|
|
422
422
|
});
|
|
423
423
|
|
|
424
|
-
describe('NaN handling', () => {
|
|
425
|
-
it('keeps valid numbers sorted when a NaN cell is present', () => {
|
|
426
|
-
const data: Employee[] = [
|
|
427
|
-
{id: '1', name: 'A', age: 5, department: 'X', salary: 1},
|
|
428
|
-
{id: '2', name: 'B', age: NaN, department: 'X', salary: 1},
|
|
429
|
-
{id: '3', name: 'C', age: 1, department: 'X', salary: 1},
|
|
430
|
-
{id: '4', name: 'D', age: 3, department: 'X', salary: 1},
|
|
431
|
-
];
|
|
432
|
-
const {result} = renderHook(() =>
|
|
433
|
-
useTableSortableState({
|
|
434
|
-
data,
|
|
435
|
-
defaultSort: [{sortKey: 'age', direction: 'ascending'}],
|
|
436
|
-
}),
|
|
437
|
-
);
|
|
438
|
-
// NaN groups at the end like null; the valid numbers stay ordered.
|
|
439
|
-
expect(result.current.sortedData.map(e => e.age)).toEqual([1, 3, 5, NaN]);
|
|
440
|
-
});
|
|
441
|
-
|
|
442
|
-
it('sorts NaN to the start in descending order, like null', () => {
|
|
443
|
-
const data: Employee[] = [
|
|
444
|
-
{id: '1', name: 'A', age: 5, department: 'X', salary: 1},
|
|
445
|
-
{id: '2', name: 'B', age: NaN, department: 'X', salary: 1},
|
|
446
|
-
{id: '3', name: 'C', age: 1, department: 'X', salary: 1},
|
|
447
|
-
];
|
|
448
|
-
const {result} = renderHook(() =>
|
|
449
|
-
useTableSortableState({
|
|
450
|
-
data,
|
|
451
|
-
defaultSort: [{sortKey: 'age', direction: 'descending'}],
|
|
452
|
-
}),
|
|
453
|
-
);
|
|
454
|
-
expect(result.current.sortedData.map(e => e.age)).toEqual([NaN, 5, 1]);
|
|
455
|
-
});
|
|
456
|
-
});
|
|
457
|
-
|
|
458
424
|
describe('null/undefined handling', () => {
|
|
459
425
|
it('sorts null values to the end', () => {
|
|
460
426
|
const data: Employee[] = [
|
|
@@ -168,18 +168,14 @@ function defaultCompare<T extends Record<string, unknown>>(
|
|
|
168
168
|
const aVal = a[sortKey];
|
|
169
169
|
const bVal = b[sortKey];
|
|
170
170
|
|
|
171
|
-
// null/undefined
|
|
172
|
-
|
|
173
|
-
// the comparator inconsistent and corrupts the order of the other rows.
|
|
174
|
-
const aMissing = aVal == null || (typeof aVal === 'number' && Number.isNaN(aVal));
|
|
175
|
-
const bMissing = bVal == null || (typeof bVal === 'number' && Number.isNaN(bVal));
|
|
176
|
-
if (aMissing && bMissing) {
|
|
171
|
+
// null/undefined sort to the end
|
|
172
|
+
if (aVal == null && bVal == null) {
|
|
177
173
|
return 0;
|
|
178
174
|
}
|
|
179
|
-
if (
|
|
175
|
+
if (aVal == null) {
|
|
180
176
|
return 1;
|
|
181
177
|
}
|
|
182
|
-
if (
|
|
178
|
+
if (bVal == null) {
|
|
183
179
|
return -1;
|
|
184
180
|
}
|
|
185
181
|
|
|
@@ -13,7 +13,7 @@ import {useState} from 'react';
|
|
|
13
13
|
import {describe, it, expect, vi, beforeEach} from 'vitest';
|
|
14
14
|
import {render, screen, fireEvent, waitFor} from '@testing-library/react';
|
|
15
15
|
import userEvent from '@testing-library/user-event';
|
|
16
|
-
import {
|
|
16
|
+
import {MagnifyingGlassIcon} from '@heroicons/react/24/outline';
|
|
17
17
|
import {TextArea} from './TextArea';
|
|
18
18
|
|
|
19
19
|
// Mock showPopover/hidePopover since jsdom does not implement them. Used by the
|
|
@@ -193,7 +193,7 @@ describe('TextArea', () => {
|
|
|
193
193
|
label="Description"
|
|
194
194
|
value=""
|
|
195
195
|
onChange={() => {}}
|
|
196
|
-
startIcon={
|
|
196
|
+
startIcon={MagnifyingGlassIcon}
|
|
197
197
|
/>,
|
|
198
198
|
);
|
|
199
199
|
expect(screen.getByRole('textbox')).toBeInTheDocument();
|
|
@@ -577,7 +577,7 @@ describe('TextArea', () => {
|
|
|
577
577
|
label="Notes"
|
|
578
578
|
value=""
|
|
579
579
|
onChange={() => {}}
|
|
580
|
-
startIcon={<
|
|
580
|
+
startIcon={<MagnifyingGlassIcon />}
|
|
581
581
|
/>,
|
|
582
582
|
);
|
|
583
583
|
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
import {describe, it, expect, vi, beforeEach} from 'vitest';
|
|
13
13
|
import {render, screen, fireEvent, waitFor} from '@testing-library/react';
|
|
14
14
|
import userEvent from '@testing-library/user-event';
|
|
15
|
-
import {
|
|
15
|
+
import {MagnifyingGlassIcon} from '@heroicons/react/24/outline';
|
|
16
16
|
import {TextInput} from './TextInput';
|
|
17
17
|
|
|
18
18
|
// Mock showPopover/hidePopover since jsdom does not implement them. Used by the
|
|
@@ -163,7 +163,7 @@ describe('TextInput', () => {
|
|
|
163
163
|
label="Search"
|
|
164
164
|
value=""
|
|
165
165
|
onChange={() => {}}
|
|
166
|
-
startIcon={
|
|
166
|
+
startIcon={MagnifyingGlassIcon}
|
|
167
167
|
/>,
|
|
168
168
|
);
|
|
169
169
|
expect(screen.getByRole('textbox')).toBeInTheDocument();
|
|
@@ -482,7 +482,7 @@ describe('TextInput', () => {
|
|
|
482
482
|
label="Search"
|
|
483
483
|
value=""
|
|
484
484
|
onChange={() => {}}
|
|
485
|
-
startIcon={<
|
|
485
|
+
startIcon={<MagnifyingGlassIcon />}
|
|
486
486
|
/>,
|
|
487
487
|
);
|
|
488
488
|
|
|
@@ -37,45 +37,11 @@ describe('Thumbnail', () => {
|
|
|
37
37
|
expect(screen.getByRole('status')).toBeInTheDocument();
|
|
38
38
|
});
|
|
39
39
|
|
|
40
|
-
it('
|
|
40
|
+
it('uses label as accessible name on root', () => {
|
|
41
41
|
render(<Thumbnail label="photo.png" data-testid="thumb" />);
|
|
42
|
-
|
|
43
|
-
// not by a bare aria-label on a generic div (aria-prohibited-attr).
|
|
44
|
-
const group = screen.getByRole('group', {name: 'photo.png'});
|
|
45
|
-
expect(group).toBe(screen.getByTestId('thumb'));
|
|
42
|
+
expect(screen.getByTestId('thumb')).toHaveAttribute('aria-label', 'photo.png');
|
|
46
43
|
});
|
|
47
44
|
|
|
48
|
-
it('does not put aria-label on a generic (roleless) element', () => {
|
|
49
|
-
render(<Thumbnail label="photo.png" data-testid="thumb" />);
|
|
50
|
-
const thumb = screen.getByTestId('thumb');
|
|
51
|
-
// The labeled element must declare a role so the name is legitimate.
|
|
52
|
-
expect(thumb).toHaveAttribute('role', 'group');
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it('keeps interactive children accessible while exposing the group name', () => {
|
|
56
|
-
const onRemove = vi.fn();
|
|
57
|
-
render(
|
|
58
|
-
<Thumbnail
|
|
59
|
-
src="/img.jpg"
|
|
60
|
-
alt="Clickable"
|
|
61
|
-
label="file.png"
|
|
62
|
-
onClick={vi.fn()}
|
|
63
|
-
onRemove={onRemove}
|
|
64
|
-
/>,
|
|
65
|
-
);
|
|
66
|
-
// A group role (unlike img) must not hide descendant controls.
|
|
67
|
-
expect(
|
|
68
|
-
screen.getByRole('group', {name: 'file.png — Clickable'}),
|
|
69
|
-
).toBeInTheDocument();
|
|
70
|
-
expect(
|
|
71
|
-
screen.getByRole('button', {name: 'Open file.png — Clickable'}),
|
|
72
|
-
).toBeInTheDocument();
|
|
73
|
-
expect(
|
|
74
|
-
screen.getByRole('button', {name: 'Remove file.png — Clickable'}),
|
|
75
|
-
).toBeInTheDocument();
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
|
|
79
45
|
it('label is shown via tooltip, not as inline text', () => {
|
|
80
46
|
render(<Thumbnail label="photo.png" data-testid="thumb" />);
|
|
81
47
|
// Label should exist in DOM (tooltip) but not as a direct child text node
|
|
@@ -58,9 +58,8 @@ export interface ThumbnailProps extends BaseProps<HTMLDivElement> {
|
|
|
58
58
|
alt?: string;
|
|
59
59
|
/**
|
|
60
60
|
* Accessible label for the thumbnail (e.g. file name).
|
|
61
|
-
* Not rendered visually — shown in a tooltip on hover
|
|
62
|
-
* accessible name
|
|
63
|
-
* name for the remove button.
|
|
61
|
+
* Not rendered visually — shown in a tooltip on hover and used
|
|
62
|
+
* as accessible name for the remove button.
|
|
64
63
|
*/
|
|
65
64
|
label?: string;
|
|
66
65
|
/**
|
|
@@ -297,7 +296,6 @@ export function Thumbnail({
|
|
|
297
296
|
<div
|
|
298
297
|
ref={ref}
|
|
299
298
|
data-testid={testId}
|
|
300
|
-
role="group"
|
|
301
299
|
aria-label={accessibleName}
|
|
302
300
|
{...mergeProps(
|
|
303
301
|
themeProps('thumbnail'),
|
|
@@ -160,43 +160,6 @@ describe('TimeInput', () => {
|
|
|
160
160
|
expect(onChange).not.toHaveBeenCalled();
|
|
161
161
|
});
|
|
162
162
|
|
|
163
|
-
it('sets aria-invalid="true" when typed input is out of range', () => {
|
|
164
|
-
render(<TimeInput label="Time" onChange={() => {}} />);
|
|
165
|
-
|
|
166
|
-
const input = screen.getByRole('textbox');
|
|
167
|
-
fireEvent.change(input, {target: {value: '25:99'}});
|
|
168
|
-
|
|
169
|
-
expect(input).toHaveAttribute('aria-invalid', 'true');
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
it('does not set aria-invalid when typed input is a valid time', () => {
|
|
173
|
-
render(<TimeInput label="Time" onChange={() => {}} />);
|
|
174
|
-
|
|
175
|
-
const input = screen.getByRole('textbox');
|
|
176
|
-
fireEvent.change(input, {target: {value: '3:45 pm'}});
|
|
177
|
-
|
|
178
|
-
expect(input).not.toHaveAttribute('aria-invalid');
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
it('announces an alert message when typed input is invalid', () => {
|
|
182
|
-
render(<TimeInput label="Time" onChange={() => {}} />);
|
|
183
|
-
|
|
184
|
-
const input = screen.getByRole('textbox');
|
|
185
|
-
fireEvent.change(input, {target: {value: '25:99'}});
|
|
186
|
-
|
|
187
|
-
expect(screen.getByRole('alert')).toHaveTextContent('Invalid time');
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
it('does not announce an alert message when input is valid', () => {
|
|
191
|
-
render(<TimeInput label="Time" onChange={() => {}} />);
|
|
192
|
-
|
|
193
|
-
const input = screen.getByRole('textbox');
|
|
194
|
-
fireEvent.change(input, {target: {value: '3:45 pm'}});
|
|
195
|
-
|
|
196
|
-
expect(screen.getByRole('alert')).toHaveTextContent('');
|
|
197
|
-
expect(screen.queryByText('Invalid time')).not.toBeInTheDocument();
|
|
198
|
-
});
|
|
199
|
-
|
|
200
163
|
it('calls onChange on blur when input is valid', async () => {
|
|
201
164
|
const user = userEvent.setup();
|
|
202
165
|
const onChange = vi.fn();
|
|
@@ -635,9 +635,7 @@ export function TimeInput({
|
|
|
635
635
|
data-autofocus={hasAutoFocus || undefined}
|
|
636
636
|
aria-describedby={ariaDescribedBy}
|
|
637
637
|
aria-required={isRequired === true ? 'true' : undefined}
|
|
638
|
-
aria-invalid={
|
|
639
|
-
status?.type === 'error' || !isInputValid ? 'true' : undefined
|
|
640
|
-
}
|
|
638
|
+
aria-invalid={status?.type === 'error' ? 'true' : undefined}
|
|
641
639
|
aria-busy={isBusy || undefined}
|
|
642
640
|
aria-labelledby={ariaLabelledBy}
|
|
643
641
|
{...stylex.props(
|
|
@@ -646,14 +644,6 @@ export function TimeInput({
|
|
|
646
644
|
!isInputValid && styles.inputInvalid,
|
|
647
645
|
)}
|
|
648
646
|
/>
|
|
649
|
-
{/*
|
|
650
|
-
Live region announcing invalid typed input to assistive technology.
|
|
651
|
-
The value silently reverts on blur, so without this a screen-reader
|
|
652
|
-
user would get no feedback that their entry was rejected (WCAG 3.3.1).
|
|
653
|
-
*/}
|
|
654
|
-
<VisuallyHidden as="div" role="alert" aria-live="assertive">
|
|
655
|
-
{!isInputValid ? 'Invalid time' : ''}
|
|
656
|
-
</VisuallyHidden>
|
|
657
647
|
{isBusy && <Spinner size="sm" />}
|
|
658
648
|
{hasClear && value && !isDisabled && (
|
|
659
649
|
<button
|
package/src/Toast/Toast.tsx
CHANGED
|
@@ -121,14 +121,8 @@ export function Toast({
|
|
|
121
121
|
// Will be initialized by startTimer when actually used
|
|
122
122
|
const startTimeRef = useRef<number | null>(null);
|
|
123
123
|
|
|
124
|
-
// Read onDismiss through a ref: the viewport re-creates it on every render
|
|
125
|
-
// (another toast arriving/exiting), and a startTimer that depends on it
|
|
126
|
-
// would restart — and un-pause — this toast's timer on unrelated renders.
|
|
127
|
-
const onDismissRef = useRef(onDismiss);
|
|
128
|
-
onDismissRef.current = onDismiss;
|
|
129
|
-
|
|
130
124
|
const startTimer = useCallback(() => {
|
|
131
|
-
if (!isAutoHide
|
|
125
|
+
if (!isAutoHide) {
|
|
132
126
|
return;
|
|
133
127
|
}
|
|
134
128
|
if (timerRef.current) {
|
|
@@ -136,9 +130,9 @@ export function Toast({
|
|
|
136
130
|
}
|
|
137
131
|
startTimeRef.current = Date.now();
|
|
138
132
|
timerRef.current = setTimeout(() => {
|
|
139
|
-
|
|
133
|
+
onDismiss('auto');
|
|
140
134
|
}, remainingRef.current);
|
|
141
|
-
}, [isAutoHide]);
|
|
135
|
+
}, [isAutoHide, onDismiss]);
|
|
142
136
|
|
|
143
137
|
const pauseTimer = useCallback(() => {
|
|
144
138
|
if (!isAutoHide || isPausedRef.current) {
|
|
@@ -171,8 +165,6 @@ export function Toast({
|
|
|
171
165
|
clearTimeout(timerRef.current);
|
|
172
166
|
}
|
|
173
167
|
};
|
|
174
|
-
// startTimer's identity is stable per isAutoHide, so this runs on mount
|
|
175
|
-
// and on a genuine duration change — not on unrelated viewport renders.
|
|
176
168
|
}, [autoHideDuration, startTimer]);
|
|
177
169
|
|
|
178
170
|
// Pause the auto-hide timer while the window is not focused, so a toast
|
|
@@ -212,67 +212,3 @@ describe('ToastViewport region ARIA', () => {
|
|
|
212
212
|
expect(region).not.toHaveAttribute('aria-modal');
|
|
213
213
|
});
|
|
214
214
|
});
|
|
215
|
-
|
|
216
|
-
describe('toast timer lifecycle (#3589)', () => {
|
|
217
|
-
it('fires onHide exactly once when dismissed twice during the exit window', () => {
|
|
218
|
-
const onHide = vi.fn();
|
|
219
|
-
renderViewport(
|
|
220
|
-
<ShowToastButton options={{body: 'Once', onHide}} triggerLabel="Show" />,
|
|
221
|
-
);
|
|
222
|
-
act(() => {
|
|
223
|
-
fireEvent.click(screen.getByText('Show'));
|
|
224
|
-
});
|
|
225
|
-
const dismiss = screen.getByRole('button', {name: 'Dismiss notification'});
|
|
226
|
-
act(() => {
|
|
227
|
-
fireEvent.click(dismiss);
|
|
228
|
-
});
|
|
229
|
-
// The toast stays mounted during its exit transition; a second click
|
|
230
|
-
// lands on the same still-mounted button.
|
|
231
|
-
act(() => {
|
|
232
|
-
fireEvent.click(dismiss);
|
|
233
|
-
});
|
|
234
|
-
expect(onHide).toHaveBeenCalledTimes(1);
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
it('keeps a window-blur pause alive when another toast arrives', () => {
|
|
238
|
-
vi.useFakeTimers();
|
|
239
|
-
try {
|
|
240
|
-
const onHide = vi.fn();
|
|
241
|
-
renderViewport(
|
|
242
|
-
<>
|
|
243
|
-
<ShowToastButton
|
|
244
|
-
options={{body: 'Paused', autoHideDuration: 3000, onHide}}
|
|
245
|
-
triggerLabel="Show A"
|
|
246
|
-
/>
|
|
247
|
-
<ShowToastButton options={INFO_B} triggerLabel="Show B" />
|
|
248
|
-
</>,
|
|
249
|
-
);
|
|
250
|
-
act(() => {
|
|
251
|
-
fireEvent.click(screen.getByText('Show A'));
|
|
252
|
-
});
|
|
253
|
-
act(() => {
|
|
254
|
-
fireEvent.blur(window);
|
|
255
|
-
});
|
|
256
|
-
// A second toast arriving re-renders the viewport; the paused timer
|
|
257
|
-
// must not silently restart.
|
|
258
|
-
act(() => {
|
|
259
|
-
fireEvent.click(screen.getByText('Show B'));
|
|
260
|
-
});
|
|
261
|
-
act(() => {
|
|
262
|
-
vi.advanceTimersByTime(60_000);
|
|
263
|
-
});
|
|
264
|
-
expect(onHide).not.toHaveBeenCalled();
|
|
265
|
-
// Focus returns: the remaining time resumes and completes normally.
|
|
266
|
-
act(() => {
|
|
267
|
-
fireEvent.focus(window);
|
|
268
|
-
});
|
|
269
|
-
act(() => {
|
|
270
|
-
vi.advanceTimersByTime(60_000);
|
|
271
|
-
});
|
|
272
|
-
expect(onHide).toHaveBeenCalledTimes(1);
|
|
273
|
-
expect(onHide).toHaveBeenCalledWith('auto');
|
|
274
|
-
} finally {
|
|
275
|
-
vi.useRealTimers();
|
|
276
|
-
}
|
|
277
|
-
});
|
|
278
|
-
});
|
|
@@ -114,9 +114,6 @@ export function ToastViewport({
|
|
|
114
114
|
|
|
115
115
|
// Show the popover on mount so it enters the top layer.
|
|
116
116
|
const viewportRef = useRef<HTMLDivElement>(null);
|
|
117
|
-
// Toast ids whose exit has begun — guards onHide from double-firing (see
|
|
118
|
-
// removeToast). Mirrors exitingIds state, readable synchronously.
|
|
119
|
-
const exitingIdsRef = useRef<Set<string>>(new Set());
|
|
120
117
|
// The element that was focused before the user jumped into the viewport
|
|
121
118
|
// (via F6). Used to restore focus once all toasts are dismissed so focus
|
|
122
119
|
// never falls to <body>.
|
|
@@ -174,14 +171,6 @@ export function ToastViewport({
|
|
|
174
171
|
}, []);
|
|
175
172
|
|
|
176
173
|
const removeToast = useCallback((id: string, reason: ToastDismissReason) => {
|
|
177
|
-
// An exiting toast stays in toastsRef until its exit transition ends, so
|
|
178
|
-
// a second dismissal inside that window (double-click, auto-timer plus
|
|
179
|
-
// manual dismiss()) would re-fire onHide. Track exiting ids in a ref —
|
|
180
|
-
// the exitingIds state dedupe below runs too late to protect onHide.
|
|
181
|
-
if (exitingIdsRef.current.has(id)) {
|
|
182
|
-
return;
|
|
183
|
-
}
|
|
184
|
-
exitingIdsRef.current.add(id);
|
|
185
174
|
const entry = toastsRef.current.find(t => t.id === id);
|
|
186
175
|
if (entry) {
|
|
187
176
|
entry.options.onHide?.(reason);
|
|
@@ -221,7 +210,6 @@ export function ToastViewport({
|
|
|
221
210
|
}, []);
|
|
222
211
|
|
|
223
212
|
const handleExited = useCallback((id: string) => {
|
|
224
|
-
exitingIdsRef.current.delete(id);
|
|
225
213
|
setExitingIds(prev => {
|
|
226
214
|
if (!prev.has(id)) {
|
|
227
215
|
return prev;
|
|
@@ -17,7 +17,6 @@ export const docs = {
|
|
|
17
17
|
theming: {
|
|
18
18
|
targets: [
|
|
19
19
|
{className: 'astryx-toggle-button-group'},
|
|
20
|
-
{className: 'astryx-toggle-button', states: ['isPressed']},
|
|
21
20
|
],
|
|
22
21
|
},
|
|
23
22
|
description: 'A button that toggles between pressed and unpressed states. Thin wrapper over Button with controlled toggle pattern, icon swap, and font weight emphasis.',
|
|
@@ -189,7 +189,7 @@ export const docs = {
|
|
|
189
189
|
],
|
|
190
190
|
theming: {
|
|
191
191
|
targets: [
|
|
192
|
-
{className: 'astryx-tokenizer', visualProps: ['size'
|
|
192
|
+
{className: 'astryx-tokenizer', visualProps: ['size']},
|
|
193
193
|
],
|
|
194
194
|
},
|
|
195
195
|
usage: {
|
|
@@ -390,7 +390,7 @@ export const docsZh = {
|
|
|
390
390
|
],
|
|
391
391
|
theming: {
|
|
392
392
|
targets: [
|
|
393
|
-
{className: 'astryx-tokenizer', visualProps: ['size'
|
|
393
|
+
{className: 'astryx-tokenizer', visualProps: ['size']},
|
|
394
394
|
],
|
|
395
395
|
},
|
|
396
396
|
usage: {
|