@reliability-design/web 1.4.6 → 1.4.8

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 (148) hide show
  1. package/CHANGELOG.md +246 -49
  2. package/dist/components/rel-accordion/README.md +133 -132
  3. package/dist/components/rel-alert/README.md +145 -141
  4. package/dist/components/rel-autocomplete/README.md +172 -61
  5. package/dist/components/rel-avatar/README.md +118 -130
  6. package/dist/components/rel-badge/README.md +97 -93
  7. package/dist/components/rel-breadcrumb/README.md +96 -102
  8. package/dist/components/rel-button/README.md +190 -130
  9. package/dist/components/rel-calendar/README.md +109 -109
  10. package/dist/components/rel-card/README.md +75 -94
  11. package/dist/components/rel-cardmedia/README.md +100 -127
  12. package/dist/components/rel-carousel/README.md +186 -166
  13. package/dist/components/rel-checkbox/README.md +96 -75
  14. package/dist/components/rel-chip/README.md +138 -121
  15. package/dist/components/rel-container/README.md +107 -99
  16. package/dist/components/rel-date-picker/README.md +200 -289
  17. package/dist/components/rel-divider/README.md +88 -100
  18. package/dist/components/rel-drawer/README.md +187 -200
  19. package/dist/components/rel-kbd/README.md +141 -151
  20. package/dist/components/rel-layout/README.md +34 -0
  21. package/dist/components/rel-menu/README.md +180 -151
  22. package/dist/components/rel-menubar/README.md +218 -329
  23. package/dist/components/rel-otp/README.md +171 -440
  24. package/dist/components/rel-pagination/README.md +100 -424
  25. package/dist/components/rel-popover/README.md +203 -568
  26. package/dist/components/rel-progress/README.md +88 -110
  27. package/dist/components/rel-radio/README.md +131 -138
  28. package/dist/components/rel-range-picker/README.md +165 -0
  29. package/dist/components/rel-resizable/README.md +199 -140
  30. package/dist/components/rel-segmented-button/README.md +156 -141
  31. package/dist/components/rel-select/README.md +156 -93
  32. package/dist/components/rel-sheet/README.md +168 -224
  33. package/dist/components/rel-skeleton/README.md +104 -0
  34. package/dist/components/rel-slider/README.md +131 -135
  35. package/dist/components/rel-snackbar/README.md +168 -134
  36. package/dist/components/rel-stepper/README.md +162 -160
  37. package/dist/components/rel-switch/README.md +117 -102
  38. package/dist/components/rel-tab/README.md +173 -163
  39. package/dist/components/rel-textfield/README.md +176 -69
  40. package/dist/components/rel-time-picker/README.md +122 -157
  41. package/dist/components/rel-toast/README.md +165 -148
  42. package/dist/components/rel-tooltip/README.md +119 -111
  43. package/dist/components/rel-upload/README.md +220 -0
  44. package/dist/index.d.mts +1726 -447
  45. package/dist/index.d.ts +1726 -447
  46. package/dist/index.js +13715 -10870
  47. package/dist/index.js.map +1 -1
  48. package/dist/index.mjs +14018 -11163
  49. package/dist/index.mjs.map +1 -1
  50. package/dist/styles/focus-ring.ts +84 -0
  51. package/dist/styles/vars.css +892 -866
  52. package/dist/{vars-XNMTSRVN.css → vars-DXX6QOAG.css} +892 -866
  53. package/package.json +3 -2
  54. package/dist/components/rel-accordion/rel-accordion.ts +0 -336
  55. package/dist/components/rel-alert/rel-alert.ts +0 -305
  56. package/dist/components/rel-autocomplete/rel-autocomplete.ts +0 -720
  57. package/dist/components/rel-avatar/rel-avatar.ts +0 -142
  58. package/dist/components/rel-badge/rel-badge.ts +0 -175
  59. package/dist/components/rel-breadcrumb/rel-breadcrumb.ts +0 -106
  60. package/dist/components/rel-button/rel-button.ts +0 -560
  61. package/dist/components/rel-calendar/index.ts +0 -1
  62. package/dist/components/rel-calendar/rel-calendar.ts +0 -565
  63. package/dist/components/rel-card/rel-card.ts +0 -88
  64. package/dist/components/rel-cardmedia/rel-cardmedia.ts +0 -88
  65. package/dist/components/rel-carousel/index.ts +0 -1
  66. package/dist/components/rel-carousel/rel-carousel.ts +0 -723
  67. package/dist/components/rel-chart/README.md +0 -286
  68. package/dist/components/rel-chart/index.ts +0 -1
  69. package/dist/components/rel-chart/rel-chart.ts +0 -754
  70. package/dist/components/rel-checkbox/rel-checkbox.ts +0 -382
  71. package/dist/components/rel-chip/rel-chip.ts +0 -483
  72. package/dist/components/rel-container/rel-container.ts +0 -95
  73. package/dist/components/rel-date-picker/date-format.test.ts +0 -43
  74. package/dist/components/rel-date-picker/date-format.ts +0 -47
  75. package/dist/components/rel-date-picker/date-parse.test.ts +0 -116
  76. package/dist/components/rel-date-picker/date-parse.ts +0 -178
  77. package/dist/components/rel-date-picker/index.ts +0 -1
  78. package/dist/components/rel-date-picker/rel-date-picker.ts +0 -1676
  79. package/dist/components/rel-divider/rel-divider.ts +0 -54
  80. package/dist/components/rel-drawer/index.ts +0 -1
  81. package/dist/components/rel-drawer/rel-drawer-footer.ts +0 -27
  82. package/dist/components/rel-drawer/rel-drawer.ts +0 -607
  83. package/dist/components/rel-kbd/rel-kbd.ts +0 -188
  84. package/dist/components/rel-layout/rel-layout.ts +0 -49
  85. package/dist/components/rel-menu/rel-menu.ts +0 -541
  86. package/dist/components/rel-menubar/index.ts +0 -2
  87. package/dist/components/rel-menubar/rel-menubar-menu.css +0 -3
  88. package/dist/components/rel-menubar/rel-menubar-menu.ts +0 -17
  89. package/dist/components/rel-menubar/rel-menubar.ts +0 -777
  90. package/dist/components/rel-nav-menu/README.md +0 -289
  91. package/dist/components/rel-nav-menu/index.ts +0 -3
  92. package/dist/components/rel-nav-menu/rel-nav-menu-item.ts +0 -243
  93. package/dist/components/rel-nav-menu/rel-nav-menu-panel.ts +0 -72
  94. package/dist/components/rel-nav-menu/rel-nav-menu.ts +0 -270
  95. package/dist/components/rel-navbar/README.md +0 -126
  96. package/dist/components/rel-navbar/rel-navbar.ts +0 -105
  97. package/dist/components/rel-otp/index.ts +0 -1
  98. package/dist/components/rel-otp/rel-otp.ts +0 -527
  99. package/dist/components/rel-pagination/index.ts +0 -1
  100. package/dist/components/rel-pagination/rel-pagination.ts +0 -522
  101. package/dist/components/rel-popover/index.ts +0 -2
  102. package/dist/components/rel-popover/rel-popover-content.ts +0 -81
  103. package/dist/components/rel-popover/rel-popover.ts +0 -475
  104. package/dist/components/rel-progress/rel-progress.ts +0 -234
  105. package/dist/components/rel-radio/rel-radio.ts +0 -481
  106. package/dist/components/rel-range-picker/rel-range-picker.ts +0 -1688
  107. package/dist/components/rel-resizable/index.ts +0 -3
  108. package/dist/components/rel-resizable/rel-resizable-group.ts +0 -405
  109. package/dist/components/rel-resizable/rel-resizable-handle.ts +0 -150
  110. package/dist/components/rel-resizable/rel-resizable-panel.ts +0 -58
  111. package/dist/components/rel-segmented-button/rel-segmented-button.ts +0 -217
  112. package/dist/components/rel-select/rel-select.ts +0 -1416
  113. package/dist/components/rel-sheet/rel-sheet.ts +0 -610
  114. package/dist/components/rel-sidebar/README.md +0 -209
  115. package/dist/components/rel-sidebar/rel-sidebar.ts +0 -1191
  116. package/dist/components/rel-skeleton/rel-skeleton.ts +0 -143
  117. package/dist/components/rel-slider/rel-slider.ts +0 -673
  118. package/dist/components/rel-snackbar/rel-snackbar.ts +0 -329
  119. package/dist/components/rel-stepper/rel-stepper.ts +0 -567
  120. package/dist/components/rel-switch/rel-switch.ts +0 -199
  121. package/dist/components/rel-tab/rel-tab.ts +0 -264
  122. package/dist/components/rel-table/README.md +0 -178
  123. package/dist/components/rel-table/rel-table-body.ts +0 -22
  124. package/dist/components/rel-table/rel-table-caption.ts +0 -30
  125. package/dist/components/rel-table/rel-table-cell.ts +0 -43
  126. package/dist/components/rel-table/rel-table-footer.ts +0 -24
  127. package/dist/components/rel-table/rel-table-head.ts +0 -47
  128. package/dist/components/rel-table/rel-table-header.ts +0 -22
  129. package/dist/components/rel-table/rel-table-row.ts +0 -37
  130. package/dist/components/rel-table/rel-table.ts +0 -62
  131. package/dist/components/rel-textfield/rel-textfield.ts +0 -1054
  132. package/dist/components/rel-time-picker/rel-time-picker.ts +0 -1482
  133. package/dist/components/rel-time-picker/time-parse.test.ts +0 -67
  134. package/dist/components/rel-time-picker/time-parse.ts +0 -85
  135. package/dist/components/rel-toast/rel-toast.ts +0 -206
  136. package/dist/components/rel-tooltip/rel-tooltip.ts +0 -193
  137. package/dist/components/rel-topbar/rel-topbar-actions.ts +0 -27
  138. package/dist/components/rel-topbar/rel-topbar-brand.ts +0 -54
  139. package/dist/components/rel-topbar/rel-topbar-dropdown-item.ts +0 -69
  140. package/dist/components/rel-topbar/rel-topbar-dropdown.ts +0 -31
  141. package/dist/components/rel-topbar/rel-topbar-item.ts +0 -324
  142. package/dist/components/rel-topbar/rel-topbar-mobile-panel.ts +0 -27
  143. package/dist/components/rel-topbar/rel-topbar-profile.ts +0 -243
  144. package/dist/components/rel-topbar/rel-topbar-utilities.ts +0 -27
  145. package/dist/components/rel-topbar/rel-topbar.ts +0 -446
  146. package/dist/components/rel-upload/UPLOAD-DESIGN-CHANGES.md +0 -134
  147. package/dist/components/rel-upload/rel-upload-actions.ts +0 -163
  148. package/dist/components/rel-upload/rel-upload.ts +0 -968
