@astryxdesign/core 0.7.2-canary.55d8dff → 0.7.2-canary.577a62e

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 (111) hide show
  1. package/README.md +4 -4
  2. package/dist/Avatar/Avatar.d.ts.map +1 -1
  3. package/dist/Avatar/Avatar.js +8 -1
  4. package/dist/AvatarGroup/AvatarGroupOverflow.d.ts.map +1 -1
  5. package/dist/AvatarGroup/AvatarGroupOverflow.js +4 -1
  6. package/dist/Chat/ChatLayoutScrollButton.d.ts.map +1 -1
  7. package/dist/Chat/ChatLayoutScrollButton.js +1 -0
  8. package/dist/CheckboxList/CheckboxList.d.ts +1 -1
  9. package/dist/CheckboxList/CheckboxList.d.ts.map +1 -1
  10. package/dist/CheckboxList/CheckboxList.js +6 -2
  11. package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
  12. package/dist/DropdownMenu/DropdownMenu.js +2 -103
  13. package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts +1 -1
  14. package/dist/DropdownMenu/DropdownMenuGroup.d.ts +6 -4
  15. package/dist/DropdownMenu/DropdownMenuGroup.d.ts.map +1 -1
  16. package/dist/DropdownMenu/DropdownMenuGroup.js +4 -3
  17. package/dist/DropdownMenu/DropdownMenuRadioItem.d.ts +1 -1
  18. package/dist/DropdownMenu/menuRegionStops.d.ts +38 -0
  19. package/dist/DropdownMenu/menuRegionStops.d.ts.map +1 -0
  20. package/dist/DropdownMenu/menuRegionStops.js +131 -0
  21. package/dist/FormLayout/FormLayout.d.ts +5 -4
  22. package/dist/FormLayout/FormLayout.d.ts.map +1 -1
  23. package/dist/FormLayout/FormLayout.js +1 -1
  24. package/dist/FormLayout/FormLayoutContext.d.ts +3 -3
  25. package/dist/FormLayout/FormLayoutContext.js +3 -3
  26. package/dist/Grid/Grid.d.ts +8 -7
  27. package/dist/Grid/Grid.d.ts.map +1 -1
  28. package/dist/Grid/Grid.js +5 -4
  29. package/dist/Grid/GridSpan.d.ts +1 -1
  30. package/dist/Markdown/parser.d.ts.map +1 -1
  31. package/dist/Markdown/parser.js +8 -2
  32. package/dist/Markdown/plugins/headingLinks.d.ts.map +1 -1
  33. package/dist/Markdown/plugins/headingLinks.js +9 -1
  34. package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
  35. package/dist/PowerSearch/PowerSearch.js +5 -0
  36. package/dist/PowerSearch/PowerSearchValueEditor.d.ts.map +1 -1
  37. package/dist/PowerSearch/PowerSearchValueEditor.js +45 -11
  38. package/dist/SideNav/SideNavItem.d.ts.map +1 -1
  39. package/dist/SideNav/SideNavItem.js +6 -2
  40. package/dist/Token/Token.js +1 -1
  41. package/dist/TopNav/TopNavMegaMenu.js +2 -0
  42. package/dist/TopNav/TopNavMenu.d.ts.map +1 -1
  43. package/dist/TopNav/TopNavMenu.js +2 -0
  44. package/dist/astryx.css +1 -1
  45. package/dist/reset.a11y.chromium.spec.js +50 -1
  46. package/docs.mjs +4 -4
  47. package/package.json +3 -3
  48. package/scripts/agent-doc-state.mjs +29 -7
  49. package/scripts/postinstall.mjs +43 -2
  50. package/src/Avatar/Avatar.tsx +11 -2
  51. package/src/AvatarGroup/AvatarGroupOverflow.test.tsx +24 -1
  52. package/src/AvatarGroup/AvatarGroupOverflow.tsx +2 -0
  53. package/src/Chat/ChatLayoutScrollButton.test.tsx +13 -0
  54. package/src/Chat/ChatLayoutScrollButton.tsx +4 -2
  55. package/src/Chat/__tests__/ChatLayoutScrollButton.a11y.chromium.spec.ts +64 -19
  56. package/src/CheckboxList/CheckboxList.fr10.test.tsx +29 -2
  57. package/src/CheckboxList/CheckboxList.tsx +4 -1
  58. package/src/ContextMenu/ContextMenu.test.tsx +12 -2
  59. package/src/DropdownMenu/DropdownMenu.test.tsx +3 -4
  60. package/src/DropdownMenu/DropdownMenu.tsx +7 -134
  61. package/src/DropdownMenu/DropdownMenuCheckboxItem.doc.mjs +1 -1
  62. package/src/DropdownMenu/DropdownMenuCheckboxItem.tsx +1 -1
  63. package/src/DropdownMenu/DropdownMenuGroup.doc.mjs +169 -10
  64. package/src/DropdownMenu/DropdownMenuGroup.tsx +6 -4
  65. package/src/DropdownMenu/DropdownMenuRadioItem.doc.mjs +1 -1
  66. package/src/DropdownMenu/DropdownMenuRadioItem.tsx +1 -1
  67. package/src/DropdownMenu/__tests__/DropdownMenuGroupAudit.a11y.chromium.spec.ts +334 -0
  68. package/src/DropdownMenu/menuRegionStops.ts +167 -0
  69. package/src/Field/FieldLabel.doc.mjs +133 -26
  70. package/src/Field/FieldLabel.spec.md +172 -0
  71. package/src/Field/__tests__/FieldLabelAudit.a11y.chromium.spec.ts +296 -0
  72. package/src/FormLayout/FormLayout.doc.mjs +6 -3
  73. package/src/FormLayout/FormLayout.spec.md +192 -0
  74. package/src/FormLayout/FormLayout.test.tsx +15 -0
  75. package/src/FormLayout/FormLayout.tsx +7 -6
  76. package/src/FormLayout/FormLayoutContext.ts +3 -3
  77. package/src/Grid/Grid.doc.mjs +7 -1
  78. package/src/Grid/Grid.spec.md +93 -113
  79. package/src/Grid/Grid.test.tsx +22 -0
  80. package/src/Grid/Grid.tsx +10 -9
  81. package/src/Grid/GridSpan.doc.mjs +3 -3
  82. package/src/Grid/GridSpan.tsx +1 -1
  83. package/src/Indicator/Indicator.doc.mjs +3 -3
  84. package/src/Markdown/parser.ts +8 -2
  85. package/src/Markdown/plugins/headingLinks.ts +9 -1
  86. package/src/PowerSearch/PowerSearch.test.tsx +62 -0
  87. package/src/PowerSearch/PowerSearch.tsx +9 -0
  88. package/src/PowerSearch/PowerSearchValueEditor.test.tsx +71 -0
  89. package/src/PowerSearch/PowerSearchValueEditor.tsx +45 -0
  90. package/src/Resizable/useResizable.doc.mjs +0 -1
  91. package/src/SideNav/SideNav.spec.md +227 -0
  92. package/src/SideNav/SideNav.test.tsx +41 -0
  93. package/src/SideNav/SideNavItem.tsx +5 -2
  94. package/src/Table/Table.spec.md +6 -1
  95. package/src/Table/plugins/activation/useTableActivation.spec.md +254 -0
  96. package/src/Token/Token.tsx +1 -1
  97. package/src/Token/__tests__/TokenRemoveTarget.a11y.chromium.spec.ts +86 -0
  98. package/src/Toolbar/Toolbar.test.tsx +0 -8
  99. package/src/Toolbar/__tests__/Toolbar.a11y.chromium.spec.ts +163 -0
  100. package/src/Toolbar/__tests__/Toolbar.a11y.renders.tsx +63 -0
  101. package/src/Toolbar/__tests__/Toolbar.a11y.states.ts +142 -0
  102. package/src/Toolbar/__tests__/Toolbar.a11y.test.tsx +126 -0
  103. package/src/TopNav/TopNavMegaMenu.test.tsx +25 -0
  104. package/src/TopNav/TopNavMegaMenu.tsx +2 -0
  105. package/src/TopNav/TopNavMenu.test.tsx +27 -1
  106. package/src/TopNav/TopNavMenu.tsx +2 -0
  107. package/src/postinstall.test.mjs +33 -0
  108. package/src/reset.a11y.chromium.spec.ts +52 -1
  109. package/src/reset.css +9 -0
  110. package/src/reset.test.ts +11 -0
  111. package/src/unicodePropertyEscapes.source-build.test.mjs +69 -0
