@boostdev/design-system-components 1.1.0 → 1.1.1

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 (72) hide show
  1. package/AGENTS.md +46 -5
  2. package/dist/client.cjs +206 -113
  3. package/dist/client.css +664 -624
  4. package/dist/client.d.cts +9 -3
  5. package/dist/client.d.ts +9 -3
  6. package/dist/client.js +241 -149
  7. package/dist/index.cjs +206 -113
  8. package/dist/index.css +664 -624
  9. package/dist/index.d.cts +9 -3
  10. package/dist/index.d.ts +9 -3
  11. package/dist/index.js +241 -149
  12. package/dist/web-components.d.ts +1 -0
  13. package/dist/web-components.js +10 -0
  14. package/package.json +1 -1
  15. package/src/components/interaction/Button/Button.module.css +1 -1
  16. package/src/components/interaction/Button/Button.native.mdx +72 -0
  17. package/src/components/interaction/Command/Command.module.css +7 -7
  18. package/src/components/interaction/Command/Command.spec.tsx +37 -0
  19. package/src/components/interaction/Command/Command.tsx +37 -2
  20. package/src/components/interaction/Dialog/Dialog.module.css +10 -13
  21. package/src/components/interaction/Dialog/Dialog.spec.tsx +53 -1
  22. package/src/components/interaction/Dialog/Dialog.stories.tsx +17 -0
  23. package/src/components/interaction/Dialog/Dialog.tsx +50 -16
  24. package/src/components/interaction/Drawer/Drawer.module.css +11 -11
  25. package/src/components/interaction/Drawer/Drawer.spec.tsx +48 -1
  26. package/src/components/interaction/Drawer/Drawer.tsx +38 -4
  27. package/src/components/interaction/DropdownMenu/DropdownMenu.module.css +6 -6
  28. package/src/components/interaction/Popover/Popover.module.css +74 -15
  29. package/src/components/interaction/Popover/Popover.spec.tsx +94 -59
  30. package/src/components/interaction/Popover/Popover.tsx +44 -60
  31. package/src/components/interaction/Toast/Toast.module.css +7 -7
  32. package/src/components/interaction/form/Checkbox/Checkbox.module.css +6 -6
  33. package/src/components/interaction/form/Checkbox/Checkbox.native.mdx +74 -0
  34. package/src/components/interaction/form/Combobox/Combobox.module.css +7 -8
  35. package/src/components/interaction/form/FileInput/FileInput.module.css +4 -4
  36. package/src/components/interaction/form/NumberInput/NumberInput.module.css +1 -1
  37. package/src/components/interaction/form/Radio/Radio.module.css +6 -6
  38. package/src/components/interaction/form/SegmentedControl/SegmentedControl.module.css +5 -6
  39. package/src/components/interaction/form/Select/Select.module.css +3 -3
  40. package/src/components/interaction/form/Slider/Slider.module.css +6 -6
  41. package/src/components/interaction/form/Switch/Switch.module.css +8 -8
  42. package/src/components/interaction/form/Textarea/Textarea.module.css +1 -1
  43. package/src/components/layout/IconWrapper/IconWrapper.module.css +2 -2
  44. package/src/components/ui/Accordion/Accordion.module.css +5 -5
  45. package/src/components/ui/Alert/Alert.native.mdx +62 -0
  46. package/src/components/ui/Avatar/Avatar.native.mdx +50 -0
  47. package/src/components/ui/Badge/Badge.native.mdx +54 -0
  48. package/src/components/ui/Calendar/Calendar.module.css +7 -7
  49. package/src/components/ui/Carousel/Carousel.module.css +5 -5
  50. package/src/components/ui/Collapsible/Collapsible.module.css +4 -4
  51. package/src/components/ui/Link/Link.module.css +2 -2
  52. package/src/components/ui/Loading/Loading.module.css +7 -7
  53. package/src/components/ui/NotificationBanner/NotificationBanner.module.css +3 -3
  54. package/src/components/ui/Pagination/Pagination.module.css +6 -6
  55. package/src/components/ui/Progress/Progress.module.css +5 -5
  56. package/src/components/ui/Separator/Separator.module.css +3 -3
  57. package/src/components/ui/Skeleton/Skeleton.module.css +1 -1
  58. package/src/components/ui/Table/Table.module.css +9 -9
  59. package/src/components/ui/Tabs/Tabs.module.css +3 -3
  60. package/src/components/ui/Tooltip/Tooltip.module.css +8 -8
  61. package/src/components/ui/Typography/Typography.native.mdx +56 -0
  62. package/src/polyfill-invoker-commands.ts +68 -0
  63. package/src/react-augment.d.ts +19 -0
  64. package/src/stories/ReactNative.mdx +121 -0
  65. package/src/web-components/interaction/BdsButton.mdx +106 -0
  66. package/src/web-components/interaction/BdsPopover.mdx +120 -0
  67. package/src/web-components/interaction/BdsToastProvider.mdx +94 -0
  68. package/src/web-components/interaction/bds-popover.spec.ts +49 -1
  69. package/src/web-components/interaction/bds-popover.ts +12 -0
  70. package/src/web-components/test/helpers.ts +8 -0
  71. package/src/web-components/ui/BdsAlert.mdx +90 -0
  72. package/src/web-components/ui/BdsBadge.mdx +74 -0
