@reliability-design/web 1.4.8 → 1.4.10
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/CHANGELOG.md +214 -0
- package/custom-elements.json +18600 -0
- package/dist/components/rel-accordion/README.md +9 -2
- package/dist/components/rel-alert/README.md +5 -8
- package/dist/components/rel-autocomplete/README.md +209 -208
- package/dist/components/rel-avatar/README.md +228 -118
- package/dist/components/rel-breadcrumb/README.md +1 -0
- package/dist/components/rel-button/README.md +9 -9
- package/dist/components/rel-calendar/README.md +111 -109
- package/dist/components/rel-cardmedia/README.md +1 -1
- package/dist/components/rel-carousel/README.md +187 -186
- package/dist/components/rel-checkbox/README.md +159 -146
- package/dist/components/rel-chip/README.md +141 -138
- package/dist/components/rel-container/README.md +1 -1
- package/dist/components/rel-date-picker/README.md +221 -200
- package/dist/components/rel-drawer/README.md +4 -4
- package/dist/components/rel-kbd/README.md +188 -141
- package/dist/components/rel-layout/README.md +34 -34
- package/dist/components/rel-menu/README.md +17 -10
- package/dist/components/rel-menubar/README.md +218 -218
- package/dist/components/rel-otp/README.md +4 -8
- package/dist/components/rel-pagination/README.md +141 -100
- package/dist/components/rel-popover/README.md +220 -203
- package/dist/components/rel-radio/README.md +142 -131
- package/dist/components/rel-range-picker/README.md +194 -165
- package/dist/components/rel-resizable/README.md +220 -199
- package/dist/components/rel-segmented-button/README.md +158 -156
- package/dist/components/rel-select/README.md +22 -16
- package/dist/components/rel-sheet/README.md +3 -2
- package/dist/components/rel-signature-pad/README.md +161 -0
- package/dist/components/rel-slider/README.md +140 -131
- package/dist/components/rel-snackbar/README.md +170 -168
- package/dist/components/rel-split-button/README.md +129 -0
- package/dist/components/rel-stepper/README.md +171 -162
- package/dist/components/rel-switch/README.md +122 -117
- package/dist/components/rel-tab/README.md +184 -173
- package/dist/components/rel-textfield/README.md +23 -30
- package/dist/components/rel-time-picker/README.md +188 -164
- package/dist/components/rel-toast/README.md +20 -11
- package/dist/components/rel-tooltip/README.md +5 -4
- package/dist/components/rel-tree/README.md +157 -0
- package/dist/components/rel-upload/README.md +8 -5
- package/dist/index.d.mts +1698 -607
- package/dist/index.d.ts +1698 -607
- package/dist/index.js +12276 -6347
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +12218 -6296
- package/dist/index.mjs.map +1 -1
- package/dist/styles/motion.css +31 -0
- package/dist/styles/motion.tokens.json +38 -0
- package/dist/styles/vars.css +88 -64
- package/dist/{vars-DXX6QOAG.css → vars-E22LYFMV.css} +88 -64
- package/package.json +60 -37
- package/dist/styles/focus-ring.ts +0 -84
|
@@ -1,218 +1,218 @@
|
|
|
1
|
-
# rel-menubar
|
|
2
|
-
|
|
3
|
-
`<rel-menubar>` is a horizontal application menubar (`role="menubar"`) in the style of a desktop app's File / Edit / View strip. Each child `<rel-menubar-menu label="…">` becomes a trigger button, and that menu's light-DOM children — `data-item` buttons, `data-separator` rules, `data-submenu` groups, `data-checkbox` labels and `data-radio-group` blocks — are read by the menubar and rendered into a fixed-position dropdown panel with full arrow-key navigation, optional bound keyboard shortcuts and `rel-menubar-select` / `rel-menubar-change` events. Use it for dense, command-style navigation in tools and admin surfaces. For one dropdown hanging off a single button use `rel-menu`; for site navigation with links use the Navbar recipe (`/docs/recipes/navbar`).
|
|
4
|
-
|
|
5
|
-
## Usage
|
|
6
|
-
|
|
7
|
-
```html
|
|
8
|
-
<rel-menubar>
|
|
9
|
-
<rel-menubar-menu label="File">
|
|
10
|
-
<button data-item data-shortcut="Ctrl+N">New Tab</button>
|
|
11
|
-
<button data-item data-shortcut="Ctrl+Shift+N">New Window</button>
|
|
12
|
-
<div data-separator></div>
|
|
13
|
-
<button data-item disabled>Close</button>
|
|
14
|
-
</rel-menubar-menu>
|
|
15
|
-
|
|
16
|
-
<rel-menubar-menu label="Edit">
|
|
17
|
-
<button data-item data-shortcut="Ctrl+Z">Undo</button>
|
|
18
|
-
<button data-item data-shortcut="Ctrl+Y">Redo</button>
|
|
19
|
-
<div data-separator></div>
|
|
20
|
-
<button data-item data-shortcut="Ctrl+X">Cut</button>
|
|
21
|
-
<button data-item data-shortcut="Ctrl+C">Copy</button>
|
|
22
|
-
<button data-item data-shortcut="Ctrl+V">Paste</button>
|
|
23
|
-
</rel-menubar-menu>
|
|
24
|
-
</rel-menubar>
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
### Width mode and disabled menus
|
|
28
|
-
|
|
29
|
-
```html
|
|
30
|
-
<!-- type="fit" (default) hugs its triggers; type="fixed" spans its container -->
|
|
31
|
-
<rel-menubar type="fixed">
|
|
32
|
-
<rel-menubar-menu label="File">
|
|
33
|
-
<button data-item>New Tab</button>
|
|
34
|
-
</rel-menubar-menu>
|
|
35
|
-
<rel-menubar-menu label="Edit">
|
|
36
|
-
<button data-item>Undo</button>
|
|
37
|
-
</rel-menubar-menu>
|
|
38
|
-
<!-- a disabled menu keeps its trigger visible and focusable but never opens -->
|
|
39
|
-
<rel-menubar-menu label="View" disabled>
|
|
40
|
-
<button data-item>Zoom In</button>
|
|
41
|
-
</rel-menubar-menu>
|
|
42
|
-
</rel-menubar>
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
### Submenus, checkbox items and radio groups
|
|
46
|
-
|
|
47
|
-
```html
|
|
48
|
-
<rel-menubar>
|
|
49
|
-
<rel-menubar-menu label="File">
|
|
50
|
-
<button data-item>New Tab</button>
|
|
51
|
-
<div data-separator></div>
|
|
52
|
-
<div data-submenu label="Share">
|
|
53
|
-
<button data-item value="email">Email link</button>
|
|
54
|
-
<button data-item value="messages">Messages</button>
|
|
55
|
-
<button data-item value="notes">Notes</button>
|
|
56
|
-
</div>
|
|
57
|
-
</rel-menubar-menu>
|
|
58
|
-
|
|
59
|
-
<rel-menubar-menu label="View">
|
|
60
|
-
<label data-checkbox>
|
|
61
|
-
<input type="checkbox" />
|
|
62
|
-
Always Show Bookmarks Bar
|
|
63
|
-
</label>
|
|
64
|
-
<label data-checkbox>
|
|
65
|
-
<input type="checkbox" checked />
|
|
66
|
-
Always Show Full URLs
|
|
67
|
-
</label>
|
|
68
|
-
<div data-separator></div>
|
|
69
|
-
<div data-radio-group name="profile" value="andy">
|
|
70
|
-
<button data-radio value="andy">Andy</button>
|
|
71
|
-
<button data-radio value="benoit">Benoit</button>
|
|
72
|
-
<button data-radio value="luis">Luis</button>
|
|
73
|
-
</div>
|
|
74
|
-
</rel-menubar-menu>
|
|
75
|
-
</rel-menubar>
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
### Bound shortcuts and events
|
|
79
|
-
|
|
80
|
-
```html
|
|
81
|
-
<rel-menubar id="app-menubar" bind-shortcuts>
|
|
82
|
-
<rel-menubar-menu label="Edit">
|
|
83
|
-
<button data-item value="undo" data-shortcut="Mod+Z">Undo</button>
|
|
84
|
-
<button data-item value="redo" data-shortcut="Mod+Shift+Z">Redo</button>
|
|
85
|
-
</rel-menubar-menu>
|
|
86
|
-
</rel-menubar>
|
|
87
|
-
|
|
88
|
-
<script>
|
|
89
|
-
const bar = document.getElementById('app-menubar');
|
|
90
|
-
// { label: "Undo", value: "undo", shortcut: "Mod+Z" }
|
|
91
|
-
bar.addEventListener('rel-menubar-select', (e) => console.log('select', e.detail));
|
|
92
|
-
// { type: "checkbox", label, checked } | { type: "radio", name, value }
|
|
93
|
-
bar.addEventListener('rel-menubar-change', (e) => console.log('change', e.detail));
|
|
94
|
-
</script>
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
### Authoring items
|
|
98
|
-
|
|
99
|
-
Items are plain HTML placed inside a `<rel-menubar-menu>`; the menubar recognises them by data attribute and renders its own copy in the panel.
|
|
100
|
-
|
|
101
|
-
| Attribute | Put it on | What it becomes |
|
|
102
|
-
| --- | --- | --- |
|
|
103
|
-
| `data-item` | `<button>` | A `role="menuitem"` row. Its text content is the label; `value` (optional) and `data-shortcut` are echoed in `rel-menubar-select`. A native `disabled` attribute renders the row `aria-disabled` and skips it for clicks and bound shortcuts. |
|
|
104
|
-
| `data-shortcut` | a `data-item` button | Shortcut text shown right-aligned in the row, exactly as written. With `bind-shortcuts` it is also matched against keydowns: `+`-joined tokens, case-insensitive, `Mod` = Cmd on Apple / Ctrl elsewhere, plus `Ctrl`, `Alt`, `Shift`, `Meta`. |
|
|
105
|
-
| `data-separator` | `<div>` | A `role="separator"` rule. |
|
|
106
|
-
| `data-submenu` + `label` | `<div>` wrapping `data-item` buttons | A submenu trigger row (`aria-haspopup="menu"`) that opens a nested panel to the right on hover, click, Enter or ArrowRight. |
|
|
107
|
-
| `data-checkbox` | `<label>` containing `<input type="checkbox">` | A `rel-checkbox` row. The label text is the element's own text nodes (or a `label` attribute); state lives on the wrapped `<input>`. |
|
|
108
|
-
| `data-radio-group` + `name` + `value` | `<div>` wrapping `data-radio` buttons | A `rel-radio` group. `value` on the wrapper is the current selection and is rewritten on change; the chosen `data-radio` also gets `data-checked="true"`. |
|
|
109
|
-
| `data-radio` + `value` | `<button>` inside a `data-radio-group` | One option; text content is its label. |
|
|
110
|
-
|
|
111
|
-
<!-- readme-gen:start -->
|
|
112
|
-
|
|
113
|
-
<!-- Generated from rel-menubar.ts, rel-menubar-menu.ts — do not edit; run readme-generate.mjs -->
|
|
114
|
-
|
|
115
|
-
## Elements
|
|
116
|
-
|
|
117
|
-
| Element | Class | Source |
|
|
118
|
-
| --- | --- | --- |
|
|
119
|
-
| `<rel-menubar>` | `RelMenubar` | `rel-menubar.ts` |
|
|
120
|
-
| `<rel-menubar-menu>` | `RelMenubarMenu` | `rel-menubar-menu.ts` |
|
|
121
|
-
|
|
122
|
-
## API
|
|
123
|
-
|
|
124
|
-
### `<rel-menubar>`
|
|
125
|
-
|
|
126
|
-
#### Attributes / Properties
|
|
127
|
-
|
|
128
|
-
| Property | Attribute | Type | Default | Description |
|
|
129
|
-
| --- | --- | --- | --- | --- |
|
|
130
|
-
| `bindShortcuts` | `bind-shortcuts` | `boolean` | `false` | Opt-in: actually bind the keys declared via `data-shortcut` on items (e.g. `data-shortcut="Ctrl+Z"`) instead of only displaying them; a "Mod" token means Cmd on Apple platforms and Ctrl elsewhere, and keystrokes that originate in inputs, textareas or contenteditable regions are ignored. |
|
|
131
|
-
| `type` | `type` | `"fixed" \| "fit"` | `"fit"` | Width mode (Figma "Menu Bar" Type, 8699:4): `fit` sizes the bar to its triggers, `fixed` stretches it across its container (override the stretched width with `--rel-menubar-width`). The default stays `fit`, which is how the bar has always rendered. Reflected. |
|
|
132
|
-
|
|
133
|
-
### Events
|
|
134
|
-
|
|
135
|
-
| Event | Detail | Bubbles / composed | Description |
|
|
136
|
-
| --- | --- | --- | --- |
|
|
137
|
-
| `rel-menubar-change` | `detail` | bubbles / composed | Fired when a `data-checkbox` item is toggled or a `data-radio` option is picked. Detail: `{ type: "checkbox", label, checked }` or `{ type: "radio", name, value }`. |
|
|
138
|
-
| `rel-menubar-select` | `{ label: item.textContent?.trim() ?? "", value: item.getAttribute("value") ?? undefined, shortcut: item.getAttribute("data-shortcut") ?? undefined, }` | bubbles / composed | Fired when a regular `data-item` (in a panel or a submenu) is activated by click, Enter/Space or a bound shortcut. Detail: `{ label: string, value?: string, shortcut?: string }` taken from the item's text, `value` and `data-shortcut` attributes. |
|
|
139
|
-
|
|
140
|
-
### Slots
|
|
141
|
-
|
|
142
|
-
| Slot | Description |
|
|
143
|
-
| --- | --- |
|
|
144
|
-
| — | This element has no slots. |
|
|
145
|
-
|
|
146
|
-
### CSS Parts
|
|
147
|
-
|
|
148
|
-
| Part | Description |
|
|
149
|
-
| --- | --- |
|
|
150
|
-
| — | None exposed. |
|
|
151
|
-
|
|
152
|
-
### `<rel-menubar-menu>`
|
|
153
|
-
|
|
154
|
-
#### Attributes / Properties
|
|
155
|
-
|
|
156
|
-
| Property | Attribute | Type | Default | Description |
|
|
157
|
-
| --- | --- | --- | --- | --- |
|
|
158
|
-
| `label` | `label` | `string` | `""` | Text of the trigger button the parent menubar renders for this menu. |
|
|
159
|
-
| `disabled` | `disabled` | `boolean` | `false` | Renders this menu's trigger in the Figma State=Disabled style and stops it opening by pointer, keyboard or hover-switch. The trigger stays in the tab order and reachable with the arrow keys (`aria-disabled`, not the native `disabled` attribute), matching how disabled `data-item` rows behave. Reflected. |
|
|
160
|
-
|
|
161
|
-
### Events
|
|
162
|
-
|
|
163
|
-
| Event | Detail | Bubbles / composed | Description |
|
|
164
|
-
| --- | --- | --- | --- |
|
|
165
|
-
| — | — | — | This element emits no custom events. |
|
|
166
|
-
|
|
167
|
-
### Slots
|
|
168
|
-
|
|
169
|
-
| Slot | Description |
|
|
170
|
-
| --- | --- |
|
|
171
|
-
| — | This element has no slots. |
|
|
172
|
-
|
|
173
|
-
### CSS Parts
|
|
174
|
-
|
|
175
|
-
| Part | Description |
|
|
176
|
-
| --- | --- |
|
|
177
|
-
| — | None exposed. |
|
|
178
|
-
|
|
179
|
-
<!-- readme-gen:end -->
|
|
180
|
-
|
|
181
|
-
## Accessibility
|
|
182
|
-
|
|
183
|
-
- Roles follow the APG menubar pattern: the strip is `role="menubar"`; each trigger is a `<button role="menuitem" aria-haspopup="menu" aria-expanded="true|false">`; each dropdown is a `role="menu"` panel with `tabindex="-1"`; rows are `role="menuitem"` (disabled ones add `aria-disabled="true"`), separators are `role="separator"`, and a submenu trigger row carries `aria-haspopup="menu"` / `aria-expanded` with its own nested `role="menu"` panel.
|
|
184
|
-
- Focus model: the triggers are real buttons in the tab order. When a menu opens from the keyboard, DOM focus moves to the panel itself and the highlighted row is exposed through `aria-activedescendant` (row ids are generated per menubar instance), so arrow keys move a virtual focus rather than DOM focus. `Escape` closes everything and returns DOM focus to the trigger that owned the menu.
|
|
185
|
-
- Checkbox items render as `rel-checkbox` and radio groups as `rel-radio` inside the panel, so those rows carry the native checkbox / radiogroup semantics of those components.
|
|
186
|
-
- There is no type-ahead and no wrap-around: arrow keys stop at the first / last trigger or row.
|
|
187
|
-
- Bound shortcuts (`bind-shortcuts`) listen on `document`, skip events whose `defaultPrevented` is already set, skip keystrokes that start in an input, textarea, select or contenteditable, and call `preventDefault()` on a match.
|
|
188
|
-
|
|
189
|
-
| Key | Where | Action |
|
|
190
|
-
| --- | --- | --- |
|
|
191
|
-
| `Tab` / `Shift+Tab` | anywhere | Native focus movement between the trigger buttons (and out of the menubar). |
|
|
192
|
-
| `ArrowRight` / `ArrowLeft` | trigger | Move focus to the next / previous trigger (does not open it). |
|
|
193
|
-
| `Enter` / `Space` / `ArrowDown` | trigger | Open that menu, focus the panel and highlight the first item. |
|
|
194
|
-
| `Enter` / `Space` / `ArrowDown` | trigger of a `disabled` menu | Nothing — the trigger is reachable but never opens. |
|
|
195
|
-
| `Escape` | trigger (menu open) | Close all menus and keep focus on the trigger. |
|
|
196
|
-
| `ArrowDown` / `ArrowUp` | open panel | Highlight the next / previous item (clamped, no wrap). |
|
|
197
|
-
| `Home` / `End` | open panel | Highlight the first / last item. |
|
|
198
|
-
| `ArrowRight` | open panel, on a submenu row | Open the submenu and highlight its first item. |
|
|
199
|
-
| `Enter` / `Space` | open panel | Activate the highlighted row: fire `rel-menubar-select` and close, toggle a checkbox, pick a radio option, or open a submenu. |
|
|
200
|
-
| `Escape` | open panel | Close all menus and return focus to the trigger. |
|
|
201
|
-
| `ArrowDown` / `ArrowUp` / `Home` / `End` | open submenu | Move the highlight inside the submenu. |
|
|
202
|
-
| `ArrowLeft` / `Escape` | open submenu | Close only the submenu; the parent panel stays open and focused. |
|
|
203
|
-
| `Enter` / `Space` | open submenu | Activate the highlighted sub-item: fire `rel-menubar-select`, close everything, refocus the trigger. |
|
|
204
|
-
|
|
205
|
-
## Notes / Edge Cases
|
|
206
|
-
|
|
207
|
-
- Both elements render into **light DOM** (`createRenderRoot()` returns the host): there are no slots, no `::part` hooks and no style encapsulation. The menubar injects its own `<style>` block; the `.menubar-trigger` / `.menubar-panel` / `.menu-item` class names are implementation details, not a styling contract.
|
|
208
|
-
- `<rel-menubar-menu>` is a hidden data holder (`display: none`) — it never renders its children itself. The menubar discovers menus once, when it connects; menus added or removed later are not picked up until the menubar is re-attached. Item lists inside a menu are re-read on each render, so item changes show up the next time a panel opens.
|
|
209
|
-
- Item content is flattened: a `data-item` row renders the button's `textContent` only, so markup inside the button (icons, `<rel-kbd>` chips) is not reproduced. Use `data-shortcut` for the shortcut hint — it is displayed verbatim, so `Mod+Z` is shown literally as "Mod+Z"; write `Ctrl+Z` if you want that text and only need Ctrl.
|
|
210
|
-
- Panels are `position: fixed` (z-index 1000, submenus 1001), placed flush under the trigger / against the right edge of the submenu row, and repositioned on scroll. They are not moved into the top layer, so a `rel-modal` or other top-layer surface can cover them.
|
|
211
|
-
- Panels are a fixed 300px wide, matching the Figma Menu component the bar is paired with; override with `--rel-menubar-panel-width`. There is no viewport clamping, so a panel opened near the right edge can overflow.
|
|
212
|
-
- The bar follows Figma's Menu Bar geometry: 40px tall triggers (`spacing/xs` × `spacing/md` padding on a `body/lg` label) on a `bg/fill/neutral/default` surface with `border-radius/sm` and no border. The trigger whose menu is open takes the hover overlay plus a medium-weight label, which widens it by a pixel or two — that weight shift is in the Figma spec, not an artefact.
|
|
213
|
-
- A `disabled` `<rel-menubar-menu>` renders its trigger in the disabled tone and refuses to open by click, hover-switch, Enter/Space/ArrowDown or a bound shortcut. It uses `aria-disabled`, not the native `disabled` attribute, so the trigger stays in the tab order — the same model the disabled `data-item` rows use.
|
|
214
|
-
- With a menu already open the bar is in hover-switch mode: moving the pointer onto another trigger opens that menu, so a *click* on it arrives when it is already open and toggles it shut. Switch menus by hovering, or click twice.
|
|
215
|
-
- Hover-switching between menus is armed only after a click has opened one; hovering triggers on a closed menubar does nothing. Clicking outside (checked via `composedPath`, so it works inside shadow roots) closes everything.
|
|
216
|
-
- Disabled `data-item` buttons ignore pointer clicks and bound shortcuts but are still reachable with the arrow keys.
|
|
217
|
-
- `rel-menubar-select` fires only for regular `data-item` rows (panel or submenu); checkbox and radio rows fire `rel-menubar-change` instead. Both events bubble and are composed.
|
|
218
|
-
- Only one submenu can be open at a time, and submenus nest one level deep (a `data-submenu` inside a `data-submenu` is not rendered).
|
|
1
|
+
# rel-menubar
|
|
2
|
+
|
|
3
|
+
`<rel-menubar>` is a horizontal application menubar (`role="menubar"`) in the style of a desktop app's File / Edit / View strip. Each child `<rel-menubar-menu label="…">` becomes a trigger button, and that menu's light-DOM children — `data-item` buttons, `data-separator` rules, `data-submenu` groups, `data-checkbox` labels and `data-radio-group` blocks — are read by the menubar and rendered into a fixed-position dropdown panel with full arrow-key navigation, optional bound keyboard shortcuts and `rel-menubar-select` / `rel-menubar-change` events. Use it for dense, command-style navigation in tools and admin surfaces. For one dropdown hanging off a single button use `rel-menu`; for site navigation with links use the Navbar recipe (`/docs/recipes/navbar`).
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```html
|
|
8
|
+
<rel-menubar>
|
|
9
|
+
<rel-menubar-menu label="File">
|
|
10
|
+
<button data-item data-shortcut="Ctrl+N">New Tab</button>
|
|
11
|
+
<button data-item data-shortcut="Ctrl+Shift+N">New Window</button>
|
|
12
|
+
<div data-separator></div>
|
|
13
|
+
<button data-item disabled>Close</button>
|
|
14
|
+
</rel-menubar-menu>
|
|
15
|
+
|
|
16
|
+
<rel-menubar-menu label="Edit">
|
|
17
|
+
<button data-item data-shortcut="Ctrl+Z">Undo</button>
|
|
18
|
+
<button data-item data-shortcut="Ctrl+Y">Redo</button>
|
|
19
|
+
<div data-separator></div>
|
|
20
|
+
<button data-item data-shortcut="Ctrl+X">Cut</button>
|
|
21
|
+
<button data-item data-shortcut="Ctrl+C">Copy</button>
|
|
22
|
+
<button data-item data-shortcut="Ctrl+V">Paste</button>
|
|
23
|
+
</rel-menubar-menu>
|
|
24
|
+
</rel-menubar>
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Width mode and disabled menus
|
|
28
|
+
|
|
29
|
+
```html
|
|
30
|
+
<!-- type="fit" (default) hugs its triggers; type="fixed" spans its container -->
|
|
31
|
+
<rel-menubar type="fixed">
|
|
32
|
+
<rel-menubar-menu label="File">
|
|
33
|
+
<button data-item>New Tab</button>
|
|
34
|
+
</rel-menubar-menu>
|
|
35
|
+
<rel-menubar-menu label="Edit">
|
|
36
|
+
<button data-item>Undo</button>
|
|
37
|
+
</rel-menubar-menu>
|
|
38
|
+
<!-- a disabled menu keeps its trigger visible and focusable but never opens -->
|
|
39
|
+
<rel-menubar-menu label="View" disabled>
|
|
40
|
+
<button data-item>Zoom In</button>
|
|
41
|
+
</rel-menubar-menu>
|
|
42
|
+
</rel-menubar>
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Submenus, checkbox items and radio groups
|
|
46
|
+
|
|
47
|
+
```html
|
|
48
|
+
<rel-menubar>
|
|
49
|
+
<rel-menubar-menu label="File">
|
|
50
|
+
<button data-item>New Tab</button>
|
|
51
|
+
<div data-separator></div>
|
|
52
|
+
<div data-submenu label="Share">
|
|
53
|
+
<button data-item value="email">Email link</button>
|
|
54
|
+
<button data-item value="messages">Messages</button>
|
|
55
|
+
<button data-item value="notes">Notes</button>
|
|
56
|
+
</div>
|
|
57
|
+
</rel-menubar-menu>
|
|
58
|
+
|
|
59
|
+
<rel-menubar-menu label="View">
|
|
60
|
+
<label data-checkbox>
|
|
61
|
+
<input type="checkbox" />
|
|
62
|
+
Always Show Bookmarks Bar
|
|
63
|
+
</label>
|
|
64
|
+
<label data-checkbox>
|
|
65
|
+
<input type="checkbox" checked />
|
|
66
|
+
Always Show Full URLs
|
|
67
|
+
</label>
|
|
68
|
+
<div data-separator></div>
|
|
69
|
+
<div data-radio-group name="profile" value="andy">
|
|
70
|
+
<button data-radio value="andy">Andy</button>
|
|
71
|
+
<button data-radio value="benoit">Benoit</button>
|
|
72
|
+
<button data-radio value="luis">Luis</button>
|
|
73
|
+
</div>
|
|
74
|
+
</rel-menubar-menu>
|
|
75
|
+
</rel-menubar>
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Bound shortcuts and events
|
|
79
|
+
|
|
80
|
+
```html
|
|
81
|
+
<rel-menubar id="app-menubar" bind-shortcuts>
|
|
82
|
+
<rel-menubar-menu label="Edit">
|
|
83
|
+
<button data-item value="undo" data-shortcut="Mod+Z">Undo</button>
|
|
84
|
+
<button data-item value="redo" data-shortcut="Mod+Shift+Z">Redo</button>
|
|
85
|
+
</rel-menubar-menu>
|
|
86
|
+
</rel-menubar>
|
|
87
|
+
|
|
88
|
+
<script>
|
|
89
|
+
const bar = document.getElementById('app-menubar');
|
|
90
|
+
// { label: "Undo", value: "undo", shortcut: "Mod+Z" }
|
|
91
|
+
bar.addEventListener('rel-menubar-select', (e) => console.log('select', e.detail));
|
|
92
|
+
// { type: "checkbox", label, checked } | { type: "radio", name, value }
|
|
93
|
+
bar.addEventListener('rel-menubar-change', (e) => console.log('change', e.detail));
|
|
94
|
+
</script>
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Authoring items
|
|
98
|
+
|
|
99
|
+
Items are plain HTML placed inside a `<rel-menubar-menu>`; the menubar recognises them by data attribute and renders its own copy in the panel.
|
|
100
|
+
|
|
101
|
+
| Attribute | Put it on | What it becomes |
|
|
102
|
+
| --- | --- | --- |
|
|
103
|
+
| `data-item` | `<button>` | A `role="menuitem"` row. Its text content is the label; `value` (optional) and `data-shortcut` are echoed in `rel-menubar-select`. A native `disabled` attribute renders the row `aria-disabled` and skips it for clicks and bound shortcuts. |
|
|
104
|
+
| `data-shortcut` | a `data-item` button | Shortcut text shown right-aligned in the row, exactly as written. With `bind-shortcuts` it is also matched against keydowns: `+`-joined tokens, case-insensitive, `Mod` = Cmd on Apple / Ctrl elsewhere, plus `Ctrl`, `Alt`, `Shift`, `Meta`. |
|
|
105
|
+
| `data-separator` | `<div>` | A `role="separator"` rule. |
|
|
106
|
+
| `data-submenu` + `label` | `<div>` wrapping `data-item` buttons | A submenu trigger row (`aria-haspopup="menu"`) that opens a nested panel to the right on hover, click, Enter or ArrowRight. |
|
|
107
|
+
| `data-checkbox` | `<label>` containing `<input type="checkbox">` | A `rel-checkbox` row. The label text is the element's own text nodes (or a `label` attribute); state lives on the wrapped `<input>`. |
|
|
108
|
+
| `data-radio-group` + `name` + `value` | `<div>` wrapping `data-radio` buttons | A `rel-radio` group. `value` on the wrapper is the current selection and is rewritten on change; the chosen `data-radio` also gets `data-checked="true"`. |
|
|
109
|
+
| `data-radio` + `value` | `<button>` inside a `data-radio-group` | One option; text content is its label. |
|
|
110
|
+
|
|
111
|
+
<!-- readme-gen:start -->
|
|
112
|
+
|
|
113
|
+
<!-- Generated from rel-menubar.ts, rel-menubar-menu.ts — do not edit; run readme-generate.mjs -->
|
|
114
|
+
|
|
115
|
+
## Elements
|
|
116
|
+
|
|
117
|
+
| Element | Class | Source |
|
|
118
|
+
| --- | --- | --- |
|
|
119
|
+
| `<rel-menubar>` | `RelMenubar` | `rel-menubar.ts` |
|
|
120
|
+
| `<rel-menubar-menu>` | `RelMenubarMenu` | `rel-menubar-menu.ts` |
|
|
121
|
+
|
|
122
|
+
## API
|
|
123
|
+
|
|
124
|
+
### `<rel-menubar>`
|
|
125
|
+
|
|
126
|
+
#### Attributes / Properties
|
|
127
|
+
|
|
128
|
+
| Property | Attribute | Type | Default | Description |
|
|
129
|
+
| --- | --- | --- | --- | --- |
|
|
130
|
+
| `bindShortcuts` | `bind-shortcuts` | `boolean` | `false` | Opt-in: actually bind the keys declared via `data-shortcut` on items (e.g. `data-shortcut="Ctrl+Z"`) instead of only displaying them; a "Mod" token means Cmd on Apple platforms and Ctrl elsewhere, and keystrokes that originate in inputs, textareas or contenteditable regions are ignored. |
|
|
131
|
+
| `type` | `type` | `"fixed" \| "fit"` | `"fit"` | Width mode (Figma "Menu Bar" Type, 8699:4): `fit` sizes the bar to its triggers, `fixed` stretches it across its container (override the stretched width with `--rel-menubar-width`). The default stays `fit`, which is how the bar has always rendered. Reflected. |
|
|
132
|
+
|
|
133
|
+
### Events
|
|
134
|
+
|
|
135
|
+
| Event | Detail | Bubbles / composed | Description |
|
|
136
|
+
| --- | --- | --- | --- |
|
|
137
|
+
| `rel-menubar-change` | `detail` | bubbles / composed | Fired when a `data-checkbox` item is toggled or a `data-radio` option is picked. Detail: `{ type: "checkbox", label, checked }` or `{ type: "radio", name, value }`. |
|
|
138
|
+
| `rel-menubar-select` | `{ label: item.textContent?.trim() ?? "", value: item.getAttribute("value") ?? undefined, shortcut: item.getAttribute("data-shortcut") ?? undefined, }` | bubbles / composed | Fired when a regular `data-item` (in a panel or a submenu) is activated by click, Enter/Space or a bound shortcut. Detail: `{ label: string, value?: string, shortcut?: string }` taken from the item's text, `value` and `data-shortcut` attributes. |
|
|
139
|
+
|
|
140
|
+
### Slots
|
|
141
|
+
|
|
142
|
+
| Slot | Description |
|
|
143
|
+
| --- | --- |
|
|
144
|
+
| — | This element has no slots. |
|
|
145
|
+
|
|
146
|
+
### CSS Parts
|
|
147
|
+
|
|
148
|
+
| Part | Description |
|
|
149
|
+
| --- | --- |
|
|
150
|
+
| — | None exposed. |
|
|
151
|
+
|
|
152
|
+
### `<rel-menubar-menu>`
|
|
153
|
+
|
|
154
|
+
#### Attributes / Properties
|
|
155
|
+
|
|
156
|
+
| Property | Attribute | Type | Default | Description |
|
|
157
|
+
| --- | --- | --- | --- | --- |
|
|
158
|
+
| `label` | `label` | `string` | `""` | Text of the trigger button the parent menubar renders for this menu. |
|
|
159
|
+
| `disabled` | `disabled` | `boolean` | `false` | Renders this menu's trigger in the Figma State=Disabled style and stops it opening by pointer, keyboard or hover-switch. The trigger stays in the tab order and reachable with the arrow keys (`aria-disabled`, not the native `disabled` attribute), matching how disabled `data-item` rows behave. Reflected. |
|
|
160
|
+
|
|
161
|
+
### Events
|
|
162
|
+
|
|
163
|
+
| Event | Detail | Bubbles / composed | Description |
|
|
164
|
+
| --- | --- | --- | --- |
|
|
165
|
+
| — | — | — | This element emits no custom events. |
|
|
166
|
+
|
|
167
|
+
### Slots
|
|
168
|
+
|
|
169
|
+
| Slot | Description |
|
|
170
|
+
| --- | --- |
|
|
171
|
+
| — | This element has no slots. |
|
|
172
|
+
|
|
173
|
+
### CSS Parts
|
|
174
|
+
|
|
175
|
+
| Part | Description |
|
|
176
|
+
| --- | --- |
|
|
177
|
+
| — | None exposed. |
|
|
178
|
+
|
|
179
|
+
<!-- readme-gen:end -->
|
|
180
|
+
|
|
181
|
+
## Accessibility
|
|
182
|
+
|
|
183
|
+
- Roles follow the APG menubar pattern: the strip is `role="menubar"`; each trigger is a `<button role="menuitem" aria-haspopup="menu" aria-expanded="true|false">`; each dropdown is a `role="menu"` panel with `tabindex="-1"`; rows are `role="menuitem"` (disabled ones add `aria-disabled="true"`), separators are `role="separator"`, and a submenu trigger row carries `aria-haspopup="menu"` / `aria-expanded` with its own nested `role="menu"` panel.
|
|
184
|
+
- Focus model: the triggers are real buttons in the tab order. When a menu opens from the keyboard, DOM focus moves to the panel itself and the highlighted row is exposed through `aria-activedescendant` (row ids are generated per menubar instance), so arrow keys move a virtual focus rather than DOM focus. `Escape` closes everything and returns DOM focus to the trigger that owned the menu.
|
|
185
|
+
- Checkbox items render as `rel-checkbox` and radio groups as `rel-radio` inside the panel, so those rows carry the native checkbox / radiogroup semantics of those components.
|
|
186
|
+
- There is no type-ahead and no wrap-around: arrow keys stop at the first / last trigger or row.
|
|
187
|
+
- Bound shortcuts (`bind-shortcuts`) listen on `document`, skip events whose `defaultPrevented` is already set, skip keystrokes that start in an input, textarea, select or contenteditable, and call `preventDefault()` on a match.
|
|
188
|
+
|
|
189
|
+
| Key | Where | Action |
|
|
190
|
+
| --- | --- | --- |
|
|
191
|
+
| `Tab` / `Shift+Tab` | anywhere | Native focus movement between the trigger buttons (and out of the menubar). |
|
|
192
|
+
| `ArrowRight` / `ArrowLeft` | trigger | Move focus to the next / previous trigger (does not open it). |
|
|
193
|
+
| `Enter` / `Space` / `ArrowDown` | trigger | Open that menu, focus the panel and highlight the first item. |
|
|
194
|
+
| `Enter` / `Space` / `ArrowDown` | trigger of a `disabled` menu | Nothing — the trigger is reachable but never opens. |
|
|
195
|
+
| `Escape` | trigger (menu open) | Close all menus and keep focus on the trigger. |
|
|
196
|
+
| `ArrowDown` / `ArrowUp` | open panel | Highlight the next / previous item (clamped, no wrap). |
|
|
197
|
+
| `Home` / `End` | open panel | Highlight the first / last item. |
|
|
198
|
+
| `ArrowRight` | open panel, on a submenu row | Open the submenu and highlight its first item. |
|
|
199
|
+
| `Enter` / `Space` | open panel | Activate the highlighted row: fire `rel-menubar-select` and close, toggle a checkbox, pick a radio option, or open a submenu. |
|
|
200
|
+
| `Escape` | open panel | Close all menus and return focus to the trigger. |
|
|
201
|
+
| `ArrowDown` / `ArrowUp` / `Home` / `End` | open submenu | Move the highlight inside the submenu. |
|
|
202
|
+
| `ArrowLeft` / `Escape` | open submenu | Close only the submenu; the parent panel stays open and focused. |
|
|
203
|
+
| `Enter` / `Space` | open submenu | Activate the highlighted sub-item: fire `rel-menubar-select`, close everything, refocus the trigger. |
|
|
204
|
+
|
|
205
|
+
## Notes / Edge Cases
|
|
206
|
+
|
|
207
|
+
- Both elements render into **light DOM** (`createRenderRoot()` returns the host): there are no slots, no `::part` hooks and no style encapsulation. The menubar injects its own `<style>` block; the `.menubar-trigger` / `.menubar-panel` / `.menu-item` class names are implementation details, not a styling contract.
|
|
208
|
+
- `<rel-menubar-menu>` is a hidden data holder (`display: none`) — it never renders its children itself. The menubar discovers menus once, when it connects; menus added or removed later are not picked up until the menubar is re-attached. Item lists inside a menu are re-read on each render, so item changes show up the next time a panel opens.
|
|
209
|
+
- Item content is flattened: a `data-item` row renders the button's `textContent` only, so markup inside the button (icons, `<rel-kbd>` chips) is not reproduced. Use `data-shortcut` for the shortcut hint — it is displayed verbatim, so `Mod+Z` is shown literally as "Mod+Z"; write `Ctrl+Z` if you want that text and only need Ctrl.
|
|
210
|
+
- Panels are `position: fixed` (z-index 1000, submenus 1001), placed flush under the trigger / against the right edge of the submenu row, and repositioned on scroll. They are not moved into the top layer, so a `rel-modal` or other top-layer surface can cover them.
|
|
211
|
+
- Panels are a fixed 300px wide, matching the Figma Menu component the bar is paired with; override with `--rel-menubar-panel-width`. There is no viewport clamping, so a panel opened near the right edge can overflow.
|
|
212
|
+
- The bar follows Figma's Menu Bar geometry: 40px tall triggers (`spacing/xs` × `spacing/md` padding on a `body/lg` label) on a `bg/fill/neutral/default` surface with `border-radius/sm` and no border. The trigger whose menu is open takes the hover overlay plus a medium-weight label, which widens it by a pixel or two — that weight shift is in the Figma spec, not an artefact.
|
|
213
|
+
- A `disabled` `<rel-menubar-menu>` renders its trigger in the disabled tone and refuses to open by click, hover-switch, Enter/Space/ArrowDown or a bound shortcut. It uses `aria-disabled`, not the native `disabled` attribute, so the trigger stays in the tab order — the same model the disabled `data-item` rows use.
|
|
214
|
+
- With a menu already open the bar is in hover-switch mode: moving the pointer onto another trigger opens that menu, so a *click* on it arrives when it is already open and toggles it shut. Switch menus by hovering, or click twice.
|
|
215
|
+
- Hover-switching between menus is armed only after a click has opened one; hovering triggers on a closed menubar does nothing. Clicking outside (checked via `composedPath`, so it works inside shadow roots) closes everything.
|
|
216
|
+
- Disabled `data-item` buttons ignore pointer clicks and bound shortcuts but are still reachable with the arrow keys.
|
|
217
|
+
- `rel-menubar-select` fires only for regular `data-item` rows (panel or submenu); checkbox and radio rows fire `rel-menubar-change` instead. Both events bubble and are composed.
|
|
218
|
+
- Only one submenu can be open at a time, and submenus nest one level deep (a `data-submenu` inside a `data-submenu` is not rendered).
|
|
@@ -52,12 +52,8 @@ A one-time-password input implementing the Figma "OTP Input" cell: one single-ch
|
|
|
52
52
|
});
|
|
53
53
|
</script>
|
|
54
54
|
|
|
55
|
-
<!-- No separators
|
|
56
|
-
|
|
57
|
-
<rel-otp id="otp-plain" length="6"></rel-otp>
|
|
58
|
-
<script>
|
|
59
|
-
document.getElementById('otp-plain').showSeparator = false;
|
|
60
|
-
</script>
|
|
55
|
+
<!-- No separators ("false" switches a default-true boolean off, since 1.5.0) -->
|
|
56
|
+
<rel-otp length="6" show-separator="false"></rel-otp>
|
|
61
57
|
|
|
62
58
|
<!-- States -->
|
|
63
59
|
<rel-otp value="123456" readonly></rel-otp>
|
|
@@ -102,7 +98,7 @@ A one-time-password input implementing the Figma "OTP Input" cell: one single-ch
|
|
|
102
98
|
| `pattern` | `pattern` | `string \| null` | `null` | Native `pattern` attribute forwarded to every cell (per-character regex for form validation). |
|
|
103
99
|
| `ariaLabel` | `aria-label` | `string \| null` | `null` | Accessible name of the whole group; defaults to "One-time password input". Each cell is additionally labelled "Digit/Character N of M". |
|
|
104
100
|
| `invalid` | `invalid` | `boolean` | `false` | Error styling (danger border on every cell) and `aria-invalid="true"`. Reflected. |
|
|
105
|
-
| `showSeparator` | `show-separator` | `boolean` | `true` | Renders the dash separator between groups.
|
|
101
|
+
| `showSeparator` | `show-separator` | `boolean` | `true` | Renders the dash separator between groups. Presence or `""` means true; `show-separator="false"` (or the property set to `false`) hides them. Even when on, separators appear only if `length` divides evenly by `group-size`; see `groupSize`. |
|
|
106
102
|
| `size` | `size` | `"small" \| "medium" \| "large" \| "sm" \| "md" \| "lg"` | `"medium"` | Cell size on the shared field scale (square, border-box): `small` 32px, `medium` 40px, `large` 48px (the Figma OTP Input cell). `sm` / `md` / `lg` are deprecated aliases of the full words. Reflected. |
|
|
107
103
|
|
|
108
104
|
#### Deprecated (still supported — removal targeted for 3.0)
|
|
@@ -163,7 +159,7 @@ A one-time-password input implementing the Figma "OTP Input" cell: one single-ch
|
|
|
163
159
|
- `rel-change` fires on every edit; `rel-complete` fires right after it when all cells hold a valid character for the current mode — validate the code server-side, the component only checks the character set.
|
|
164
160
|
- `input-mode` unset (`""`) falls back to the deprecated `type` prop (default `"numeric"`); set `input-mode` explicitly and it always wins.
|
|
165
161
|
- The lowercase `inputmode` prop is the raw native keyboard hint, not the accepted character set — normally leave it unset.
|
|
166
|
-
- `show-separator
|
|
162
|
+
- `show-separator="false"` hides the separators (since 1.5.0; before that any value enabled them), as do `el.showSeparator = false` and `group-size="0"`.
|
|
167
163
|
- **Separators require an even split.** Grouping applies only when `length` divides evenly by `group-size`; otherwise the cells render as one ungrouped row. A separator marks a boundary between two equal groups, so a length that does not divide would put one before a lone trailing cell — `length="4"` with the default `group-size="3"` used to draw `3 – 1`. Affected lengths at the default group size are 4, 5, 7 and 8; each now renders ungrouped. To group a length of 4, set `group-size="2"` for `2 – 2`.
|
|
168
164
|
- `required` is applied to the first cell only, so native form validation blocks submission when the code is empty but not when it is partial — check `e.detail.complete` yourself.
|
|
169
165
|
- The `autocomplete` prop currently has no effect: every cell always renders `autocomplete="one-time-code"`.
|