@@ -4,7 +4,8 @@
4
4
  * @file reset.a11y.chromium.spec.ts
5
5
  * @input packages/core/src/reset.css, alone on a blank Chromium page
6
6
  * @output Browser evidence that the reset reaches ::before, ::after,
7
- * ::placeholder and the search field's cancel button
7
+ * ::placeholder, the search field's cancel button and the dropdown marker
8
+ * of a field with a list
8
9
  * @position reset.css integration proof. A pseudo-element inside :where() is
9
10
  * invalid, and the forgiving list drops it without an error, so only a
10
11
  * shipping engine shows which of the reset's rules still match.
@@ -13,6 +14,8 @@
13
14
  import * as fs from 'node:fs';
14
15
  import {fileURLToPath} from 'node:url';
15
16
  import {expect, test, type Page} from '@playwright/test';
17
+ // @ts-expect-error -- pngjs ships no declarations; runtime support is pinned.
18
+ import {PNG} from 'pngjs';
16
19
 
17
20
  const RESET_CSS = fs.readFileSync(
18
21
  fileURLToPath(new URL('./reset.css', import.meta.url)),
@@ -154,6 +157,54 @@ test('a search field has no cancel button to clear it', async ({page}) => {
154
157
  await expect(query).toHaveValue('query');
155
158
  });
156
159
 
160
+ test('the dropdown marker of a field with a list sits in its middle', async ({
161
+ page,
162
+ }) => {
163
+ // Chromium shows the marker while the pointer is over the field and draws
164
+ // it as a glyph set on the field's line-height, the 1.5 the reset inherits.
165
+ await page.evaluate(() => {
166
+ const field = document.createElement('input');
167
+ field.id = 'fruit';
168
+ field.setAttribute('list', 'fruits');
169
+ const options = document.createElement('datalist');
170
+ options.id = 'fruits';
171
+ options.append(new Option('Apple'));
172
+ document.body.append(field, options);
173
+ });
174
+ await page.addStyleTag({
175
+ content: `
176
+ #fruit {
177
+ display: block;
178
+ width: 240px;
179
+ padding: 7px 11px;
180
+ border: 1px solid;
181
+ line-height: 1.5;
182
+ }
183
+ `,
184
+ });
185
+ const field = page.locator('#fruit');
186
+ await field.hover();
187
+ const png = PNG.sync.read(await field.screenshot({caret: 'hide'}));
188
+ // The marker is the only ink in the field's last 32px, inside the border.
189
+ const inkRows: number[] = [];
190
+ for (let y = 2; y < png.height - 2; y++) {
191
+ for (let x = png.width - 32; x < png.width - 2; x++) {
192
+ const offset = (y * png.width + x) * 4;
193
+ const luminance =
194
+ (png.data[offset] + png.data[offset + 1] + png.data[offset + 2]) / 3;
195
+ if (luminance < 160) {
196
+ inkRows.push(y);
197
+ break;
198
+ }
199
+ }
200
+ }
201
+ expect(inkRows.length, 'the hovered field shows its marker').toBeGreaterThan(
202
+ 0,
203
+ );
204
+ const markerMiddle = (inkRows[0] + inkRows[inkRows.length - 1] + 1) / 2;
205
+ expect(Math.abs(markerMiddle - png.height / 2)).toBeLessThanOrEqual(1);
206
+ });
207
+
157
208
  test('a later layer and an unlayered rule still override the reset', async ({
158
209
  page,
159
210
  }) => {
package/src/reset.css CHANGED
@@ -265,6 +265,15 @@
265
265
  color: var(--color-text-secondary, light-dark(#4E606F, #AAAFB5));
266
266
  }
267
267
 
268
+ /*
269
+ * Chromium draws the dropdown marker of a field with a `list` as a glyph
270
+ * set on the line-height the field inherits, so with any line-height above
271
+ * 1 the marker sits below the middle of the field.
272
+ */
273
+ :where([list])::-webkit-calendar-picker-indicator {
274
+ line-height: 1;
275
+ }
276
+
268
277
  /*
269
278
  * Remove the inner padding and cancel buttons in search inputs
270
279
  * for Chrome and Safari.
package/src/reset.test.ts CHANGED
@@ -172,6 +172,17 @@ describe('reset.css pseudo-element rules', () => {
172
172
  ).toBe(true);
173
173
  });
174
174
 
175
+ it('sets the dropdown marker of a field with a list on a single line', () => {
176
+ const marker = ruleBlocks.filter(({selector}) =>
177
+ topLevelSelectors(selector).some(entry =>
178
+ entry.endsWith('::-webkit-calendar-picker-indicator'),
179
+ ),
180
+ );
181
+ expect(marker.map(({body}) => body).join('\n')).toMatch(
182
+ /line-height:\s*1;/,
183
+ );
184
+ });
185
+
175
186
  it('falls back to the --color-text-secondary default for placeholders', () => {
176
187
  // A page that loads only the reset never defines the token. The fallback
177
188
  // repeats the token's own default, so that page shows the placeholder an
@@ -0,0 +1,69 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file unicodePropertyEscapes.source-build.test.mjs
5
+ * @input Every Core source module a bundler compiles (tests and stories
6
+ * excluded)
7
+ * @output Fails when a module writes a Unicode property escape (`\p{…}` or
8
+ * `\P{…}`) as a regular expression literal
9
+ * @position Guard for source builds: for targets without property escapes, or
10
+ * with no browserslist, Babel rewrites such a literal, and the copy bundled
11
+ * with Next.js lacks the Unicode data to do it, so the build fails with
12
+ * "Unknown property". Modules build these expressions at runtime instead,
13
+ * as NumberInput/numberParser.ts does.
14
+ */
15
+
16
+ import fs from 'node:fs';
17
+ import path from 'node:path';
18
+ import {fileURLToPath} from 'node:url';
19
+ import {parseSync} from '@babel/core';
20
+ import {describe, expect, it} from 'vitest';
21
+
22
+ const SRC = path.dirname(fileURLToPath(import.meta.url));
23
+ const SKIPPED = /\.(test|stories)\.tsx?$|\.d\.ts$/;
24
+
25
+ function sourceFiles(dir) {
26
+ return fs.readdirSync(dir, {withFileTypes: true}).flatMap(entry => {
27
+ const file = path.join(dir, entry.name);
28
+ if (entry.isDirectory()) {
29
+ return entry.name === '__tests__' ? [] : sourceFiles(file);
30
+ }
31
+ return /\.tsx?$/.test(entry.name) && !SKIPPED.test(entry.name)
32
+ ? [file]
33
+ : [];
34
+ });
35
+ }
36
+
37
+ /** Every regular expression literal in a parsed tree. */
38
+ function regexLiterals(node, found = []) {
39
+ if (node == null || typeof node !== 'object') return found;
40
+ if (node.type === 'RegExpLiteral') found.push(node);
41
+ for (const [key, value] of Object.entries(node)) {
42
+ if (key !== 'loc' && key !== 'extra') regexLiterals(value, found);
43
+ }
44
+ return found;
45
+ }
46
+
47
+ describe('Core regular expressions in source builds', () => {
48
+ it('writes no Unicode property escape as a regex literal', () => {
49
+ const offenders = [];
50
+ for (const file of sourceFiles(SRC)) {
51
+ const source = fs.readFileSync(file, 'utf8');
52
+ if (!/\\[pP]\{/.test(source)) continue;
53
+ const ast = parseSync(source, {
54
+ filename: file,
55
+ babelrc: false,
56
+ configFile: false,
57
+ parserOpts: {plugins: ['typescript', 'jsx']},
58
+ });
59
+ for (const literal of regexLiterals(ast)) {
60
+ if (/\\[pP]\{/.test(literal.pattern)) {
61
+ offenders.push(
62
+ `${path.relative(SRC, file)}:${literal.loc.start.line} /${literal.pattern}/${literal.flags}`,
63
+ );
64
+ }
65
+ }
66
+ }
67
+ expect(offenders).toEqual([]);
68
+ });
69
+ });