@@ -0,0 +1,50 @@
1
+ import { Meta, Canvas, ArgTypes } from '@storybook/blocks';
2
+ import * as Stories from './Avatar.native.stories';
3
+
4
+ <Meta of={Stories} />
5
+
6
+ # Avatar (React Native)
7
+
8
+ React Native avatar component displaying user initials on a coloured background. Derives initials from the `name` prop (first letter of each word, max 2).
9
+
10
+ > **Status: alpha** — API may change before stable release.
11
+
12
+ ## Usage
13
+
14
+ ```tsx
15
+ import { Avatar } from '@boostdev/components';
16
+
17
+ <Avatar name="John Doe" size="medium" />
18
+ ```
19
+
20
+ ## Examples
21
+
22
+ ### All sizes
23
+ <Canvas of={Stories.AllSizes} />
24
+
25
+ ### With initials
26
+ <Canvas of={Stories.WithInitials} />
27
+
28
+ ### Small
29
+ <Canvas of={Stories.Small} />
30
+
31
+ ### Large
32
+ <Canvas of={Stories.Large} />
33
+
34
+ ### Single name
35
+ <Canvas of={Stories.SingleName} />
36
+
37
+ ## Props
38
+
39
+ <ArgTypes of={Stories} />
40
+
41
+ ## Props reference
42
+
43
+ | Prop | Type | Default | Description |
44
+ |------|------|---------|-------------|
45
+ | `name` | string | — | Full name — initials are derived from this |
46
+ | `size` | `"small" \| "medium" \| "large"` | `"medium"` | Circle diameter |
47
+
48
+ ## Accessibility
49
+
50
+ - `accessibilityLabel` defaults to the `name` prop so screen readers announce the user's name
@@ -0,0 +1,54 @@
1
+ import { Meta, Canvas, ArgTypes } from '@storybook/blocks';
2
+ import * as Stories from './Badge.native.stories';
3
+
4
+ <Meta of={Stories} />
5
+
6
+ # Badge (React Native)
7
+
8
+ React Native badge component for short status labels and counts. Five semantic variants matching the web `Badge` API.
9
+
10
+ > **Status: alpha** — API may change before stable release.
11
+
12
+ ## Usage
13
+
14
+ ```tsx
15
+ import { Badge } from '@boostdev/components';
16
+
17
+ <Badge variant="success">Active</Badge>
18
+ <Badge variant="error">3</Badge>
19
+ ```
20
+
21
+ ## Examples
22
+
23
+ ### All variants
24
+ <Canvas of={Stories.AllVariants} />
25
+
26
+ ### Primary
27
+ <Canvas of={Stories.Primary} />
28
+
29
+ ### Secondary
30
+ <Canvas of={Stories.Secondary} />
31
+
32
+ ### Success
33
+ <Canvas of={Stories.Success} />
34
+
35
+ ### Error
36
+ <Canvas of={Stories.Error} />
37
+
38
+ ### Warning
39
+ <Canvas of={Stories.Warning} />
40
+
41
+ ## Props
42
+
43
+ <ArgTypes of={Stories} />
44
+
45
+ ## Props reference
46
+
47
+ | Prop | Type | Default | Description |
48
+ |------|------|---------|-------------|
49
+ | `variant` | `"primary" \| "secondary" \| "success" \| "error" \| "warning"` | `"primary"` | Semantic colour |
50
+ | `children` | `ReactNode` | — | Badge label |
51
+
52
+ ## Accessibility
53
+
54
+ - Badge is decorative — wrap in a component with `accessibilityLabel` when colour alone conveys status (e.g. `accessibilityLabel="3 notifications"`)
@@ -13,7 +13,7 @@
13
13
  border-radius: var(--calendar_radius, var(--bdc-outline_radius));