@@ -0,0 +1,220 @@
1
+ # rel-upload
2
+
3
+ File upload: `<rel-upload>` renders the dropzone, the format / size hint, the status alert and the per-file list (Figma "↳ File Upload" — `Upload Dropzone`, `Upload File Row`, `Upload Modal`), and `<rel-upload-actions>` renders the Cancel / Upload footer as a separate element so it can sit in `<rel-modal>`'s `footer` slot and stay pinned while the file list scrolls. Use it wherever users attach one or more files and need per-file progress, success and failure states; the pair runs either **controlled** (the component picks and validates, your app transfers) or **self-driving** (pass an `uploader` and the component runs the whole batch). For a plain native file field without a list, use `<input type="file">` directly.
4
+
5
+ ## Usage
6
+
7
+ ```html
8
+ <!-- Controlled mode: pick + validate here, transfer in your app -->
9
+ <rel-upload
10
+ id="docs"
11
+ accept=".pdf,.png,.jpg"
12
+ max-size="10MB"
13
+ multiple>
14
+ </rel-upload>
15
+ <rel-upload-actions for="docs"></rel-upload-actions>
16
+ <script>
17
+ const upload = document.getElementById('docs');
18
+
19
+ upload.addEventListener('rel-upload', async (e) => {
20
+ for (const entry of e.detail.files) {
21
+ // Write progress / result back onto `files` — the rows re-render from it.
22
+ const patch = (partial) => {
23
+ upload.files = upload.files.map((f) => (f.id === entry.id ? { ...f, ...partial } : f));
24
+ };
25
+ patch({ status: 'uploading', progress: 0, loaded: 0 });
26
+ try {
27
+ await sendToServer(entry.file, (loaded) =>
28
+ patch({ loaded, progress: Math.round((loaded / entry.size) * 100) }));
29
+ patch({ status: 'success', progress: 100, loaded: entry.size });
30
+ } catch (err) {
31
+ patch({ status: 'error', error: err.message });
32
+ }
33
+ }
34
+ });
35
+ </script>
36
+
37
+ <!-- Self-driving mode: hand over an uploader and listen for the outcome -->
38
+ <rel-upload id="photos" accept="image/*" max-size="5MB" multiple concurrency="2"></rel-upload>
39
+ <rel-upload-actions for="photos"></rel-upload-actions>
40
+ <script>
41
+ const photos = document.getElementById('photos');
42
+ photos.uploader = async (file, { signal, onProgress }) => {
43
+ // Any transport works; honour `signal` so Cancel aborts, call `onProgress(loaded, total)` for the bar.
44
+ await putWithProgress('/api/photos', file, { signal, onProgress });
45
+ };
46
+ photos.addEventListener('rel-complete', (e) => {
47
+ console.log('done', e.detail.succeeded.length, 'failed', e.detail.failed.length);
48
+ });
49
+ </script>
50
+
51
+ <!-- Inside rel-modal: body scrolls, footer stays pinned; the actions find the sibling upload automatically -->
52
+ <rel-modal open side="center" title="Upload documents">
53
+ <rel-upload accept=".pdf" max-size="10MB" multiple></rel-upload>
54
+ <rel-upload-actions slot="footer"></rel-upload-actions>
55
+ </rel-modal>
56
+
57
+ <!-- Single file, custom copy, forward-only footer -->
58
+ <rel-upload
59
+ id="avatar"
60
+ accept=".png,.jpg"
61
+ max-size="2MB"
62
+ dropzone-title="Drop your avatar here"
63
+ browse-label="Choose image"
64
+ hint="PNG or JPG, square, up to 2 MB">
65
+ </rel-upload>
66
+ <rel-upload-actions for="avatar" no-cancel></rel-upload-actions>
67
+
68
+ <!-- Disabled -->
69
+ <rel-upload disabled></rel-upload>
70
+ ```
71
+
72
+ <!-- readme-gen:start -->
73
+
74
+ <!-- Generated from rel-upload.ts, rel-upload-actions.ts — do not edit; run readme-generate.mjs -->
75
+
76
+ ## Elements
77
+
78
+ | Element | Class | Source |
79
+ | --- | --- | --- |
80
+ | `<rel-upload>` | `RelUpload` | `rel-upload.ts` |
81
+ | `<rel-upload-actions>` | `RelUploadActions` | `rel-upload-actions.ts` |
82
+
83
+ ## API
84
+
85
+ ### `<rel-upload>`
86
+
87
+ #### Attributes / Properties
88
+
89
+ | Property | Attribute | Type | Default | Description |
90
+ | --- | --- | --- | --- | --- |
91
+ | `accept` | `accept` | `string` | `""` | Comma-separated accept list, e.g. ".pdf,.png,.jpg" or "image/*". |
92
+ | `maxSize` | `max-size` | `string` | `""` | Max size per file as a human string ("10MB", "500kb"). Empty → no limit. |
93
+ | `multiple` | `multiple` | `boolean` | `false` | Allow more than one file. |
94
+ | `disabled` | `disabled` | `boolean` | `false` | Disables the whole control. Reflected. |
95
+ | `hint` | `hint` | `string` | `""` | Overrides the derived "PDF, PNG or JPG - up to 10 MB per file" hint. |
96
+ | `dropzoneTitle` | `dropzone-title` | `string` | `"Drag and drop your files here"` | Dropzone headline. |
97
+ | `browseLabel` | `browse-label` | `string` | `"Browse files"` | Browse button label. |
98
+ | `files` | — | `UploadFile[]` | `[]` | The file list. Set it to drive the component in controlled mode; the component also maintains it itself as files are added and removed. Property only. |
99
+ | `uploader` | — | `(file: File, ctx: UploaderContext) => Promise<void>` | `undefined` | Supply an `Uploader` `(file, { file, signal, onProgress }) => Promise<void>` to switch to self-driving mode: the component runs the batch itself with `concurrency`, progress, the delay alert, abort and retry. Unset (default) the component only emits intents. Property only. |
100
+ | `concurrency` | `concurrency` | `number` | `3` | Max files transferred at once in self-driving mode. |
101
+ | `delayThreshold` | `delay-threshold` | `number` | `15000` | ms of continuous uploading before the "taking longer than usual" alert. |
102
+
103
+ #### Public fields / accessors
104
+
105
+ | Name | Type | Description |
106
+ | --- | --- | --- |
107
+ | `phase` | `UploadPhase` | Aggregate state. Read-only; corresponds to the Figma modal variants. `failed` means there is nothing left to send AND something errored — while any row is still `ready` the primary action is Upload, not Retry. |
108
+ | `canUpload` | `boolean` | Whether the primary action is enabled: not disabled, nothing in flight, and either `ready` rows exist or (in the failed phase) at least one failed row can be retried. |
109
+ | `primaryLabel` | `string` | Footer button label — "Upload", "Upload 3 files" or "Retry upload". |
110
+ | `state` | `UploadState` | The snapshot `<rel-upload-actions>` renders from. |
111
+
112
+ #### Methods
113
+
114
+ | Method | Signature | Description |
115
+ | --- | --- | --- |
116
+ | `upload` | `upload()` | Start (or resume) the batch. Emits `rel-upload`; runs it when `uploader` is set. |
117
+ | `retry` | `retry()` | Re-queue every retryable failed row and send again. |
118
+ | `cancel` | `cancel()` | Abort anything in flight and emit `rel-cancel`. Does not clear the list. |
119
+ | `removeFile` | `removeFile(id: string)` | Remove one row by id. |
120
+ | `reset` | `reset()` | Clear everything and abort in-flight transfers. |
121
+
122
+ ### Events
123
+
124
+ | Event | Detail | Bubbles / composed | Description |
125
+ | --- | --- | --- | --- |
126
+ | `rel-cancel` | — | bubbles / composed | Fired when Cancel is pressed (or `cancel()` is called); in-flight transfers are aborted, the list is kept. No detail. |
127
+ | `rel-change` | `{ files: this.files }` | bubbles / composed | Fired whenever the file list changes (files added, a row removed, `reset()`). Detail: `{ files: UploadFile[] }`. |
128
+ | `rel-complete` | `{ succeeded: this.files.filter((f) => f.status === "success"), failed: this.failedFiles, }` | bubbles / composed | Fired when the whole self-driving batch settles without being cancelled. Detail: `{ succeeded: UploadFile[], failed: UploadFile[] }`. |
129
+ | `rel-file-complete` | `{ id: item.id, file: item.file }` | bubbles / composed | Fired when one file finishes uploading in self-driving mode. Detail: `{ id: string, file: File }`. |
130
+ | `rel-file-error` | `{ id: item.id, file: item.file, error: message }` | bubbles / composed | Fired when one file's `uploader` rejects in self-driving mode. Detail: `{ id: string, file: File, error: string }`. |
131
+ | `rel-files-added` | `{ added, rejected }` | bubbles / composed | Fired after files are picked or dropped and validated. Detail: `{ added: UploadFile[], rejected: UploadRejection[] }`. |
132
+ | `rel-invalid` | `{ rejected }` | bubbles / composed | Fired just before `rel-files-added` when at least one picked file fails `accept` / `max-size`. Detail: `{ rejected: UploadRejection[] }`. |
133
+ | `rel-progress` | `{ id: item.id, progress, loaded, total: t }` | bubbles / composed | Fired on every progress tick in self-driving mode. Detail: `{ id: string, progress: number (0–100), loaded: number, total: number }` (bytes). |
134
+ | `rel-remove` | `{ id, file: target?.file }` | bubbles / composed | Fired when a row is removed via its delete button or `removeFile()`. Detail: `{ id: string, file?: File }`. |
135
+ | `rel-retry` | `{ files: requeued }` | bubbles / composed | Fired when Retry re-queues the retryable failed rows. Detail: `{ files: UploadFile[] }`. |
136
+ | `rel-state` | `s / this.state` | bubbles / composed | Fired when the derived `state` snapshot changes (and once after connecting). Detail: `UploadState`. |
137
+ | `rel-upload` | `{ files: targets }` | bubbles / composed | Fired when Upload is pressed (or `upload()` is called) with the rows still `ready`; in controlled mode the app performs the transfer. Detail: `{ files: UploadFile[] }`. |
138
+
139
+ ### Slots
140
+
141
+ | Slot | Description |
142
+ | --- | --- |
143
+ | — | This element has no slots. |
144
+
145
+ ### CSS Parts
146
+
147
+ | Part | Description |
148
+ | --- | --- |
149
+ | `browse` | The `rel-button` that opens the file picker (Figma "Browse Button": Size=Medium, Style=Secondary). |
150
+
151
+ ### Tokens / CSS Variables
152
+
153
+ #### Component hooks
154
+
155
+ Set these on the host (or an ancestor) to customise the element without touching design tokens.
156
+
157
+ | Custom property | Default | Description |
158
+ | --- | --- | --- |
159
+ | `--rel-upload-browse-hover-bg` | `transparent` | Background of the Browse button on hover. Defaults to `transparent`, which is what the underlying `rel-button` secondary style draws (Figma's outline button reacts on its border, not its fill), so this only takes effect when you set it. |
160
+
161
+ ### `<rel-upload-actions>`
162
+
163
+ #### Attributes / Properties
164
+
165
+ | Property | Attribute | Type | Default | Description |
166
+ | --- | --- | --- | --- | --- |
167
+ | `for` | `for` | `string` | `""` | Id of the `<rel-upload>` to control. Omit to auto-discover a sibling. |
168
+ | `cancelLabel` | `cancel-label` | `string` | `"Cancel"` | Cancel button label. |
169
+ | `noCancel` | `no-cancel` | `boolean` | `false` | Hides Cancel for flows that only ever move forward. |
170
+
171
+ ### Events
172
+
173
+ | Event | Detail | Bubbles / composed | Description |
174
+ | --- | --- | --- | --- |
175
+ | `rel-cancel` | — | bubbles / composed | Fired when the Cancel button is pressed, after `cancel()` has been called on the target `<rel-upload>`. No detail. |
176
+
177
+ ### Slots
178
+
179
+ | Slot | Description |
180
+ | --- | --- |
181
+ | — | This element has no slots. |
182
+
183
+ ### CSS Parts
184
+
185
+ | Part | Description |
186
+ | --- | --- |
187
+ | `base` | The right-aligned flex row holding the buttons |
188
+ | `cancel` | The secondary Cancel `rel-button` (absent when `no-cancel` is set) |
189
+ | `primary` | The primary Upload / Retry `rel-button` |
190
+
191
+ <!-- readme-gen:end -->
192
+
193
+ ## Accessibility
194
+
195
+ - The dropzone itself is a mouse / drag-and-drop affordance only — it has no role and is not focusable, so no interactive control is nested inside another. The **Browse** `<button type="button">` is the only focusable control in the dropzone; it opens the hidden native `<input type="file">` (which carries `accept` and `multiple`).
196
+ - The file rows container is `role="list"`. Each row shows a status icon with `role="img"` and an accessible name ("Uploaded" / "Failed") and a remove button labelled "Remove {file name}"; remove buttons are disabled while `disabled` is set.
197
+ - Browse and remove buttons show a 2px `--rel-border-focus` focus ring on `:focus-visible`.
198
+ - In `<rel-upload-actions>` the primary button is `disabled` whenever `canUpload` is false and, while a batch is in flight, shows `rel-button`'s `loading` state with `aria-label="Uploading"`.
199
+ - Screen-reader announcements go through the shared `announce()` live region: "N file(s) added" (polite) and "N file(s) rejected" (assertive) after a pick or drop; the list heading whenever it changes — "Selected files (3)", "Uploading 3 files" (polite) or "3 files - 2 failed" (assertive); the alert text ("This is taking longer than usual…" / "We couldn't upload…") assertively when it appears; "{name} removed" after `removeFile()`; "File list cleared" after `reset()`.
200
+ - Status colours are backed by the tokens `--rel-text-caption`, `--rel-text-success` and `--rel-text-danger` rather than the Figma hexes (see `UPLOAD-DESIGN-CHANGES.md` for the contrast rationale).
201
+
202
+ | Key | Action |
203
+ | --- | --- |
204
+ | `Tab` / `Shift+Tab` | Move between Browse, each row's Remove button and the footer buttons (all native buttons) |
205
+ | `Enter` / `Space` on Browse | Open the native file picker |
206
+ | `Enter` / `Space` on Remove | Remove that row (`rel-remove` + `rel-change`) |
207
+ | `Enter` / `Space` on Upload / Retry / Cancel | Call `upload()` / `retry()` / `cancel()` on the target `<rel-upload>` |
208
+
209
+ ## Notes / Edge Cases
210
+
211
+ - **Modes.** Without `uploader` the component only emits intents (`rel-upload`, `rel-retry`, `rel-cancel`, `rel-remove`) and your app must write `status` / `progress` / `loaded` / `error` back onto `files` (replace the array — rows are keyed by `id`). With `uploader` the component patches `files` itself, runs up to `concurrency` transfers at once, emits `rel-progress` / `rel-file-complete` / `rel-file-error` per file and `rel-complete` when the batch settles.
212
+ - **Validation happens on pick.** Files failing `accept` or `max-size` are kept as `error` rows ("unsupported file type" / "file exceeds the 10MB limit") — that is how the Figma Failure state surfaces them — and are reported in `rel-invalid` and `rel-files-added.rejected`. `retry()` skips rows that would fail validation again (and rows without a `file` object); if every failed row is unretryable the Retry button stays disabled — remove the row instead.
213
+ - **`accept` tokens** are `.ext` (name suffix), `type/*` (MIME prefix) or an exact MIME type; the hint line is derived from them ("PDF, PNG or JPG") plus `max-size` ("- up to 10MB per file"). `max-size` accepts `b` / `kb` / `mb` / `gb` (decimal: 1MB = 1,000,000 bytes); an unparseable string means no limit.
214
+ - **Same-named files replace** the existing entry instead of duplicating it. Without `multiple` only the first picked file is taken and it replaces the whole list.
215
+ - **Phase.** `phase` is `failed` only when nothing is left to send AND something errored; while any row is still `ready` the primary action remains "Upload N files". `delayed` is entered after `delay-threshold` ms of continuous uploading (`0` disables the "taking longer than usual" alert) and shows a subtle warning `rel-alert`; failures show a subtle destructive `rel-alert`.
216
+ - **Cancel** aborts the `AbortSignal` handed to `uploader` and returns in-flight rows to `ready`; it keeps the list. `reset()` aborts and clears everything.
217
+ - **`rel-state` is de-duplicated** — it only fires when the derived snapshot (`phase`, counts, `canUpload`, `primaryLabel`) changes, so progress ticks do not spam `<rel-upload-actions>`; it also fires once right after connecting.
218
+ - **Pairing.** `<rel-upload-actions>` resolves its target by `for` (an id in the same DOM root), else the first `<rel-upload>` in its parent element, else the first in its root. It listens for `rel-state` on its root node, so both elements must share the same document or shadow root — slotting the actions into `<rel-modal>`'s footer is fine because slotting does not change the root.
219
+ - **Figma deviations.** The Browse button (not the whole dropzone) is the focusable control; the drag-over frame (solid brand border + 8% brand tint) and the disabled state (60% opacity) have no Figma frame; three colours were moved to the correct token layer for contrast. Details in `UPLOAD-DESIGN-CHANGES.md`.
220
+ - The Browse hover background reads `--rel-upload-browse-hover-bg` and falls back to `--rel-action-neutral-outline-hover`, then `--rel-bg-surface-elevated`.