@astryxdesign/core 0.7.2-canary.caec7ca → 0.7.2-canary.cbb7fa3

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 (125) hide show
  1. package/dist/AspectRatio/AspectRatio.d.ts +6 -8
  2. package/dist/AspectRatio/AspectRatio.d.ts.map +1 -1
  3. package/dist/AspectRatio/AspectRatio.js +44 -3
  4. package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
  5. package/dist/Breadcrumbs/BreadcrumbItem.js +5 -2
  6. package/dist/CheckboxInput/CheckboxInput.d.ts +1 -1
  7. package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
  8. package/dist/CheckboxInput/CheckboxInput.js +3 -1
  9. package/dist/CommandPalette/CommandPaletteInput.d.ts +1 -1
  10. package/dist/CommandPalette/CommandPaletteInput.d.ts.map +1 -1
  11. package/dist/CommandPalette/CommandPaletteInput.js +37 -5
  12. package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
  13. package/dist/ContextMenu/ContextMenu.js +5 -2
  14. package/dist/Dialog/Dialog.d.ts.map +1 -1
  15. package/dist/Dialog/Dialog.js +20 -8
  16. package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
  17. package/dist/DropdownMenu/DropdownMenu.js +46 -3
  18. package/dist/DropdownMenu/DropdownMenuContext.d.ts +17 -0
  19. package/dist/DropdownMenu/DropdownMenuContext.d.ts.map +1 -1
  20. package/dist/DropdownMenu/DropdownMenuContext.js +21 -0
  21. package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts.map +1 -1
  22. package/dist/DropdownMenu/DropdownMenuSubMenu.js +15 -4
  23. package/dist/EmptyState/EmptyState.d.ts.map +1 -1
  24. package/dist/EmptyState/EmptyState.js +4 -10
  25. package/dist/Field/Field.d.ts +1 -1
  26. package/dist/Field/Field.d.ts.map +1 -1
  27. package/dist/Field/Field.js +8 -4
  28. package/dist/Grid/GridSpan.d.ts.map +1 -1
  29. package/dist/Grid/GridSpan.js +1 -0
  30. package/dist/List/List.d.ts +46 -19
  31. package/dist/List/List.d.ts.map +1 -1
  32. package/dist/List/List.js +28 -10
  33. package/dist/List/ListContext.d.ts +8 -3
  34. package/dist/List/ListContext.d.ts.map +1 -1
  35. package/dist/List/ListContext.js +7 -2
  36. package/dist/List/ListItem.d.ts +4 -3
  37. package/dist/List/ListItem.d.ts.map +1 -1
  38. package/dist/List/ListItem.js +19 -8
  39. package/dist/List/index.d.ts +1 -1
  40. package/dist/List/index.d.ts.map +1 -1
  41. package/dist/Markdown/Markdown.d.ts.map +1 -1
  42. package/dist/Markdown/Markdown.js +8 -1
  43. package/dist/MetadataList/MetadataList.d.ts.map +1 -1
  44. package/dist/MetadataList/MetadataList.js +5 -1
  45. package/dist/MetadataList/MetadataListContext.d.ts +1 -1
  46. package/dist/MetadataList/MetadataListContext.d.ts.map +1 -1
  47. package/dist/RadioList/RadioList.d.ts +1 -1
  48. package/dist/RadioList/RadioList.d.ts.map +1 -1
  49. package/dist/RadioList/RadioList.js +6 -2
  50. package/dist/ToggleButton/ToggleButton.d.ts +3 -2
  51. package/dist/ToggleButton/ToggleButton.d.ts.map +1 -1
  52. package/dist/ToggleButton/ToggleButton.js +12 -1
  53. package/dist/TreeList/TreeList.d.ts.map +1 -1
  54. package/dist/TreeList/TreeList.js +1 -0
  55. package/dist/TreeList/TreeListBranches.d.ts.map +1 -1
  56. package/dist/TreeList/TreeListBranches.js +2 -2
  57. package/dist/TreeList/TreeListItem.d.ts.map +1 -1
  58. package/dist/TreeList/TreeListItem.js +57 -9
  59. package/dist/astryx.css +16 -3
  60. package/dist/hooks/useMenuHover.d.ts.map +1 -1
  61. package/dist/hooks/useMenuHover.js +18 -13
  62. package/dist/utils/plainDate.d.ts.map +1 -1
  63. package/dist/utils/plainDate.js +15 -9
  64. package/package.json +3 -3
  65. package/src/AspectRatio/AspectRatio.test.tsx +44 -43
  66. package/src/AspectRatio/AspectRatio.tsx +38 -18
  67. package/src/AspectRatio/__tests__/AspectRatioFitLayers.a11y.browser.spec.ts +157 -0
  68. package/src/Avatar/Avatar.doc.mjs +5 -3
  69. package/src/AvatarGroup/AvatarGroup.doc.mjs +3 -2
  70. package/src/Breadcrumbs/BreadcrumbItem.tsx +4 -1
  71. package/src/Chat/__tests__/ChatMessageMetadata.a11y.chromium.spec.ts +3 -1
  72. package/src/CheckboxInput/CheckboxInput.test.tsx +34 -0
  73. package/src/CheckboxInput/CheckboxInput.tsx +3 -1
  74. package/src/CommandPalette/CommandPaletteInput.test.tsx +53 -4
  75. package/src/CommandPalette/CommandPaletteInput.tsx +33 -3
  76. package/src/CommandPalette/__tests__/CommandPaletteInputAudit.a11y.chromium.spec.ts +335 -0
  77. package/src/ContextMenu/ContextMenu.tsx +4 -1
  78. package/src/Dialog/Dialog.test.tsx +46 -0
  79. package/src/Dialog/Dialog.tsx +20 -7
  80. package/src/Dialog/__tests__/Dialog.a11y.known-failures.ts +0 -14
  81. package/src/DropdownMenu/DropdownMenu.tsx +45 -2
  82. package/src/DropdownMenu/DropdownMenuContext.tsx +24 -0
  83. package/src/DropdownMenu/DropdownMenuSubMenu.test.tsx +185 -0
  84. package/src/DropdownMenu/DropdownMenuSubMenu.tsx +17 -3
  85. package/src/EmptyState/EmptyState.doc.mjs +3 -2
  86. package/src/EmptyState/EmptyState.spec.md +189 -0
  87. package/src/EmptyState/EmptyState.test.tsx +74 -3
  88. package/src/EmptyState/EmptyState.tsx +4 -8
  89. package/src/Field/Field.doc.mjs +14 -8
  90. package/src/Field/Field.spec.md +217 -0
  91. package/src/Field/Field.test.tsx +60 -2
  92. package/src/Field/Field.tsx +17 -8
  93. package/src/Grid/GridSpan.tsx +3 -0
  94. package/src/Item/Item.doc.mjs +1 -1
  95. package/src/Item/Item.tsx +1 -1
  96. package/src/List/List.doc.mjs +14 -1
  97. package/src/List/List.spec.md +129 -77
  98. package/src/List/List.test.tsx +144 -94
  99. package/src/List/List.tsx +74 -18
  100. package/src/List/ListContext.tsx +9 -3
  101. package/src/List/ListItem.tsx +46 -26
  102. package/src/List/index.ts +1 -1
  103. package/src/Markdown/Markdown.tsx +14 -1
  104. package/src/MetadataList/MetadataList.doc.mjs +2 -2
  105. package/src/MetadataList/MetadataList.test.tsx +38 -0
  106. package/src/MetadataList/MetadataList.tsx +8 -1
  107. package/src/MetadataList/MetadataListContext.tsx +1 -1
  108. package/src/RadioList/RadioList.fr10.test.tsx +45 -2
  109. package/src/RadioList/RadioList.tsx +4 -1
  110. package/src/Slider/Slider.spec.md +39 -18
  111. package/src/TabList/TabList.spec.md +57 -44
  112. package/src/TimeInput/TimeInput.fr10.test.tsx +15 -0
  113. package/src/ToggleButton/ToggleButton.test.tsx +95 -0
  114. package/src/ToggleButton/ToggleButton.tsx +12 -1
  115. package/src/TreeList/TreeList.doc.mjs +1 -0
  116. package/src/TreeList/TreeList.test.tsx +327 -6
  117. package/src/TreeList/TreeList.tsx +5 -1
  118. package/src/TreeList/TreeListBranches.tsx +6 -0
  119. package/src/TreeList/TreeListItem.tsx +88 -13
  120. package/src/docPropLiterals.test.ts +36 -10
  121. package/src/hooks/useMenuHover.ts +18 -13
  122. package/src/reset.css +0 -29
  123. package/src/theme/derivedVarRegistry.test.ts +7 -0
  124. package/src/utils/plainDate.test.ts +37 -1
  125. package/src/utils/plainDate.ts +15 -5