14
14
  padding: var(--bds-space_m);
15
15
  box-shadow: var(--calendar_shadow, var(--bds-shadow_s));
16
- width: 20rem;
16
+ inline-size: 20rem;
17
17
  }
18
18
 
19
19
  .header {
@@ -33,8 +33,8 @@
33
33
  display: flex;
34
34
  align-items: center;
35
35
  justify-content: center;
36
- width: 2rem;
37
- height: 2rem;
36
+ inline-size: 2rem;
37
+ block-size: 2rem;
38
38
  border-radius: 50%;
39
39
  cursor: pointer;
40
40
  color: var(--bds-color_on-bg);
@@ -42,8 +42,8 @@
42
42
  }
43
43
 
44
44
  .navBtn svg {
45
- width: 1rem;
46
- height: 1rem;
45
+ inline-size: 1rem;
46
+ block-size: 1rem;
47
47
  }
48
48
 
49
49
  .navBtn:focus-visible {
@@ -59,7 +59,7 @@
59
59
 
60
60
  .grid {
61
61
  border-collapse: collapse;
62
- width: 100%;
62
+ inline-size: 100%;
63
63
  table-layout: fixed;
64
64
  }
65
65
 
@@ -80,7 +80,7 @@
80
80
  display: flex;
81
81
  align-items: center;
82
82
  justify-content: center;
83
- width: 100%;
83
+ inline-size: 100%;
84
84
  aspect-ratio: 1;
85
85
  border-radius: 50%;
86
86
  font-size: var(--bds-font_size--body--s);
@@ -9,7 +9,7 @@
9
9
  .track {
10
10
  display: flex;
11
11
  gap: var(--bds-space_m);
12
- overflow-x: auto;
12
+ overflow-inline: auto;
13
13
  scroll-snap-type: x mandatory;
14
14
  scrollbar-width: none;
15
15
  flex: 1;
@@ -30,8 +30,8 @@
30
30
  display: flex;
31
31
  align-items: center;
32
32
  justify-content: center;
33
- width: 2.5rem;
34
- height: 2.5rem;
33
+ inline-size: 2.5rem;
34
+ block-size: 2.5rem;
35
35
  border-radius: 50%;
36
36
  background-color: var(--bds-color_bg);
37
37
  color: var(--bds-color_on-bg);
@@ -48,8 +48,8 @@
48
48
  }
49
49
 
50
50
  .navBtn svg {
51
- width: 1.25rem;
52
- height: 1.25rem;
51
+ inline-size: 1.25rem;
52
+ block-size: 1.25rem;
53
53
  }
54
54
 
55
55
  .navBtn:focus-visible {
@@ -48,10 +48,10 @@
48
48
 
49
49
  /* Chevron drawn with CSS borders */
50
50
  .icon {
51
- width: 0.5rem;
52
- height: 0.5rem;
53
- border-right: 2px solid currentcolor;
54
- border-bottom: 2px solid currentcolor;
51
+ inline-size: 0.5rem;
52
+ block-size: 0.5rem;
53
+ border-inline-end: 2px solid currentcolor;
54
+ border-block-end: 2px solid currentcolor;
55
55
  transform: rotate(45deg);
56
56
  flex-shrink: 0;
57
57
  transition: transform var(--bds-animation_transition-duration) var(--bds-animation_easing);
@@ -49,8 +49,8 @@
49
49
 
50
50
  .externalLabel {
51
51
  position: absolute;
52
- width: 1px;
53
- height: 1px;
52
+ inline-size: 1px;
53
+ block-size: 1px;
54
54
  padding: 0;
55
55
  margin: -1px;
56
56
  overflow: hidden;
@@ -6,23 +6,23 @@
6
6
  }
7
7
 
8
8
  .spinner {
9
- width: 2.5em;
10
- height: 2.5em;
9
+ inline-size: 2.5em;
10
+ block-size: 2.5em;
11
11
  border: 4px solid var(--bds-color_bg);
12
- border-top: 4px solid var(--bds-color_interactive);
12
+ border-block-start: 4px solid var(--bds-color_interactive);
13
13
  border-radius: 50%;
14
14
  animation: spin 1s linear infinite;
15
15
  }
16
16
 
17
17
  .--size_small .spinner {
18
- width: 1.5em;
19
- height: 1.5em;
18
+ inline-size: 1.5em;
19
+ block-size: 1.5em;
20
20
  border-width: 2px;
21
21
  }
22
22
 
23
23
  .--size_large .spinner {
24
- width: 4em;
25
- height: 4em;
24
+ inline-size: 4em;
25
+ block-size: 4em;
26
26
  border-width: 6px;
27
27
  }
28
28
  }
@@ -9,7 +9,7 @@
9
9
  border-block-end: 3px solid var(--banner_border, var(--bds-color_blue));
10
10
  font-size: var(--bds-font_size--body);
11
11
  line-height: var(--bds-font_line-height--body);
12
- width: 100%;
12
+ inline-size: 100%;
13
13
  }
14
14
 
15
15
  .--variant_info {
@@ -62,8 +62,8 @@
62
62
  }
63
63
 
64
64
  .dismiss svg {
65
- width: 1rem;
66
- height: 1rem;
65
+ inline-size: 1rem;
66
+ block-size: 1rem;
67
67
  }
68
68
 
69
69
  .dismiss:focus-visible {
@@ -18,8 +18,8 @@
18
18
  display: inline-flex;
19
19
  align-items: center;
20
20
  justify-content: center;
21
- min-width: 2.25rem;
22
- height: 2.25rem;
21
+ min-inline-size: 2.25rem;
22
+ block-size: 2.25rem;
23
23
  padding-inline: var(--bds-space_xs);
24
24
  border-radius: var(--bds-border_radius--xs);
25
25
  background-color: var(--btn_bg, transparent);
@@ -62,16 +62,16 @@
62
62
  }
63
63
 
64
64
  .button.--nav svg {
65
- width: 1rem;
66
- height: 1rem;
65
+ inline-size: 1rem;
66
+ block-size: 1rem;
67
67
  }
68
68
 
69
69
  .ellipsis {
70
70
  display: inline-flex;
71
71
  align-items: center;
72
72
  justify-content: center;
73
- min-width: 2.25rem;
74
- height: 2.25rem;
73
+ min-inline-size: 2.25rem;
74
+ block-size: 2.25rem;
75
75
  color: var(--bds-color_on-bg--subtle);
76
76
  font-size: var(--bds-font_size--body);
77
77
  user-select: none;
@@ -3,7 +3,7 @@
3
3
  display: flex;
4
4
  flex-direction: column;
5
5
  gap: var(--bds-space_xxs);
6
- width: 100%;
6
+ inline-size: 100%;
7
7
  }
8
8
 
9
9
  .labelRow {
@@ -19,8 +19,8 @@
19
19
  }
20
20
 
21
21
  .track {
22
- width: 100%;
23
- height: var(--progress_height, var(--bds-space_xs));
22
+ inline-size: 100%;
23
+ block-size: var(--progress_height, var(--bds-space_xs));
24
24
  background-color: var(--bds-color_bg--subtle);
25
25
  border-radius: 999px;
26
26
  overflow: hidden;
@@ -31,10 +31,10 @@
31
31
  .track.--size_large { --progress_height: var(--bds-space_s); }
32
32
 
33
33
  .fill {
34
- height: 100%;
34
+ block-size: 100%;
35
35
  background-color: var(--progress_color-active, var(--bdc-color_active));
36
36
  border-radius: 999px;
37
- transition: width var(--bds-animation_transition-duration) var(--bds-animation_easing);
37
+ transition: inline-size var(--bds-animation_transition-duration) var(--bds-animation_easing);
38
38
  }
39
39
 
40
40
  @media (prefers-reduced-motion: reduce) {
@@ -6,13 +6,13 @@
6
6
 
7
7
  .separator.--horizontal {
8
8
  display: block;
9
- width: 100%;
10
- height: var(--separator_thickness, 1px);
9
+ inline-size: 100%;
10
+ block-size: var(--separator_thickness, 1px);
11
11
  }
12
12
 
13
13
  .separator.--vertical {
14
14
  display: inline-block;
15
- width: var(--separator_thickness, 1px);
15
+ inline-size: var(--separator_thickness, 1px);
16
16
  align-self: stretch;
17
17
  }
18
18
  }
@@ -9,7 +9,7 @@
9
9
  background-size: 200% 100%;
10
10
  animation: shimmer 1.5s infinite;
11
11
  border-radius: var(--bds-border_radius--xs);
12
- min-height: var(--bds-space_m);
12
+ min-block-size: var(--bds-space_m);
13
13
  }
14
14
  }
