@elabs-ai/components-ui 4.1.0 → 4.2.0
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/dist/{chunk-JKPPTL63.js → chunk-FCH4ZN6G.js} +12 -3
- package/dist/chunk-FCH4ZN6G.js.map +1 -0
- package/dist/form.js +3 -3
- package/dist/form.js.map +1 -1
- package/dist/index.d.ts +126 -35
- package/dist/index.js +3346 -2079
- package/dist/index.js.map +1 -1
- package/dist/lib/cn.js +10 -1
- package/dist/lib/cn.js.map +1 -1
- package/package.json +3 -3
- package/src/__contract__/attribution-panel.contract.test.tsx +49 -0
- package/src/__contract__/badge.contract.test.tsx +49 -0
- package/src/__contract__/bounded-number.contract.test.tsx +49 -0
- package/src/__contract__/button.contract.test.tsx +49 -0
- package/src/__contract__/color-picker.contract.test.tsx +49 -0
- package/src/__contract__/combobox.contract.test.tsx +49 -0
- package/src/__contract__/command-trigger.contract.test.tsx +49 -0
- package/src/__contract__/copyable-value.contract.test.tsx +49 -0
- package/src/__contract__/date-picker.contract.test.tsx +49 -0
- package/src/__contract__/date-range-picker.contract.test.tsx +49 -0
- package/src/__contract__/empty-state.contract.test.tsx +49 -0
- package/src/__contract__/error-state.contract.test.tsx +49 -0
- package/src/__contract__/icon-button.contract.test.tsx +49 -0
- package/src/__contract__/input.contract.test.tsx +49 -0
- package/src/__contract__/key-value-editor.contract.test.tsx +49 -0
- package/src/__contract__/list-editor.contract.test.tsx +49 -0
- package/src/__contract__/loading-state.contract.test.tsx +49 -0
- package/src/__contract__/metric-card.contract.test.tsx +49 -0
- package/src/__contract__/model-picker.contract.test.tsx +49 -0
- package/src/__contract__/nav-notifications.contract.test.tsx +49 -0
- package/src/__contract__/number-input.contract.test.tsx +49 -0
- package/src/__contract__/rating.contract.test.tsx +49 -0
- package/src/__contract__/schema-form.contract.test.tsx +49 -0
- package/src/__contract__/section-header.contract.test.tsx +49 -0
- package/src/__contract__/segmented-field.contract.test.tsx +49 -0
- package/src/__contract__/slider-number.contract.test.tsx +49 -0
- package/src/__contract__/status-badge.contract.test.tsx +49 -0
- package/src/__contract__/tag-input.contract.test.tsx +49 -0
- package/src/__contract__/text.contract.test.tsx +49 -0
- package/src/__contract__/textarea.contract.test.tsx +49 -0
- package/src/__contract__/timeline.contract.test.tsx +49 -0
- package/src/__contract__/toggle.contract.test.tsx +49 -0
- package/src/__contract__/transfer.contract.test.tsx +49 -0
- package/src/__contract__/tree-select.contract.test.tsx +49 -0
- package/src/__contract__/tree.contract.test.tsx +49 -0
- package/src/__contract__/virtual-select.contract.test.tsx +49 -0
- package/src/__contract__/workspace-picker.contract.test.tsx +49 -0
- package/src/components/accordion/accordion.tsx +13 -4
- package/src/components/alert/alert.tsx +11 -3
- package/src/components/alert-dialog/alert-dialog.test.tsx +32 -0
- package/src/components/alert-dialog/alert-dialog.tsx +28 -6
- package/src/components/app-shell/app-shell.test.tsx +99 -0
- package/src/components/app-shell/app-shell.tsx +29 -10
- package/src/components/attribution-panel/attribution-panel.tsx +2 -0
- package/src/components/attribution-panel/attributions.generated.ts +2 -2
- package/src/components/avatar/avatar.tsx +4 -1
- package/src/components/badge/badge.test.tsx +8 -0
- package/src/components/badge/badge.tsx +14 -4
- package/src/components/bento-grid/bento-grid.test.tsx +23 -2
- package/src/components/bento-grid/bento-grid.tsx +10 -0
- package/src/components/bounded-number/bounded-number.test.tsx +1 -1
- package/src/components/bounded-number/bounded-number.tsx +78 -75
- package/src/components/breadcrumb/breadcrumb.tsx +3 -2
- package/src/components/button/button.tsx +18 -12
- package/src/components/button-group/button-group.tsx +1 -1
- package/src/components/calendar/calendar.tsx +3 -2
- package/src/components/card/card.test.tsx +24 -2
- package/src/components/card/card.tsx +47 -6
- package/src/components/carousel/carousel.test.tsx +169 -0
- package/src/components/carousel/carousel.tsx +93 -32
- package/src/components/change-review/change-review.tsx +38 -29
- package/src/components/checkbox/checkbox.tsx +1 -0
- package/src/components/collapsible-panel/use-collapsible-panel.ts +0 -3
- package/src/components/color-picker/color-picker.tsx +17 -11
- package/src/components/combobox/combobox.stories.tsx +10 -0
- package/src/components/combobox/combobox.test.tsx +54 -0
- package/src/components/combobox/combobox.tsx +25 -11
- package/src/components/command/command.test.tsx +15 -0
- package/src/components/command/command.tsx +8 -6
- package/src/components/context-menu/context-menu.tsx +16 -7
- package/src/components/context-rail/context-rail.tsx +4 -4
- package/src/components/copyable-value/copyable-value.stories.tsx +10 -0
- package/src/components/copyable-value/copyable-value.test.tsx +28 -1
- package/src/components/copyable-value/copyable-value.tsx +12 -1
- package/src/components/date-picker/date-picker.stories.tsx +9 -0
- package/src/components/date-picker/date-picker.test.tsx +52 -0
- package/src/components/date-picker/date-picker.tsx +13 -15
- package/src/components/date-range-picker/date-range-picker.stories.tsx +9 -0
- package/src/components/date-range-picker/date-range-picker.test.tsx +76 -1
- package/src/components/date-range-picker/date-range-picker.tsx +39 -28
- package/src/components/descriptions/descriptions.tsx +1 -1
- package/src/components/dialog/dialog.test.tsx +8 -3
- package/src/components/dialog/dialog.tsx +12 -4
- package/src/components/drawer/drawer.tsx +23 -6
- package/src/components/dropdown-menu/dropdown-menu.tsx +4 -4
- package/src/components/field/field.tsx +2 -0
- package/src/components/field-row/field-row.tsx +2 -0
- package/src/components/file-upload/file-upload.test.tsx +83 -10
- package/src/components/file-upload/file-upload.tsx +150 -28
- package/src/components/form/form.tsx +4 -2
- package/src/components/hover-card/hover-card.tsx +1 -0
- package/src/components/icon-button/icon-button.tsx +1 -1
- package/src/components/input/input.tsx +7 -3
- package/src/components/input-group/input-group.tsx +6 -6
- package/src/components/input-otp/input-otp.tsx +3 -1
- package/src/components/keyboard-shortcuts/keyboard-shortcuts.tsx +2 -2
- package/src/components/label/label.tsx +1 -1
- package/src/components/list-editor/list-editor.test.tsx +24 -0
- package/src/components/list-editor/list-editor.tsx +126 -52
- package/src/components/locale-provider/locale-keys.test.ts +134 -0
- package/src/components/locale-provider/messages.ts +321 -0
- package/src/components/mention-input/mention-input.stories.tsx +3 -3
- package/src/components/mention-input/mention-input.test.tsx +51 -1
- package/src/components/mention-input/mention-input.tsx +58 -8
- package/src/components/menubar/menubar.tsx +16 -6
- package/src/components/nav-main/nav-main.tsx +7 -3
- package/src/components/nav-notifications/nav-notifications.test.tsx +11 -0
- package/src/components/nav-notifications/nav-notifications.tsx +11 -5
- package/src/components/nav-user/nav-user.tsx +5 -5
- package/src/components/navigation-menu/navigation-menu.tsx +16 -4
- package/src/components/number-input/number-input.stories.tsx +7 -3
- package/src/components/number-input/number-input.test.tsx +47 -0
- package/src/components/number-input/number-input.tsx +65 -24
- package/src/components/pagination/pagination.test.tsx +20 -1
- package/src/components/pagination/pagination.tsx +18 -14
- package/src/components/popover/popover.tsx +1 -0
- package/src/components/progress/progress.tsx +2 -0
- package/src/components/rating/rating.tsx +2 -0
- package/src/components/resizable/resizable.test.tsx +76 -0
- package/src/components/resizable/resizable.tsx +31 -5
- package/src/components/revision-timeline/revision-timeline.tsx +7 -2
- package/src/components/schema-form/schema-form-store.ts +506 -0
- package/src/components/schema-form/schema-form.test.tsx +122 -1
- package/src/components/schema-form/schema-form.tsx +163 -327
- package/src/components/section-header/section-header.tsx +2 -2
- package/src/components/select/select.tsx +7 -5
- package/src/components/separator/separator.tsx +1 -0
- package/src/components/sheet/sheet.test.tsx +39 -0
- package/src/components/sheet/sheet.tsx +40 -9
- package/src/components/side-dock/side-dock.test.tsx +26 -0
- package/src/components/side-dock/side-dock.tsx +15 -2
- package/src/components/sidebar/sidebar.test.tsx +35 -1
- package/src/components/sidebar/sidebar.tsx +20 -6
- package/src/components/skeleton/skeleton.tsx +1 -0
- package/src/components/slider-number/slider-number.stories.tsx +4 -1
- package/src/components/slider-number/slider-number.test.tsx +1 -1
- package/src/components/sonner/sonner.tsx +2 -0
- package/src/components/spinner/spinner.test.tsx +18 -0
- package/src/components/spinner/spinner.tsx +14 -4
- package/src/components/state-panel/state-panel.test.tsx +3 -3
- package/src/components/state-panel/state-panel.tsx +15 -8
- package/src/components/switch/switch.tsx +2 -0
- package/src/components/table/table.stories.tsx +3 -1
- package/src/components/table/table.test.tsx +9 -2
- package/src/components/table/table.tsx +18 -8
- package/src/components/tabs/index.ts +10 -1
- package/src/components/tabs/tabs.stories.tsx +48 -0
- package/src/components/tabs/tabs.test.tsx +67 -0
- package/src/components/tabs/tabs.tsx +175 -77
- package/src/components/tag-input/tag-input.tsx +4 -2
- package/src/components/team-switcher/team-switcher.tsx +3 -3
- package/src/components/textarea/textarea.tsx +1 -1
- package/src/components/theme-switcher/theme-switcher.stories.tsx +76 -0
- package/src/components/theme-switcher/theme-switcher.test.tsx +182 -0
- package/src/components/theme-switcher/theme-switcher.tsx +134 -10
- package/src/components/timeline/timeline.tsx +0 -3
- package/src/components/toggle/toggle.tsx +7 -2
- package/src/components/toggle-group/toggle-group.tsx +2 -0
- package/src/components/tooltip/tooltip.tsx +2 -1
- package/src/components/top-nav/top-nav.tsx +1 -1
- package/src/components/transfer/transfer.tsx +35 -30
- package/src/components/tree/tree.test.tsx +41 -0
- package/src/components/tree/tree.tsx +77 -22
- package/src/components/tree/use-tree-keyboard.ts +37 -14
- package/src/components/tree-select/tree-select.stories.tsx +9 -0
- package/src/components/tree-select/tree-select.test.tsx +5 -0
- package/src/components/tree-select/tree-select.tsx +11 -14
- package/src/components/typography/prose.test.tsx +3 -2
- package/src/components/typography/prose.tsx +3 -3
- package/src/components/virtual-select/virtual-select.stories.tsx +9 -0
- package/src/components/virtual-select/virtual-select.test.tsx +28 -2
- package/src/components/virtual-select/virtual-select.tsx +56 -18
- package/src/components/wizard/wizard.test.tsx +9 -0
- package/src/components/wizard/wizard.tsx +12 -4
- package/src/index.ts +5 -0
- package/src/lib/cn.ts +9 -0
- package/src/lib/csv.ts +48 -0
- package/src/lib/diff-rows.ts +2 -0
- package/src/lib/merge-refs.ts +2 -0
- package/src/lib/use-controllable-state.ts +47 -0
- package/src/lib/use-mobile.test.tsx +47 -0
- package/src/lib/use-mobile.ts +26 -10
- package/dist/chunk-JKPPTL63.js.map +0 -1
|
@@ -32,6 +32,7 @@ export const DEFAULT_MESSAGES: Record<string, MessageValue> = {
|
|
|
32
32
|
// ── Generic (shared across packages — reuse these before minting a new key) ──
|
|
33
33
|
close: "Close",
|
|
34
34
|
copy: "Copy",
|
|
35
|
+
clear: "Clear",
|
|
35
36
|
previous: "Previous",
|
|
36
37
|
next: "Next",
|
|
37
38
|
previousSlide: "Previous slide",
|
|
@@ -139,6 +140,8 @@ export const DEFAULT_MESSAGES: Record<string, MessageValue> = {
|
|
|
139
140
|
"ui.schemaForm.testAction.success": "Connected",
|
|
140
141
|
"ui.schemaForm.testAction.failure": "Test failed",
|
|
141
142
|
// Pagination's ellipsis (sr-only — the visible glyph is decorative).
|
|
143
|
+
"ui.breadcrumb.label": "breadcrumb",
|
|
144
|
+
"ui.pagination.label": "pagination",
|
|
142
145
|
"ui.pagination.morePages": "More pages",
|
|
143
146
|
// Pagination's prev/next links — the ACCESSIBLE NAME (`aria-label`), distinct
|
|
144
147
|
// from the visible `previous`/`next` text below it: an `aria-label` overrides
|
|
@@ -161,6 +164,11 @@ export const DEFAULT_MESSAGES: Record<string, MessageValue> = {
|
|
|
161
164
|
"ui.tree.failedToLoad": "Failed to load",
|
|
162
165
|
// ThemeSwitcher's "follow the OS" option, in both dropdown and toggle modes.
|
|
163
166
|
"ui.themeSwitcher.system": "System",
|
|
167
|
+
// Group labels + scheme names in the family layout (ADR 0036).
|
|
168
|
+
"ui.themeSwitcher.theme": "Theme",
|
|
169
|
+
"ui.themeSwitcher.mode": "Mode",
|
|
170
|
+
"ui.themeSwitcher.light": "Light",
|
|
171
|
+
"ui.themeSwitcher.dark": "Dark",
|
|
164
172
|
"ui.navNotifications.label": "Notifications",
|
|
165
173
|
"ui.teamSwitcher.label": "Teams",
|
|
166
174
|
"ui.teamSwitcher.addTeam": "Add team",
|
|
@@ -173,11 +181,99 @@ export const DEFAULT_MESSAGES: Record<string, MessageValue> = {
|
|
|
173
181
|
// `viewer.content` vs this), so a translator can phrase either
|
|
174
182
|
// independently and neither package depends on a string owned by another.
|
|
175
183
|
"ui.table.scrollRegion": "Table contents, scrollable",
|
|
184
|
+
// NumberInput's step buttons (#4 i18n sweep).
|
|
185
|
+
"ui.numberInput.decrease": "Decrease",
|
|
186
|
+
"ui.numberInput.increase": "Increase",
|
|
187
|
+
// Combobox/VirtualSelect/TreeSelect each keep their own placeholder pair
|
|
188
|
+
// (English text coincides, but a translator may phrase a search box
|
|
189
|
+
// differently per surface — same rationale as `ui.table.scrollRegion`).
|
|
190
|
+
"ui.combobox.placeholder": "Select…",
|
|
191
|
+
"ui.combobox.searchPlaceholder": "Search…",
|
|
192
|
+
"ui.virtualSelect.placeholder": "Select…",
|
|
193
|
+
"ui.virtualSelect.searchPlaceholder": "Search…",
|
|
194
|
+
"ui.treeSelect.placeholder": "Select…",
|
|
195
|
+
"ui.treeSelect.moreSelected": "and {count} more selected",
|
|
196
|
+
"ui.fileUpload.selectedFiles": "Selected files",
|
|
197
|
+
"ui.fileUpload.dragDropHere": "Drag & drop files here",
|
|
198
|
+
"ui.fileUpload.browseFiles": "Browse files",
|
|
199
|
+
// Sidebar's mobile Sheet description (sr-only) and the collapse/expand
|
|
200
|
+
// trigger's shared name (SidebarTrigger's sr-only text + SidebarRail's
|
|
201
|
+
// aria-label/title).
|
|
202
|
+
"ui.sidebar.mobileDescription": "Displays the mobile sidebar.",
|
|
203
|
+
"ui.sidebar.toggle": "Toggle Sidebar",
|
|
204
|
+
"ui.revisionTimeline.label": "Revisions",
|
|
205
|
+
"ui.tree.retry": "Retry",
|
|
206
|
+
"ui.tree.retryLoadingChildren": "Retry loading children",
|
|
207
|
+
// Spinner's bare default label — kept WITHOUT an ellipsis (unlike the
|
|
208
|
+
// generic `loading` key) to stay byte-identical to its pre-i18n default.
|
|
209
|
+
"ui.spinner.label": "Loading",
|
|
210
|
+
"ui.statePanel.errorTitle": "Something went wrong",
|
|
211
|
+
"ui.statePanel.errorDescription": "An unexpected error occurred. Please try again.",
|
|
212
|
+
"ui.statePanel.errorEyebrow": "Error",
|
|
213
|
+
"ui.carousel.label": "Carousel",
|
|
214
|
+
// Transfer (#4 i18n sweep). `panelFallback`/`itemsFallback` back the two
|
|
215
|
+
// spots where the panel title isn't a plain string (a ReactNode) and the
|
|
216
|
+
// sr-only text falls back to a generic word instead.
|
|
217
|
+
"ui.transfer.selectAllIn": "Select all in {title}",
|
|
218
|
+
"ui.transfer.panelFallback": "panel",
|
|
219
|
+
"ui.transfer.searchIn": "Search {title}",
|
|
220
|
+
"ui.transfer.itemsFallback": "items",
|
|
221
|
+
"ui.transfer.searchPlaceholder": "Search…",
|
|
222
|
+
"ui.transfer.empty": "No items",
|
|
223
|
+
"ui.transfer.defaultSourceTitle": "Source",
|
|
224
|
+
"ui.transfer.defaultTargetTitle": "Target",
|
|
225
|
+
"ui.transfer.movedToTarget": {
|
|
226
|
+
one: "{count} item moved to the target list.",
|
|
227
|
+
other: "{count} items moved to the target list.",
|
|
228
|
+
},
|
|
229
|
+
"ui.transfer.movedToSource": {
|
|
230
|
+
one: "{count} item moved to the source list.",
|
|
231
|
+
other: "{count} items moved to the source list.",
|
|
232
|
+
},
|
|
233
|
+
"ui.transfer.moveControls": "Move controls",
|
|
234
|
+
"ui.transfer.moveSelectedRight": "Move selected right",
|
|
235
|
+
"ui.transfer.moveAllRight": "Move all right",
|
|
236
|
+
"ui.transfer.moveSelectedLeft": "Move selected left",
|
|
237
|
+
"ui.transfer.moveAllLeft": "Move all left",
|
|
238
|
+
// ChangeReview (#4 i18n sweep).
|
|
239
|
+
"ui.changeReview.heading": "Review changes",
|
|
240
|
+
"ui.changeReview.approvedCount": "{approved} of {total} approved",
|
|
241
|
+
"ui.changeReview.rejectAll": "Reject all",
|
|
242
|
+
"ui.changeReview.rejectAllLabel": "Reject all changes",
|
|
243
|
+
"ui.changeReview.approveAll": "Approve all",
|
|
244
|
+
"ui.changeReview.approveAllLabel": "Approve all changes",
|
|
245
|
+
"ui.changeReview.provenanceLabel": "Change provenance",
|
|
246
|
+
"ui.changeReview.emptyTitle": "No changes to review",
|
|
247
|
+
"ui.changeReview.emptyDescription":
|
|
248
|
+
"When an agent proposes edits, they'll appear here for your approval.",
|
|
249
|
+
"ui.changeReview.statusAdded": "added",
|
|
250
|
+
"ui.changeReview.statusRemoved": "removed",
|
|
251
|
+
"ui.changeReview.statusModified": "modified",
|
|
252
|
+
"ui.changeReview.phaseBefore": "Before",
|
|
253
|
+
"ui.changeReview.phaseAfter": "After",
|
|
254
|
+
"ui.changeReview.checkPassed": "Passed",
|
|
255
|
+
"ui.changeReview.checkFailed": "Failed",
|
|
256
|
+
"ui.changeReview.showDetail": "Show detail",
|
|
257
|
+
"ui.changeReview.hideDetail": "Hide detail",
|
|
258
|
+
"ui.changeReview.rejectHunk": "Reject hunk: {title}",
|
|
259
|
+
"ui.changeReview.approveHunk": "Approve hunk: {title}",
|
|
260
|
+
"ui.changeReview.changeType": "Change type: {status}",
|
|
261
|
+
"ui.changeReview.beforePrefix": "Before: ",
|
|
262
|
+
"ui.changeReview.afterPrefix": "After: ",
|
|
263
|
+
"ui.changeReview.approvedBadge": "Approved",
|
|
264
|
+
"ui.navNotifications.trigger": "Open notifications",
|
|
265
|
+
"ui.colorPicker.pickColor": "Pick color",
|
|
266
|
+
"ui.colorPicker.none": "None",
|
|
267
|
+
"ui.colorPicker.swatches": "Color swatches",
|
|
268
|
+
"ui.colorPicker.customHexLabel": "Custom hex",
|
|
269
|
+
"ui.colorPicker.notThemeAware": "(not theme-aware)",
|
|
270
|
+
"ui.colorPicker.customHex": "Custom hex color",
|
|
176
271
|
|
|
177
272
|
// ── @elabs-ai/components-data ─────────────────────────────────────────────────────────────
|
|
178
273
|
// The scroll region's accessible name is rendered ONLY when the table actually
|
|
179
274
|
// overflows its container, so it is never announced for a table that fits.
|
|
180
275
|
"data.table.scrollRegion": "Table contents, scrollable",
|
|
276
|
+
"data.table.loading": "Loading table data…",
|
|
181
277
|
// Fallback name for a row's activation control when the row's first cell holds
|
|
182
278
|
// no primitive value to name it after (see `rowActionLabel`).
|
|
183
279
|
"data.table.rowAction": "Activate row",
|
|
@@ -232,6 +328,10 @@ export const DEFAULT_MESSAGES: Record<string, MessageValue> = {
|
|
|
232
328
|
"data.table.reorderRoleDescription": "sortable",
|
|
233
329
|
"data.facetFilter.clearFilters": "Clear filters",
|
|
234
330
|
"data.columnPicker.toggleColumns": "Toggle columns",
|
|
331
|
+
"data.columnPicker.label": "Columns",
|
|
332
|
+
"data.searchInput.label": "Search",
|
|
333
|
+
"data.searchInput.placeholder": "Search…",
|
|
334
|
+
"data.searchInput.clear": "Clear search",
|
|
235
335
|
|
|
236
336
|
// ── @elabs-ai/components-charts ───────────────────────────────────────────────────────────
|
|
237
337
|
// Shared caption for any bare chart surface's layout-shaped skeleton
|
|
@@ -254,6 +354,43 @@ export const DEFAULT_MESSAGES: Record<string, MessageValue> = {
|
|
|
254
354
|
// drift apart in translation.
|
|
255
355
|
"charts.chartFrame.exportSvg": "Export as SVG",
|
|
256
356
|
"charts.chartFrame.exportPng": "Export as PNG",
|
|
357
|
+
"charts.chartFrame.flipToTable": "Flip to table view",
|
|
358
|
+
"charts.chartFrame.showChart": "Show chart",
|
|
359
|
+
"charts.chartFrame.showAsTable": "Show as table",
|
|
360
|
+
"charts.chartFrame.downloadCsv": "Download CSV",
|
|
361
|
+
"charts.chartFrame.expandChart": "Expand chart",
|
|
362
|
+
"charts.chartFrame.expand": "Expand",
|
|
363
|
+
"charts.chartFrame.summary": "Summary",
|
|
364
|
+
"charts.chartFrame.noDataToSummarize": "No data to summarize.",
|
|
365
|
+
"charts.chartFrame.defaultTitle": "Chart",
|
|
366
|
+
"charts.chartFrame.summaryDetailLabel": "Chart summary",
|
|
367
|
+
"charts.legend.label": "Chart legend",
|
|
368
|
+
"charts.gantt.timeline": "Timeline",
|
|
369
|
+
"charts.gantt.dragToResizeColumn": "Drag to resize column",
|
|
370
|
+
"charts.gantt.viewControls": "Gantt view controls",
|
|
371
|
+
"charts.gantt.viewMode": "View mode",
|
|
372
|
+
"charts.gantt.viewModeLabel": "View mode:",
|
|
373
|
+
"charts.gantt.taskList": "Task list",
|
|
374
|
+
"charts.gantt.taskListLabel": "Tasks",
|
|
375
|
+
"charts.gantt.noTasksToDisplay": "No tasks to display",
|
|
376
|
+
"charts.gantt.unitDay": "Day",
|
|
377
|
+
"charts.gantt.unitWeek": "Week",
|
|
378
|
+
"charts.gantt.unitMonth": "Month",
|
|
379
|
+
"charts.gantt.unitQuarter": "Quarter",
|
|
380
|
+
"charts.gantt.unitHour": "Hour",
|
|
381
|
+
"charts.gantt.unitMinute": "Minute",
|
|
382
|
+
"charts.gantt.unitSecond": "Second",
|
|
383
|
+
"charts.gantt.unitMillisecond": "Millisecond",
|
|
384
|
+
|
|
385
|
+
// ── @elabs-ai/components-maps ─────────────────────────────────────────────────────────────
|
|
386
|
+
"maps.popup.close": "Close popup",
|
|
387
|
+
"maps.canvas.unavailableTitle": "Map unavailable",
|
|
388
|
+
"maps.canvas.unavailableDescription": "This browser can’t render WebGL maps.",
|
|
389
|
+
|
|
390
|
+
// ── @elabs-ai/components-flow ─────────────────────────────────────────────────────────────
|
|
391
|
+
"flow.inspectorPanel.close": "Close inspector",
|
|
392
|
+
"flow.inspectorPanel.title": "Inspector",
|
|
393
|
+
"flow.inspectorPanel.emptyMessage": "Select a node to see its details.",
|
|
257
394
|
|
|
258
395
|
// ── @elabs-ai/components-ai ───────────────────────────────────────────────────────────────
|
|
259
396
|
// Namespaced `ai.<area>.<key>` so package microcopy can't collide with the
|
|
@@ -271,6 +408,7 @@ export const DEFAULT_MESSAGES: Record<string, MessageValue> = {
|
|
|
271
408
|
"ai.promptInput.stop": "Stop",
|
|
272
409
|
"ai.agent.instructions": "Instructions",
|
|
273
410
|
"ai.agent.tools": "Tools",
|
|
411
|
+
"ai.agent.outputSchema": "Output Schema",
|
|
274
412
|
"ai.codeBlock.generating": "Generating…",
|
|
275
413
|
"ai.composer.placeholder": "Ask me anything…",
|
|
276
414
|
"ai.tokenUsage.usage": "Model context usage",
|
|
@@ -284,6 +422,7 @@ export const DEFAULT_MESSAGES: Record<string, MessageValue> = {
|
|
|
284
422
|
// The mobile Sheet's sr-only title (#18) — distinct from `contextPanel.toggle`,
|
|
285
423
|
// which labels the button that opens/closes it.
|
|
286
424
|
"ai.contextPanel.title": "Context panel",
|
|
425
|
+
"ai.contextPanel.description": "Displays the chat context panel.",
|
|
287
426
|
"ai.environmentVariables.toggleVisibility": "Toggle value visibility",
|
|
288
427
|
"ai.gallery.label": "Image gallery",
|
|
289
428
|
"ai.gallery.expandImage": "Expand image",
|
|
@@ -293,8 +432,11 @@ export const DEFAULT_MESSAGES: Record<string, MessageValue> = {
|
|
|
293
432
|
// Shared by Tool's technical view and SchemaDisplay's request panel.
|
|
294
433
|
"ai.schemaDisplay.parameters": "Parameters",
|
|
295
434
|
"ai.schemaDisplay.response": "Response",
|
|
435
|
+
"ai.schemaDisplay.requestBody": "Request Body",
|
|
296
436
|
// ReasoningTrigger's default not-yet-timed message (before `duration` is known).
|
|
297
437
|
"ai.reasoning.thoughtDefault": "Thought for a few seconds",
|
|
438
|
+
"ai.reasoning.thinking": "Thinking…",
|
|
439
|
+
"ai.reasoning.thoughtForDuration": "Thought for {duration} seconds",
|
|
298
440
|
"ai.stackTrace.empty": "No stack frames",
|
|
299
441
|
"ai.webPreview.noConsoleOutput": "No console output",
|
|
300
442
|
// PlanTrigger's icon-only collapse/expand control (sr-only).
|
|
@@ -439,6 +581,185 @@ export const DEFAULT_MESSAGES: Record<string, MessageValue> = {
|
|
|
439
581
|
"ai.streamdown.copyLink": "Copy link",
|
|
440
582
|
"ai.streamdown.copied": "Copied",
|
|
441
583
|
"ai.streamdown.openLink": "Open link",
|
|
584
|
+
// Tool (#4 i18n sweep).
|
|
585
|
+
"ai.tool.showTechnicalDetails": "Show technical details",
|
|
586
|
+
"ai.tool.error": "Error",
|
|
587
|
+
"ai.tool.result": "Result",
|
|
588
|
+
// AssetPreview.
|
|
589
|
+
"ai.assetPreview.rowCount": { one: "{count} row", other: "{count} rows" },
|
|
590
|
+
"ai.assetPreview.noPreview": "No preview available…",
|
|
591
|
+
"ai.assetPreview.preview": "Preview",
|
|
592
|
+
"ai.assetPreview.raw": "Raw",
|
|
593
|
+
// WebPreview's sandboxed iframe accessible name.
|
|
594
|
+
"ai.webPreview.previewTitle": "Preview",
|
|
595
|
+
// FileTree's expand/collapse chevron control.
|
|
596
|
+
"ai.fileTree.expandFolder": "Expand {name}",
|
|
597
|
+
"ai.fileTree.collapseFolder": "Collapse {name}",
|
|
598
|
+
"ai.fileTree.noAssetsProduced": "No assets produced yet.",
|
|
599
|
+
"ai.conversation.download": "Download conversation",
|
|
600
|
+
// OpenInChat. `provider` is a brand name (ChatGPT, Claude, …) — kept
|
|
601
|
+
// untranslated data, interpolated into the translated "Open in …" phrase.
|
|
602
|
+
"ai.openInChat.trigger": "Open in chat",
|
|
603
|
+
"ai.openInChat.openInProvider": "Open in {provider}",
|
|
604
|
+
// TestResults.
|
|
605
|
+
"ai.testResults.passedCount": { one: "{count} passed", other: "{count} passed" },
|
|
606
|
+
"ai.testResults.failedCount": { one: "{count} failed", other: "{count} failed" },
|
|
607
|
+
"ai.testResults.skippedCount": { one: "{count} skipped", other: "{count} skipped" },
|
|
608
|
+
"ai.testResults.testsPassed": "{passed}/{total} tests passed",
|
|
609
|
+
|
|
610
|
+
// ── @elabs-ai/components-editor ───────────────────────────────────────────
|
|
611
|
+
// DecisionCard (ai-objects). Status vocabulary + the card's own accessible name
|
|
612
|
+
// and the "Alternatives considered" section (shared by its heading and its list's
|
|
613
|
+
// aria-label).
|
|
614
|
+
"editor.decisionCard.statusAccepted": "Accepted",
|
|
615
|
+
"editor.decisionCard.statusRejected": "Rejected",
|
|
616
|
+
"editor.decisionCard.statusProposed": "Proposed",
|
|
617
|
+
"editor.decisionCard.statusSuperseded": "Superseded",
|
|
618
|
+
"editor.decisionCard.label": "Decision: {label}",
|
|
619
|
+
"editor.decisionCard.alternativesConsidered": "Alternatives considered",
|
|
620
|
+
// KnowledgeCard (ai-objects).
|
|
621
|
+
"editor.knowledgeCard.label": "Knowledge fact",
|
|
622
|
+
"editor.knowledgeCard.heading": "Knowledge",
|
|
623
|
+
"editor.knowledgeCard.sources": "Sources",
|
|
624
|
+
"editor.knowledgeCard.source": "Source: {name}",
|
|
625
|
+
"editor.knowledgeCard.sourceUnresolved": "Source (unresolved): {name}",
|
|
626
|
+
// CalcBlock. `equals` is the sr-only prefix read before a computed value —
|
|
627
|
+
// intentionally ends in a space so it reads as a prefix (mirrors `ai.diffView.addedLine`).
|
|
628
|
+
"editor.calcBlock.error": "Error: {message}",
|
|
629
|
+
"editor.calcBlock.equals": "equals ",
|
|
630
|
+
"editor.calcBlock.total": "Total",
|
|
631
|
+
"editor.calcBlock.emptyBlock": "Empty calc block.",
|
|
632
|
+
// CopyButton. The "Copy" state reuses the shared generic `copy` key.
|
|
633
|
+
"editor.copyButton.copied": "Copied",
|
|
634
|
+
// EditorToolbar.
|
|
635
|
+
"editor.editorToolbar.language": "Language",
|
|
636
|
+
// Citations (markdown-academic).
|
|
637
|
+
"editor.citations.citationLabel": "Citation: {name}",
|
|
638
|
+
"editor.citations.unresolvedCitation": "Unresolved citation",
|
|
639
|
+
"editor.citations.unresolvedKey": "Unresolved: @{key}",
|
|
640
|
+
"editor.citations.references": "References",
|
|
641
|
+
// Math (markdown-academic).
|
|
642
|
+
"editor.math.renderError": "Could not render math",
|
|
643
|
+
"editor.math.renderErrorLabel": "Math (could not render): {tex}",
|
|
644
|
+
// CompletionMenu (markdown-editor).
|
|
645
|
+
"editor.completions.suggestions": "Suggestions",
|
|
646
|
+
"editor.completions.noSuggestions": "No suggestions",
|
|
647
|
+
// SlashMenu (markdown-editor).
|
|
648
|
+
"editor.slashMenu.insertBlock": "Insert block",
|
|
649
|
+
"editor.slashMenu.noMatchingBlocks": "No matching blocks",
|
|
650
|
+
// TableControlsView (markdown-editor/table-view — the WYSIWYG GFM table toolbar).
|
|
651
|
+
"editor.tableView.tableControls": "Table controls",
|
|
652
|
+
"editor.tableView.row": "Row",
|
|
653
|
+
"editor.tableView.col": "Col",
|
|
654
|
+
"editor.tableView.addRowAbove": "Add row above",
|
|
655
|
+
"editor.tableView.addRowBelow": "Add row below",
|
|
656
|
+
"editor.tableView.deleteRow": "Delete row",
|
|
657
|
+
"editor.tableView.addColumnLeft": "Add column left",
|
|
658
|
+
"editor.tableView.addColumnRight": "Add column right",
|
|
659
|
+
"editor.tableView.deleteColumn": "Delete column",
|
|
660
|
+
// Directive node-views (markdown-editor/directive-views — Milkdown WYSIWYG).
|
|
661
|
+
"editor.directiveViews.cardTitle": "Card title",
|
|
662
|
+
"editor.directiveViews.calloutTitle": "Callout title",
|
|
663
|
+
"editor.directiveViews.unknownBlock": "Unknown block: {name}",
|
|
664
|
+
"editor.directiveViews.unknownInlineBlock": "Unknown inline block:",
|
|
665
|
+
"editor.directiveViews.metricLabel": "Metric label",
|
|
666
|
+
"editor.directiveViews.metricLabelPlaceholder": "Label",
|
|
667
|
+
"editor.directiveViews.metricValue": "Metric value",
|
|
668
|
+
"editor.directiveViews.metricValuePlaceholder": "0",
|
|
669
|
+
"editor.directiveViews.editIteration": "Edit iteration…",
|
|
670
|
+
"editor.directiveViews.changeLayout": "Change layout",
|
|
671
|
+
"editor.directiveViews.transpose": "Transpose",
|
|
672
|
+
"editor.directiveViews.convertToStatic": "Convert to static",
|
|
673
|
+
"editor.directiveViews.needsEmbeddedValues": "— needs embedded values",
|
|
674
|
+
"editor.directiveViews.pivot": "Pivot",
|
|
675
|
+
"editor.directiveViews.iterate": "Iterate",
|
|
676
|
+
"editor.directiveViews.perItem": "· per {as}",
|
|
677
|
+
"editor.directiveViews.templateSuffix": "— template",
|
|
678
|
+
"editor.directiveViews.iterationActions": "Iteration actions",
|
|
679
|
+
"editor.directiveViews.iterationActionsTitle": "Iteration actions…",
|
|
680
|
+
// IterationBuilderDialog (markdown-iteration) — the guided iterate/pivot authoring modal.
|
|
681
|
+
"editor.iterationBuilder.pivotNoun": "pivot",
|
|
682
|
+
"editor.iterationBuilder.iterationNoun": "iteration",
|
|
683
|
+
"editor.iterationBuilder.editTitle": "Edit {noun}",
|
|
684
|
+
"editor.iterationBuilder.insertTitle": "Insert {noun}",
|
|
685
|
+
"editor.iterationBuilder.pivotDescription":
|
|
686
|
+
"Pick the row and column values, then write the per-cell template. The matrix below fills in live.",
|
|
687
|
+
"editor.iterationBuilder.iterationDescription":
|
|
688
|
+
"Add the list values, then write the per-row template. The result below fills in live.",
|
|
689
|
+
"editor.iterationBuilder.bindName": "Bind name",
|
|
690
|
+
"editor.iterationBuilder.bindNamePlaceholder": "item",
|
|
691
|
+
"editor.iterationBuilder.bindNameHintPrefix": "Use ",
|
|
692
|
+
"editor.iterationBuilder.bindNameHintSuffix": " in the template.",
|
|
693
|
+
"editor.iterationBuilder.rowValues": "Row values",
|
|
694
|
+
"editor.iterationBuilder.values": "Values",
|
|
695
|
+
"editor.iterationBuilder.valuePlaceholder": "Type a value, press Enter…",
|
|
696
|
+
"editor.iterationBuilder.columnValues": "Column values",
|
|
697
|
+
"editor.iterationBuilder.layout": "Layout",
|
|
698
|
+
"editor.iterationBuilder.perCellTemplate": "Per-cell template",
|
|
699
|
+
"editor.iterationBuilder.perRowTemplate": "Per-row template",
|
|
700
|
+
"editor.iterationBuilder.livePreview": "Live preview",
|
|
701
|
+
"editor.iterationBuilder.cancel": "Cancel",
|
|
702
|
+
"editor.iterationBuilder.save": "Save",
|
|
703
|
+
"editor.iterationBuilder.insert": "Insert",
|
|
704
|
+
// IterationTemplateDialog (markdown-iteration) — the lighter template-only modal.
|
|
705
|
+
"editor.templateDialog.editPivotTitle": "Edit pivot template",
|
|
706
|
+
"editor.templateDialog.editIterationTitle": "Edit iteration template",
|
|
707
|
+
"editor.templateDialog.descriptionPrefix": "The per-{unit} template. Use ",
|
|
708
|
+
"editor.templateDialog.descriptionMiddle": " placeholders (e.g. ",
|
|
709
|
+
"editor.templateDialog.descriptionSuffix":
|
|
710
|
+
") — each is filled per {unit} when the block renders.",
|
|
711
|
+
"editor.templateDialog.editorLabel": "Iteration template editor",
|
|
712
|
+
"editor.templateDialog.cancel": "Cancel",
|
|
713
|
+
"editor.templateDialog.saveTemplate": "Save template",
|
|
714
|
+
// DocumentOutline (markdown-outline).
|
|
715
|
+
"editor.documentOutline.label": "Document outline",
|
|
716
|
+
"editor.documentOutline.empty": "No headings yet.",
|
|
717
|
+
// MarkdownToolbar (the source-pane formatting bar). Directive snippet labels
|
|
718
|
+
// are the Insert menu's fallback item text (`DIRECTIVE_SNIPPETS`); the snippet
|
|
719
|
+
// MARKDOWN they insert is example document content, not UI chrome, and stays
|
|
720
|
+
// English (see the `i18n-exempt` comments at each snippet).
|
|
721
|
+
"editor.markdownToolbar.label": "Markdown formatting",
|
|
722
|
+
"editor.markdownToolbar.bold": "Bold",
|
|
723
|
+
"editor.markdownToolbar.italic": "Italic",
|
|
724
|
+
"editor.markdownToolbar.inlineCode": "Inline code",
|
|
725
|
+
"editor.markdownToolbar.link": "Link",
|
|
726
|
+
"editor.markdownToolbar.headingLevel": "Heading level",
|
|
727
|
+
"editor.markdownToolbar.heading": "Heading",
|
|
728
|
+
"editor.markdownToolbar.headingLevelItem": "Heading {level}",
|
|
729
|
+
"editor.markdownToolbar.quote": "Quote",
|
|
730
|
+
"editor.markdownToolbar.bulletList": "Bullet list",
|
|
731
|
+
"editor.markdownToolbar.numberedList": "Numbered list",
|
|
732
|
+
"editor.markdownToolbar.divider": "Divider",
|
|
733
|
+
"editor.markdownToolbar.insertBlock": "Insert block",
|
|
734
|
+
"editor.markdownToolbar.insert": "Insert",
|
|
735
|
+
"editor.markdownToolbar.insertBrandBlock": "Insert brand block",
|
|
736
|
+
"editor.markdownToolbar.directiveCard": "Card",
|
|
737
|
+
"editor.markdownToolbar.directiveCallout": "Callout",
|
|
738
|
+
"editor.markdownToolbar.directiveMetric": "Metric",
|
|
739
|
+
"editor.markdownToolbar.directiveTimeline": "Timeline",
|
|
740
|
+
// MarkdownWorkspace's focus-writing toggle.
|
|
741
|
+
"editor.markdownWorkspace.focusWriting": "Focus writing",
|
|
742
|
+
"editor.markdownWorkspace.focus": "Focus",
|
|
743
|
+
"editor.markdownWorkspace.focusWritingHint": "Typewriter scrolling · inactive paragraphs dim",
|
|
744
|
+
// MermaidDiagram (the inline renderer).
|
|
745
|
+
"editor.mermaidDiagram.label": "Diagram",
|
|
746
|
+
"editor.mermaidDiagram.expand": "Expand diagram",
|
|
747
|
+
"editor.mermaidDiagram.downloadSvg": "Download diagram as SVG",
|
|
748
|
+
"editor.mermaidDiagram.copySource": "Copy diagram source",
|
|
749
|
+
"editor.mermaidDiagram.renderFailed": "Diagram failed to render",
|
|
750
|
+
"editor.mermaidDiagram.rendering": "Rendering diagram…",
|
|
751
|
+
// MermaidViewer (the expanded zoom/pan/search surface). `nodeCountHint` mirrors
|
|
752
|
+
// the ORIGINAL (pre-i18n) copy exactly, including its always-plural "nodes" —
|
|
753
|
+
// it is a static hint ("N nodes · type to filter"), not a real plural form, so
|
|
754
|
+
// it stays a plain interpolated string rather than a `PluralMessage`.
|
|
755
|
+
"editor.mermaidViewer.findPlaceholder": "Find in diagram…",
|
|
756
|
+
"editor.mermaidViewer.findLabel": "Find in diagram",
|
|
757
|
+
"editor.mermaidViewer.matchCount": { one: "{count} node", other: "{count} nodes" },
|
|
758
|
+
"editor.mermaidViewer.nodeCountHint": "{count} nodes · type to filter",
|
|
759
|
+
"editor.mermaidViewer.zoomOut": "Zoom out",
|
|
760
|
+
"editor.mermaidViewer.zoomIn": "Zoom in",
|
|
761
|
+
"editor.mermaidViewer.resetZoom": "Reset zoom to 100%",
|
|
762
|
+
"editor.mermaidViewer.fitDiagram": "Fit diagram",
|
|
442
763
|
|
|
443
764
|
// ── @elabs-ai/components-viewer (ADR 0024) ─────────────────────────
|
|
444
765
|
// FileViewer chrome. Every control here is icon-only, so these ARE the
|
|
@@ -77,7 +77,7 @@ function Field({
|
|
|
77
77
|
}) {
|
|
78
78
|
const id = useId();
|
|
79
79
|
return (
|
|
80
|
-
<div className="w-[28rem] space-y-2">
|
|
80
|
+
<div className="w-full max-w-[28rem] space-y-2">
|
|
81
81
|
<Label htmlFor={id}>{label}</Label>
|
|
82
82
|
<MentionInput options={options} {...props}>
|
|
83
83
|
<MentionInputTextarea id={id} placeholder="Type @ to mention someone…" />
|
|
@@ -193,7 +193,7 @@ export const SerializedOutput: Story = {
|
|
|
193
193
|
const payload = useMemo(() => serializeMentions(value), [value]);
|
|
194
194
|
|
|
195
195
|
return (
|
|
196
|
-
<div className="w-[28rem] space-y-4">
|
|
196
|
+
<div className="w-full max-w-[28rem] space-y-4">
|
|
197
197
|
<div className="space-y-2">
|
|
198
198
|
<Label htmlFor={id}>Comment</Label>
|
|
199
199
|
<MentionInput options={ROSTER} value={value} onValueChange={setValue}>
|
|
@@ -235,7 +235,7 @@ export const AsyncRoster: Story = {
|
|
|
235
235
|
const [options, setOptions] = useState<MentionOption[]>([]);
|
|
236
236
|
|
|
237
237
|
return (
|
|
238
|
-
<div className="w-[28rem] space-y-2">
|
|
238
|
+
<div className="w-full max-w-[28rem] space-y-2">
|
|
239
239
|
<Label htmlFor={id}>Assign reviewers</Label>
|
|
240
240
|
<MentionInput
|
|
241
241
|
options={options}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createRef, useState } from "react";
|
|
2
2
|
import { describe, expect, it, vi } from "vitest";
|
|
3
|
-
import { render, screen } from "@testing-library/react";
|
|
3
|
+
import { act, render, screen } from "@testing-library/react";
|
|
4
4
|
import userEvent from "@testing-library/user-event";
|
|
5
5
|
import {
|
|
6
6
|
MentionInput,
|
|
@@ -615,3 +615,53 @@ describe("onQueryChange", () => {
|
|
|
615
615
|
expect(onQueryChange).toHaveBeenLastCalledWith(null);
|
|
616
616
|
});
|
|
617
617
|
});
|
|
618
|
+
|
|
619
|
+
// ---------------------------------------------------------------------------
|
|
620
|
+
// Mirror re-measure — scoped MutationObserver (perf review §3.3)
|
|
621
|
+
// ---------------------------------------------------------------------------
|
|
622
|
+
|
|
623
|
+
describe("mirror re-measure observer scope", () => {
|
|
624
|
+
/**
|
|
625
|
+
* Regression lock for the fix: the observer that used to watch `<html>`
|
|
626
|
+
* AND `<body>` for `class`/`style`/`data-theme`/`data-density`/
|
|
627
|
+
* `data-decoration` now watches only the field's closest `[data-theme]`
|
|
628
|
+
* ancestor (`<html>` here, since jsdom's default document carries none),
|
|
629
|
+
* filtered to `data-theme`/`data-density`/`data-decoration` — so a
|
|
630
|
+
* `style` mutation on `<body>` (what Radix's scroll-lock does on every
|
|
631
|
+
* `Sheet`/`Dialog`/`Popover` open, anywhere in the app) no longer causes a
|
|
632
|
+
* re-measure, while a real theme/density/decoration attribute flip still
|
|
633
|
+
* does.
|
|
634
|
+
*/
|
|
635
|
+
it("ignores an unrelated body style mutation but reacts to a theme attribute flip", async () => {
|
|
636
|
+
render(<Harness />);
|
|
637
|
+
// Let the mount-time layout effects (which call getComputedStyle
|
|
638
|
+
// themselves) settle before counting.
|
|
639
|
+
await Promise.resolve();
|
|
640
|
+
const spy = vi.spyOn(window, "getComputedStyle");
|
|
641
|
+
spy.mockClear();
|
|
642
|
+
|
|
643
|
+
// Simulate Radix's scroll-lock: an inline style write on <body>, nothing
|
|
644
|
+
// to do with this field's own theme/density/decoration.
|
|
645
|
+
document.body.style.paddingRight = "17px";
|
|
646
|
+
await Promise.resolve();
|
|
647
|
+
await Promise.resolve();
|
|
648
|
+
expect(spy).not.toHaveBeenCalled();
|
|
649
|
+
document.body.style.removeProperty("padding-right");
|
|
650
|
+
|
|
651
|
+
// A real theme attribute flip on the scope the observer actually
|
|
652
|
+
// watches (<html>, the field's closest `[data-theme]` ancestor — there
|
|
653
|
+
// is none here, so it falls back to the document root) DOES trigger a
|
|
654
|
+
// re-measure.
|
|
655
|
+
try {
|
|
656
|
+
await act(async () => {
|
|
657
|
+
document.documentElement.setAttribute("data-theme", "dark");
|
|
658
|
+
await vi.waitFor(() => {
|
|
659
|
+
expect(spy).toHaveBeenCalled();
|
|
660
|
+
});
|
|
661
|
+
});
|
|
662
|
+
} finally {
|
|
663
|
+
document.documentElement.removeAttribute("data-theme");
|
|
664
|
+
spy.mockRestore();
|
|
665
|
+
}
|
|
666
|
+
});
|
|
667
|
+
});
|
|
@@ -154,6 +154,45 @@ const MIRRORED_PROPERTIES = [
|
|
|
154
154
|
"wordBreak",
|
|
155
155
|
] as const satisfies readonly (keyof CSSStyleDeclaration & string)[];
|
|
156
156
|
|
|
157
|
+
/**
|
|
158
|
+
* Extra keys `readMirrorStyle` writes onto the mirror style object BESIDES
|
|
159
|
+
* `MIRRORED_PROPERTIES` (some of which — `paddingRight`, `whiteSpace` — it
|
|
160
|
+
* overrides rather than adds). Used by `stylesEqual` below so the "did
|
|
161
|
+
* anything actually change" check compares every key the object can carry,
|
|
162
|
+
* without re-deriving that list from an object literal on every call.
|
|
163
|
+
*/
|
|
164
|
+
const EXTRA_MIRROR_KEYS = [
|
|
165
|
+
"position",
|
|
166
|
+
"left",
|
|
167
|
+
"top",
|
|
168
|
+
"width",
|
|
169
|
+
"height",
|
|
170
|
+
"borderStyle",
|
|
171
|
+
"borderColor",
|
|
172
|
+
"overflow",
|
|
173
|
+
"margin",
|
|
174
|
+
] as const;
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Field-by-field comparison of two mirror style snapshots (perf review §3.3:
|
|
178
|
+
* "two JSON.stringify per callback"). Both objects always carry the exact
|
|
179
|
+
* same key set (`MIRRORED_PROPERTIES` + `EXTRA_MIRROR_KEYS`), so a plain
|
|
180
|
+
* per-key `!==` walk is both cheaper and exact — `JSON.stringify` also
|
|
181
|
+
* silently drops `undefined`-valued keys and is sensitive to key order in a
|
|
182
|
+
* way this object's construction never guarantees.
|
|
183
|
+
*/
|
|
184
|
+
function stylesEqual(a: CSSProperties, b: CSSProperties): boolean {
|
|
185
|
+
const record = a as Record<string, unknown>;
|
|
186
|
+
const other = b as Record<string, unknown>;
|
|
187
|
+
for (const key of MIRRORED_PROPERTIES) {
|
|
188
|
+
if (record[key] !== other[key]) return false;
|
|
189
|
+
}
|
|
190
|
+
for (const key of EXTRA_MIRROR_KEYS) {
|
|
191
|
+
if (record[key] !== other[key]) return false;
|
|
192
|
+
}
|
|
193
|
+
return true;
|
|
194
|
+
}
|
|
195
|
+
|
|
157
196
|
function readMirrorStyle(textarea: HTMLTextAreaElement): CSSProperties {
|
|
158
197
|
const computed = getComputedStyle(textarea);
|
|
159
198
|
const style: Record<string, string | number> = {};
|
|
@@ -540,9 +579,7 @@ export const MentionInput = forwardRef<HTMLDivElement, MentionInputProps>(functi
|
|
|
540
579
|
const element = textareaRef.current;
|
|
541
580
|
if (!element) return;
|
|
542
581
|
const next = readMirrorStyle(element);
|
|
543
|
-
setMirrorStyle((previous) =>
|
|
544
|
-
previous && JSON.stringify(previous) === JSON.stringify(next) ? previous : next,
|
|
545
|
-
);
|
|
582
|
+
setMirrorStyle((previous) => (previous && stylesEqual(previous, next) ? previous : next));
|
|
546
583
|
}, []);
|
|
547
584
|
|
|
548
585
|
useLayoutEffect(() => {
|
|
@@ -571,14 +608,27 @@ export const MentionInput = forwardRef<HTMLDivElement, MentionInputProps>(functi
|
|
|
571
608
|
|
|
572
609
|
// Theme / density / decoration switches re-resolve the INHERITED font and
|
|
573
610
|
// type scale, and a stale snapshot misaligns every chip on the surface.
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
611
|
+
//
|
|
612
|
+
// Scoped to the field's OWN nearest `[data-theme]` ancestor (falling
|
|
613
|
+
// back to `<html>` when the field isn't inside a scoped region), never
|
|
614
|
+
// the whole document — and filtered to only the three attributes a
|
|
615
|
+
// theme/density/decoration switch actually writes, never generic
|
|
616
|
+
// `class`/`style` on `<html>`/`<body>`. The broader, document-wide
|
|
617
|
+
// observer this replaced fired on every Radix `Sheet`/`Dialog`/`Popover`
|
|
618
|
+
// scroll-lock ANYWHERE in the app (it toggles inline `style` —
|
|
619
|
+
// `overflow`, `padding-right` — on `<body>` on every open/close), which
|
|
620
|
+
// has nothing to do with this field's own metrics (perf review §3.3).
|
|
621
|
+
const themeScope =
|
|
622
|
+
element.closest("[data-theme]") ??
|
|
623
|
+
(typeof document !== "undefined" ? document.documentElement : null);
|
|
624
|
+
if (themeScope) {
|
|
625
|
+
const themeObserver = new MutationObserver(remeasureMirror);
|
|
626
|
+
themeObserver.observe(themeScope, {
|
|
577
627
|
attributes: true,
|
|
578
|
-
attributeFilter: ["data-theme", "data-density", "data-decoration"
|
|
628
|
+
attributeFilter: ["data-theme", "data-density", "data-decoration"],
|
|
579
629
|
});
|
|
630
|
+
teardown.push(() => themeObserver.disconnect());
|
|
580
631
|
}
|
|
581
|
-
teardown.push(() => rootObserver.disconnect());
|
|
582
632
|
}
|
|
583
633
|
|
|
584
634
|
// A web font landing after first paint changes every advance width. This
|