@@ -3,7 +3,8 @@
3
3
  /* eslint-disable @typescript-eslint/no-require-imports */
4
4
  /**
5
5
  * @file Guards doc prop types against opaque named unions (#1645).
6
- * @input Every {Name}.doc.mjs plus the TypeScript sources they describe.
6
+ * @input Every {Name}.doc.mjs plus the TypeScript sources they describe,
7
+ * including same-file private aliases used by exported literal unions.
7
8
  * @output Fails when a documented prop type hides its legal literal values.
8
9
  * @position Repo-wide doc-shape guard, sibling of docPropReferences.test.ts.
9
10
  *
@@ -188,18 +189,34 @@ for (const file of findFiles(SRC_DIR, ['.ts', '.tsx'])) {
188
189
  // markers the comment-stripped `src` has already removed.
189
190
  const rawTypeRhs = new Map<string, string>();
190
191
  for (const m of raw.matchAll(
191
- /export type ([A-Z][A-Za-z0-9]*)(?:<[^>]*>)?\s*=\s*([^;]+);/g,
192
+ /\btype ([A-Z][A-Za-z0-9]*)(?:<[^>]*>)?\s*=\s*([^;]+);/g,
192
193
  )) {
193
194
  rawTypeRhs.set(m[1], m[2]);
194
195
  }
195
- for (const m of src.matchAll(
196
- /export type ([A-Z][A-Za-z0-9]*)(?:<[^>]*>)?\s*=\s*([^;]+);/g,
197
- )) {
198
- const [, name, rhsRaw] = m;
199
- const rhs = rhsRaw.replace(/\s+/g, ' ').trim();
200
- if (LITERAL_UNION.test(rhs)) {
196
+ const declarations = [
197
+ ...src.matchAll(
198
+ /\b(export )?type ([A-Z][A-Za-z0-9]*)(?:<[^>]*>)?\s*=\s*([^;]+);/g,
199
+ ),
200
+ ];
201
+ // Resolve same-file literal aliases before registering exported types.
202
+ // Repeat until no new literal unions are found.
203
+ const fileLiterals = new Map<string, string>();
204
+ let previousSize: number;
205
+ do {
206
+ previousSize = fileLiterals.size;
207
+ for (const [, , name, rhsRaw] of declarations) {
208
+ const rhs = rhsRaw
209
+ .replace(/\s+/g, ' ')
210
+ .trim()
211
+ .split('|')
212
+ .map(part => fileLiterals.get(part.trim()) ?? part.trim())
213
+ .join(' | ');
214
+
215
+ if (!LITERAL_UNION.test(rhs)) {
216
+ continue;
217
+ }
201
218
  const deprecated = deprecatedLiteralMembers(rawTypeRhs.get(name) ?? '');
202
- literalUnions.set(
219
+ fileLiterals.set(
203
220
  name,
204
221
  rhs
205
222
  .replace(/^\|\s*/, '')