15
15
 
@@ -1,7 +1,7 @@
1
1
  @layer component {
2
2
  .wrapper {
3
- width: 100%;
4
- overflow-x: auto;
3
+ inline-size: 100%;
4
+ overflow-inline: auto;
5
5
  border: var(--table_border, none);
6
6
  --bdc_color: var(--bds-color_bg--subtle);
7
7
  --bdc-outline_radius: var(--bds-border_radius--s);
@@ -12,7 +12,7 @@
12
12
  }
13
13
 
14
14
  .table {
15
- width: 100%;
15
+ inline-size: 100%;
16
16
  border-collapse: collapse;
17
17
  font-size: var(--bds-font_size--body);
18
18
  font-family: var(--bds-font_family--body);
@@ -37,7 +37,7 @@
37
37
  font-size: var(--bds-font_size--body--s);
38
38
  color: var(--bds-color_on-bg--subtle);
39
39
  white-space: nowrap;
40
- border-bottom: 1px solid var(--bds-color_bg--subtle);
40
+ border-block-end: 1px solid var(--bds-color_bg--subtle);
41
41
  }
42
42
 
43
43
  .th.--sortable {
@@ -50,7 +50,7 @@
50
50
  align-items: center;
51
51
  gap: var(--bds-space_xxs);
52
52
  padding: var(--bds-space_s) var(--bds-space_m);
53
- width: 100%;
53
+ inline-size: 100%;
54
54
  cursor: pointer;
55
55
  color: var(--bds-color_on-bg--subtle);
56
56
  font-weight: var(--bds-font_weight--semibold);
@@ -71,8 +71,8 @@
71
71
  }
72
72
 
73
73
  .sortIcon {
74
- width: 1rem;
75
- height: 1rem;
74
+ inline-size: 1rem;
75
+ block-size: 1rem;
76
76
  opacity: 0.4;
77
77
  transition: transform var(--bds-animation_transition-duration) var(--bds-animation_easing),
78
78
  opacity var(--bds-animation_transition-duration) var(--bds-animation_easing);
@@ -88,12 +88,12 @@
88
88
  }
89
89
 
90
90
  .tbody .tr {
91
- border-bottom: 1px solid var(--bds-color_bg--subtle);
91
+ border-block-end: 1px solid var(--bds-color_bg--subtle);
92
92
  transition: var(--bds-animation_transition);
93
93
  }
94
94
 
95
95
  .tbody .tr:last-child {
96
- border-bottom: none;
96
+ border-block-end: none;
97
97
  }
98
98
 
99
99
  @media (hover: hover) and (pointer: fine) {
@@ -6,7 +6,7 @@
6
6
 
7
7
  .tabList {
8
8
  display: flex;
9
- border-bottom: 1px solid var(--bds-color_bg--subtle);
9
+ border-block-end: 1px solid var(--bds-color_bg--subtle);
10
10
  gap: var(--bds-space_xxs);
11
11
  }
12
12
 
@@ -19,8 +19,8 @@
19
19
  font-size: var(--bds-font_size--body);
20
20
  font-weight: var(--bds-font_weight--semibold);
21
21
  color: var(--tab_color, var(--bds-color_on-bg));
22
- border-bottom: 2px solid var(--tab_border-color, transparent);
23
- margin-bottom: -1px;
22
+ border-block-end: 2px solid var(--tab_border-color, transparent);
23
+ margin-block-end: -1px;
24
24
  cursor: pointer;
25
25
  transition: var(--bds-animation_transition);
26
26
  }
@@ -32,29 +32,29 @@
32
32
 
33
33
  /* Placement: top (default) */
34
34
  .--placement_top {
35
- bottom: calc(100% + var(--bds-space_xs));
36
- left: 50%;
35
+ inset-block-end: calc(100% + var(--bds-space_xs));
36
+ inset-inline-start: 50%;
37
37
  transform: translateX(-50%);
38
38
  }
39
39
 
40
40
  /* Placement: bottom */
41
41
  .--placement_bottom {
42
- top: calc(100% + var(--bds-space_xs));
43
- left: 50%;
42
+ inset-block-start: calc(100% + var(--bds-space_xs));
43
+ inset-inline-start: 50%;
44
44
  transform: translateX(-50%);
45
45
  }
46
46
 
47
47
  /* Placement: left */
48
48
  .--placement_left {
49
- right: calc(100% + var(--bds-space_xs));
50
- top: 50%;
49
+ inset-inline-end: calc(100% + var(--bds-space_xs));
50
+ inset-block-start: 50%;
51
51
  transform: translateY(-50%);
52
52
  }
53
53
 
54
54
  /* Placement: right */
55
55
  .--placement_right {
56
- left: calc(100% + var(--bds-space_xs));
57
- top: 50%;
56
+ inset-inline-start: calc(100% + var(--bds-space_xs));
57
+ inset-block-start: 50%;
58
58
  transform: translateY(-50%);
59
59
  }
60
60
 
@@ -0,0 +1,56 @@
1
+ import { Meta, Canvas, ArgTypes } from '@storybook/blocks';
2
+ import * as Stories from './Typography.native.stories';
3
+
4
+ <Meta of={Stories} />
5
+
6
+ # Typography (React Native)
7
+
8
+ React Native typography component wrapping `Text` with semantic variants. Use instead of bare `<Text>` to ensure consistent sizing, weight, and line-height from the design token scale.
9
+
10
+ > **Status: alpha** — API may change before stable release.
11
+
12
+ ## Usage
13
+
14
+ ```tsx
15
+ import { Typography } from '@boostdev/components';
16
+
17
+ <Typography variant="h1">Page title</Typography>
18
+ <Typography variant="body">Body copy text.</Typography>
19
+ ```
20
+
21
+ ## Examples
22
+
23
+ ### All variants
24
+ <Canvas of={Stories.AllVariants} />
25
+
26
+ ### Heading 1
27
+ <Canvas of={Stories.Heading1} />
28
+
29
+ ### Heading 2
30
+ <Canvas of={Stories.Heading2} />
31
+
32
+ ### Heading 3
33
+ <Canvas of={Stories.Heading3} />
34
+
35
+ ### Body
36
+ <Canvas of={Stories.Body} />
37
+
38
+ ### Body small
39
+ <Canvas of={Stories.BodySmall} />
40
+
41
+ ## Props
42
+
43
+ <ArgTypes of={Stories} />
44
+
45
+ ## Props reference
46
+
47
+ | Prop | Type | Default | Description |
48
+ |------|------|---------|-------------|
49
+ | `variant` | `"h1" \| "h2" \| "h3" \| "body" \| "body_s"` | `"body"` | Typographic scale |
50
+ | `children` | `ReactNode` | — | Text content |
51
+ | `style` | `StyleProp<TextStyle>` | — | Override text styles |
52
+
53
+ ## Accessibility
54
+
55
+ - `h1`/`h2`/`h3` variants set `accessibilityRole="header"` automatically
56
+ - Override with the `accessibilityRole` prop if the visual hierarchy doesn't match the semantic hierarchy
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Invoker Commands API polyfill (Baseline 2025).
3
+ *
4
+ * Handles `commandfor` / `command` attributes on <button> elements for browsers
5
+ * that don't yet support the native API. Dispatches a synthetic `command` event
6
+ * on the target element before performing the action — identical to native
7
+ * behaviour — so any existing `command` event listeners continue to work.
8
+ *
9
+ * Supports the dialog-specific commands used by this library:
10
+ * command="show-modal" → dialog.showModal()
11
+ * command="close" → dialog.close()
12
+ *
13
+ * Installation is idempotent and SSR-safe. The function is called automatically
14
+ * when the Dialog component is imported; consumers do not need to call it.
15
+ */
16
+
17
+ let installed = false;
18
+
19
+ export function installInvokerCommandsPolyfill(): void {
20
+ if (installed) return;
21
+ if (typeof window === 'undefined') return;
22
+ if ('command' in HTMLButtonElement.prototype) return; // native support present
23
+
24
+ installed = true;
25
+
26
+ document.addEventListener('click', (e: MouseEvent) => {
27
+ const button = (e.target as Element | null)?.closest<HTMLButtonElement>('button[commandfor]');
28
+ if (!button) return;
29
+
30
+ const targetId = button.getAttribute('commandfor');
31
+ const command = button.getAttribute('command');
32
+ if (!targetId || !command) return;
33
+
34
+ const target = document.getElementById(targetId);
35
+ if (!target) return;
36
+
37
+ // Dispatch a synthetic command event that mirrors the native CommandEvent.
38
+ const commandEvent = new Event('command', { cancelable: true, bubbles: false });
39
+ Object.defineProperty(commandEvent, 'command', { value: command, configurable: true });
40
+ Object.defineProperty(commandEvent, 'source', { value: button, configurable: true });
41
+ const notCancelled = target.dispatchEvent(commandEvent);
42
+
43
+ if (!notCancelled) return;
44
+
45
+ if (target instanceof HTMLDialogElement) {
46
+ if (command === 'show-modal' && !target.open) {
47
+ target.showModal();
48
+ } else if (command === 'close' && target.open) {
49
+ target.close();
50
+ }
51
+ } else if (target.hasAttribute('popover') && 'showPopover' in target) {
52
+ // Popover API commands — Popover API is Baseline 2024 so showPopover exists
53
+ // in any browser that lacks native invoker command support.
54
+ const popover = target as HTMLElement & {
55
+ showPopover(): void;
56
+ hidePopover(): void;
57
+ togglePopover(force?: boolean): boolean;
58
+ };
59
+ if (command === 'toggle-popover') {
60
+ popover.togglePopover();
61
+ } else if (command === 'show-popover') {
62
+ popover.showPopover();
63
+ } else if (command === 'hide-popover') {
64
+ popover.hidePopover();
65
+ }
66
+ }
67
+ });
68
+ }
@@ -10,4 +10,23 @@ declare module 'react' {
10
10
  anchorName?: string;
11
11
  positionAnchor?: string;
12
12
  }
13
+
14
+ // Invoker Commands API (Baseline 2025) — not yet in @types/react.
15
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
16
+ interface ButtonHTMLAttributes<T> {
17
+ commandfor?: string;
18
+ command?: string;
19
+ }
20
+ }
21
+
22
+ // CommandEvent and its entry in the DOM event map — not yet in TypeScript's lib.dom.
23
+ declare global {
24
+ interface CommandEvent extends Event {
25
+ readonly command: string;
26
+ readonly source: Element;
27
+ }
28
+
29
+ interface HTMLElementEventMap {
30
+ command: CommandEvent;
31
+ }
13
32
  }