@@ -209,8 +226,17 @@ for (const file of findFiles(SRC_DIR, ['.ts', '.tsx'])) {
209
226
  .filter(member => !deprecated.has(member))
210
227
  .join(' | '),
211
228
  );
229
+ }
230
+ } while (fileLiterals.size !== previousSize);
231
+ for (const [, exported, name, rhsRaw] of declarations) {
232
+ if (!exported) {
233
+ continue;
234
+ }
235
+ const literals = fileLiterals.get(name);
236
+ if (literals !== undefined) {
237
+ literalUnions.set(name, literals);
212
238
  } else {
213
- namedBodies.set(name, rhs);
239
+ namedBodies.set(name, rhsRaw.replace(/\s+/g, ' ').trim());
214
240
  }
215
241
  }
216
242
  for (const m of src.matchAll(
@@ -230,19 +230,6 @@ export function useMenuHover<T extends HTMLElement = HTMLElement>(
230
230
  const hoverOpenedAtRef = useRef(0);
231
231
  const prevIsOpenRef = useRef(isOpen);
232
232
 
233
- // Catches every close, whatever caused it. A layout effect, not render: it
234
- // must land before the browser hit-tests the vanished panel and fires the
235
- // mouseenter REOPEN_SUPPRESS_MS exists to swallow.
236
- useIsomorphicLayoutEffect(() => {
237
- const wasOpen = prevIsOpenRef.current;
238
- prevIsOpenRef.current = isOpen;
239
- if (wasOpen && !isOpen) {
240
- hoverModeRef.current = false;
241
- hoverOpenedAtRef.current = 0;
242
- closedAtRef.current = Date.now();
243
- }
244
- }, [isOpen]);
245
-
246
233
  const clearTimeouts = useCallback(() => {
247
234
  if (showTimerRef.current) {
248
235
  clearTimeout(showTimerRef.current);
@@ -256,6 +243,24 @@ export function useMenuHover<T extends HTMLElement = HTMLElement>(
256
243
  safeTriangleCleanupRef.current = null;
257
244
  }, []);
258
245
 
246
+ // Catches every close, whatever caused it. A layout effect, not render: it
247
+ // must land before the browser hit-tests the vanished panel and fires the
248
+ // mouseenter REOPEN_SUPPRESS_MS exists to swallow.
249
+ useIsomorphicLayoutEffect(() => {
250
+ const wasOpen = prevIsOpenRef.current;
251
+ prevIsOpenRef.current = isOpen;
252
+ if (wasOpen && !isOpen) {
253
+ // A hover-open scheduled before the close must not fire afterwards and
254
+ // reopen what just closed from elsewhere — e.g. the root menu's light
255
+ // dismiss closing a submenu flyout mid hover-intent (#6893). A fresh
256
+ // mouseenter still opens; only the stale timer is cancelled.
257
+ clearTimeouts();
258
+ hoverModeRef.current = false;
259
+ hoverOpenedAtRef.current = 0;
260
+ closedAtRef.current = Date.now();
261
+ }
262
+ }, [isOpen, clearTimeouts]);
263
+
259
264
  // useListFocus needs an onEscape that is defined in terms of its own listRef;
260
265
  // the indirection avoids the use-before-declare.
261
266
  const escapeHandlerRef = useRef<() => void>(() => {});
package/src/reset.css CHANGED
@@ -203,35 +203,6 @@
203
203
  height: auto;
204
204
  }
205
205
 
206
- /* ===========================================================================
207
- AspectRatio Fit Baseline
208
- =========================================================================== */
209
-
210
- /*
211
- * Child sizing for AspectRatio's `fit` prop — the component marks the
212
- * child's direct parent with `data-astryx-aspect-ratio-override` (carrying
213
- * the fit value), and these direct-child rules size the child, so
214
- * consumers don't hand-write width/height/object-fit on every media child.
215
- * The marker sits on the parent itself, so the rules don't depend on
216
- * AspectRatio's internal element structure or its theming surface. As with
217
- * the [data-astryx-media] baseline below, the rules are zero-specificity
218
- * and any styling the child sets itself still wins. `fit="center"` needs
219
- * no rules here — the component centers the child from its own wrapper.
220
- */
221
- :where([data-astryx-aspect-ratio-override="cover"], [data-astryx-aspect-ratio-override="contain"])
222
- > :where(*) {
223
- width: 100%;
224
- height: 100%;
225
- }
226
-
227
- :where([data-astryx-aspect-ratio-override="cover"]) > :where(img, video) {
228
- object-fit: cover;
229
- }
230
-
231
- :where([data-astryx-aspect-ratio-override="contain"]) > :where(img, video) {
232
- object-fit: contain;
233
- }
234
-
235
206
  /* ===========================================================================
236
207
  Forms
237
208
  =========================================================================== */
@@ -261,6 +261,9 @@ const CROSS_COMPONENT_VARS: Record<string, string[]> = {
261
261
  // The destructive item variant recolors the Item it renders; Item owns,
262
262
  // documents and reads both slots.
263
263
  DropdownMenu: ['--_item-label-color', '--_item-description-color'],
264
+ // List's edge="flush" zeroes the inline inset of the Item each ListItem
265
+ // renders; Item owns, documents and reads it.
266
+ List: ['--_item-inset-inline'],
264
267
  // A layer content root stops an outer Stepper's connector gap; Stepper owns,
265
268
  // documents and reads it.
266
269
  Layer: ['--step-connector-gap'],
@@ -316,6 +319,10 @@ const VARS_WITHOUT_DERIVED_MAPPING = new Set([
316
319
  '--tree-list-indent',
317
320
  '--_tree-indent',
318
321
  '--tree-list-row-gap',
322
+ // Published by each treeitem while it has keyboard focus and read as its
323
+ // row's z-index, so the focused row paints above the guide lines. Focus
324
+ // state, not a value a theme author sets.
325
+ '--_tree-focus-z',
319
326
  // Clipped on the ONE side each on-track connector segment faces the
320
327
  // indicator from — trailing above the node, leading below it, mirrored
321
328
  // again per axis. No standard property maps onto this purpose-based seam:
@@ -1,6 +1,6 @@
1
1
  // Copyright (c) Meta Platforms, Inc. and affiliates.
2
2
 
3
- import {describe, it, expect} from 'vitest';
3
+ import {describe, it, expect, afterEach} from 'vitest';
4
4
  import {
5
5
  type PlainDate,
6
6
  plainDateCreate,
@@ -711,6 +711,42 @@ describe('plainDateGetWeekNumber', () => {
711
711
  expect(plainDateGetWeekNumber({year: 2026, month: 1, day: 4})).toBe(1);
712
712
  });
713
713
 
714
+ // Regression: the week number must not depend on the host timezone's DST
715
+ // offset. Zones whose DST ends after New Year (southern hemisphere) used to
716
+ // add a phantom hour to the Thursday-vs-Jan-1 day count, and in years whose
717
+ // Jan 1 is a Friday that hour rounded a whole week up.
718
+ describe('DST independence', () => {
719
+ const originalTZ = process.env.TZ;
720
+
721
+ afterEach(() => {
722
+ if (originalTZ === undefined) {
723
+ delete process.env.TZ;
724
+ } else {
725
+ process.env.TZ = originalTZ;
726
+ }
727
+ });
728
+
729
+ it.each(['Australia/Sydney', 'Pacific/Auckland', 'America/Santiago'])(
730
+ 'does not round week 14 up to 15 in %s (DST ends after New Year)',
731
+ zone => {
732
+ process.env.TZ = zone;
733
+ // 2021-01-01 is a Friday, so every ISO Thursday falls on a whole
734
+ // multiple of 7 days into the year. 2021-04-05..11 is ISO week 14.
735
+ expect(plainDateGetWeekNumber({year: 2021, month: 4, day: 5})).toBe(14);
736
+ expect(plainDateGetWeekNumber({year: 2021, month: 4, day: 8})).toBe(14);
737
+ expect(plainDateGetWeekNumber({year: 2021, month: 4, day: 11})).toBe(
738
+ 14,
739
+ );
740
+ },
741
+ );
742
+
743
+ it('keeps late-December weeks correct in Australia/Sydney', () => {
744
+ process.env.TZ = 'Australia/Sydney';
745
+ // 2027-12-30 (Thursday) is ISO week 52, not 53.
746
+ expect(plainDateGetWeekNumber({year: 2027, month: 12, day: 30})).toBe(52);
747
+ });
748
+ });
749
+
714
750
  it('keeps years 1-99 instead of mapping them to 1900-1999', () => {
715
751
  // 0004-01-01 is a Thursday (ISO week 1); 1904-01-01 was a Friday.
716
752
  expect(plainDateGetWeekNumber({year: 4, month: 1, day: 1})).toBe(1);
@@ -282,11 +282,21 @@ export function plainDateSetEndOfWeekExclusive(
282
282
  }
283
283
 
284
284
  export function plainDateGetWeekNumber(pd: PlainDate): number {
285
- const d = plainDateToDate(pd);
286
- const dayNum = d.getDay() || 7;
287
- d.setDate(d.getDate() + 4 - dayNum);
288
- const yearStart = plainDateToDate({year: d.getFullYear(), month: 1, day: 1});
289
- return Math.ceil(((d.getTime() - yearStart.getTime()) / 86400000 + 1) / 7);
285
+ // ISO week number: ceil(day-of-year of the week's Thursday / 7). Computed
286
+ // entirely on UTC anchors — the same DST immunity plainDateDiffDays relies
287
+ // on — because mixing local-time instants leaks the host zone's DST offset
288
+ // into the day count. When Jan 1 is a Friday (so every Thursday's day-of-year
289
+ // is an exact multiple of 7), a one-hour offset from a zone whose DST ends
290
+ // after New Year rounds a whole week up: Sydney, Auckland and Santiago users
291
+ // saw week 15 for the week that is ISO week 14. utcTime keeps years 1-99
292
+ // as given, like the other Calendar date helpers.
293
+ const msPerDay = 24 * 60 * 60 * 1000;
294
+ const utcMidnight = utcTime(pd.year, pd.month - 1, pd.day);
295
+ const dayNum = new Date(utcMidnight).getUTCDay() || 7;
296
+ const thursday = utcMidnight + (4 - dayNum) * msPerDay;
297
+ const yearStart = utcTime(new Date(thursday).getUTCFullYear(), 0, 1);
298
+ const dayOfYear = (thursday - yearStart) / msPerDay + 1;
299
+ return Math.ceil(dayOfYear / 7);
290
300
  }
291
301
 
292
302
  // e.g. "Wednesday, May 21, 2026" (locale-dependent)