diff --git a/.changeset/compact-display.md b/.changeset/compact-display.md new file mode 100644 index 0000000..ded9e8c --- /dev/null +++ b/.changeset/compact-display.md @@ -0,0 +1,19 @@ +--- +'@wirecore/wirelayer-recipes': minor +'@wirecore/wirelayer-css': minor +'@wirecore/wirelayer-vue': minor +'@wirecore/wirelayer-react': minor +'@wirecore/wirelayer-native': minor +--- + +Display parts for dense desktop UIs, all opt-in: + +- `Progress status="neutral"`: a grey fill, e.g. for a cancelled transfer (also React Native) +- `Avatar initials`: own initials instead of the ones from `name` ("MS" for "my-server"; also React Native) +- `Card size="sm"`: smaller radius, padding and title, for lists of cards inside a dialog (`wc-card-sm` in HTML); + `ConfigProvider size` leaves cards alone +- `Code` in the main package: code in a sentence with the look of `InlineCode`, without syntax colours and without the + optional `shiki` + +`rounded-card` and `rounded-panel` now count as radii for tailwind-merge (like `rounded-popup`): another radius in +`class`/`classes` replaces them instead of both classes ending up on the element. diff --git a/.changeset/context-menu-keyboard.md b/.changeset/context-menu-keyboard.md new file mode 100644 index 0000000..89336ea --- /dev/null +++ b/.changeset/context-menu-keyboard.md @@ -0,0 +1,10 @@ +--- +'@wirecore/wirelayer-recipes': minor +'@wirecore/wirelayer-vue': minor +'@wirecore/wirelayer-react': minor +--- + +`ContextMenu`'s `before-open` / `onBeforeOpen` gets a second argument `{ source: 'pointer' | 'keyboard', focused }`: +opened with Shift+F10 or the menu key, `focused` is the element with the focus – a list can tell a row from its empty +space even when the browser fires the event on the whole area. The first argument stays as it was. +`createContextMenuSource()` in the recipes tells the two apart. diff --git a/.changeset/dialog-focus.md b/.changeset/dialog-focus.md new file mode 100644 index 0000000..1c8e07c --- /dev/null +++ b/.changeset/dialog-focus.md @@ -0,0 +1,15 @@ +--- +'@wirecore/wirelayer-recipes': minor +'@wirecore/wirelayer-vue': minor +'@wirecore/wirelayer-react': minor +--- + +Focus for dialogs and wizards whose content changes: + +- `data-autoselect` on a field in a `Dialog`: it gets the focus when the dialog opens, with its text selected (e.g. a + prefilled name). Without it Ark's choice stays (an alertdialog's close button, `data-autofocus`, the first element) +- `focusFirst(container)` (also from the component packages) focuses like opening does – `data-autoselect`, + `data-autofocus`/`autofocus`, otherwise the first field; Vue's `DialogContent` exposes it as `focusFirst()` +- `Wizard focusOnStepChange` moves the focus into each new step the same way (not after a failed check: the invalid + field keeps it) +- `autoselectElement(content)` in the recipes for a dialog's own initial focus diff --git a/.changeset/non-empty-values.md b/.changeset/non-empty-values.md new file mode 100644 index 0000000..0894963 --- /dev/null +++ b/.changeset/non-empty-values.md @@ -0,0 +1,13 @@ +--- +'@wirecore/wirelayer-vue': minor +'@wirecore/wirelayer-react': minor +'@wirecore/wirelayer-native': minor +--- + +Choices that cannot be empty give their value type without the empty value – no `$event && …` guard for a model that +is never empty. Only the types change, unless noted: + +- `RadioGroup` and `SegmentedControl` with `required`: v-model / `onValueChange` get `V`, not `V | null`. A form reset + back to an empty start (the only way Ark empties the group) is no longer passed on for a required group. + `SegmentedControl` takes `required` now (`aria-required` on the group); React Native's `RadioGroup` is typed the same +- `NativeSelect` without `placeholder`: `V`, not `V | ''` (only the placeholder option gives `''`) diff --git a/.changeset/number-input-empty-fallback.md b/.changeset/number-input-empty-fallback.md new file mode 100644 index 0000000..b898776 --- /dev/null +++ b/.changeset/number-input-empty-fallback.md @@ -0,0 +1,11 @@ +--- +'@wirecore/wirelayer-vue': minor +'@wirecore/wirelayer-react': minor +--- + +`NumberInput` with `emptyFallback`, for fields that always need a number (e.g. a port): an emptied field keeps its value +while it is empty and falls back to `min` (or 0) or to the last valid number (`previous`) when it is left or Enter is +pressed. v-model / `onValueChange` is then typed `number`. Without it an emptied field still reports null. + +Note for type code around the components: `NumberInput` (Vue) is generic now, so `InstanceType` and +Storybook's `Meta` need a cast (as for Select). diff --git a/.changeset/positioner-z-index.md b/.changeset/positioner-z-index.md new file mode 100644 index 0000000..9851f4c --- /dev/null +++ b/.changeset/positioner-z-index.md @@ -0,0 +1,9 @@ +--- +'@wirecore/wirelayer-tokens': patch +'@wirecore/wirelayer-vue': patch +'@wirecore/wirelayer-react': patch +--- + +Popups with a positioner (Select, Combobox, Menu, Popover …) stay above a dialog: the theme gives Ark's positioners +`z-index: var(--z-index)` (in the base layer). Ark sets the z-index inline, but Vue removed it again whenever the popup +rendered anew – the list then lay behind the dialog, looked closed and the pointer hit the dialog. diff --git a/.changeset/resolve-tokens.md b/.changeset/resolve-tokens.md new file mode 100644 index 0000000..e505032 --- /dev/null +++ b/.changeset/resolve-tokens.md @@ -0,0 +1,10 @@ +--- +'@wirecore/wirelayer-tokens': minor +'@wirecore/wirelayer-vue': minor +'@wirecore/wirelayer-react': minor +--- + +Token colours as fixed values for canvas libraries (xterm.js, charts) that cannot read CSS variables: +`resolveTokens(element?)` gives the colours that apply right now (mode, theme and own overrides) as hex – `#rrggbbaa` for +the translucent ones – and `watchTokens(onChange)` reports a change of mode, theme or system colour scheme. The +composable/hook `useResolvedTokens()` keeps them up to date. diff --git a/.changeset/select-empty-value.md b/.changeset/select-empty-value.md new file mode 100644 index 0000000..a3a70fa --- /dev/null +++ b/.changeset/select-empty-value.md @@ -0,0 +1,8 @@ +--- +'@wirecore/wirelayer-vue': minor +'@wirecore/wirelayer-react': minor +--- + +`Select` with `emptyValue`, e.g. `""` for a model that uses an empty string for "nothing chosen": the value counts as +null – the placeholder shows, no development warning about an unknown value – and clearing gives it back instead of null +(typed `V | E`). Without it `""` is still an unknown value shown as text. diff --git a/.changeset/tabs-panels-elsewhere.md b/.changeset/tabs-panels-elsewhere.md new file mode 100644 index 0000000..a38c9b7 --- /dev/null +++ b/.changeset/tabs-panels-elsewhere.md @@ -0,0 +1,10 @@ +--- +'@wirecore/wirelayer-vue': minor +'@wirecore/wirelayer-react': minor +--- + +`Tabs` for panels that live elsewhere (editor or terminal tabs, panes shown with `v-show`): + +- `TabsTrigger controls="pane-id"` points `aria-controls` at that element; `null` removes it (tabs without panels) +- `Tabs` takes `null` as its value: no tab selected. The first enabled tab then stays reachable with Tab (Ark takes + every tab out of the Tab order without a selection); Tabs without a value stay as they were diff --git a/.changeset/vite-plugin-dev-imports.md b/.changeset/vite-plugin-dev-imports.md new file mode 100644 index 0000000..145fbed --- /dev/null +++ b/.changeset/vite-plugin-dev-imports.md @@ -0,0 +1,8 @@ +--- +'@wirecore/wirelayer-recipes': patch +--- + +Vite plugin: a component imported for the first time while `vite dev` runs gets its classes right away. The plugin added +its sources file to the CSS, but Tailwind's Vite plugin only builds a CSS file again when a file it imports changes on +disk – the classes were missing until a restart (cards without padding, tiny avatars). In the dev server the imports now +live in a file in Vite's cache folder; the build is unchanged. diff --git a/.changeset/wizard-texts.md b/.changeset/wizard-texts.md new file mode 100644 index 0000000..81ebf1b --- /dev/null +++ b/.changeset/wizard-texts.md @@ -0,0 +1,8 @@ +--- +'@wirecore/wirelayer-vue': minor +'@wirecore/wirelayer-react': minor +--- + +Own texts in `Wizard` (and `Stepper`): `finishLabel` names the last button (default "Finish"), and a step's `validate` +may return a text (also async) – it keeps the step open and is shown as its error instead of "Please check the entries +in this step". diff --git a/apps/storybook/src/Migration.mdx b/apps/storybook/src/Migration.mdx index 3c96cff..1ea8bb0 100644 --- a/apps/storybook/src/Migration.mdx +++ b/apps/storybook/src/Migration.mdx @@ -74,3 +74,19 @@ by side, then the places where the two behave differently. The React package has numbers); `Checkbox` with a boolean model gives booleans. - **Compact parts:** `Progress size="xs"` with `hide-label`, `bar-text` and `status`; `Alert #actions`; `mono` on text fields; `DialogContent title-position="header"` puts the title next to the close button. +- **Values that are never empty:** with `required`, `RadioGroup` and `SegmentedControl` give v-model `V` instead of + `V | null`; `NativeSelect` without `placeholder` gives `V` instead of `V | ''`; `NumberInput empty-fallback="min"` or + `"previous"` refills an emptied field when it is left (typed `number`). `Select empty-value=""` treats an empty string + like null – the placeholder shows, clearing gives `""` back. +- **Focus in dialogs and wizards:** `data-autoselect` focuses a field and selects its text when the dialog opens (e.g. a + prefilled name). For content that changes while it is open, `DialogContent` exposes `focusFirst()` (React: + `focusFirst(element)`), and `Wizard focus-on-step-change` moves the focus into each new step. `Wizard finish-label` + names the last button; `validate` may return a text, shown as the step's error. +- **Panels elsewhere:** `TabsTrigger controls="pane-id"` points a tab at a pane outside the Tabs (`:controls="null"` + for none); `Tabs` takes `null` for "no tab selected" – the first tab stays reachable with Tab. +- **Context menus by keyboard:** `@before-open` gets a second argument `{ source: 'pointer' | 'keyboard', focused }` – + with Shift+F10 or the menu key `focused` is the element with the focus (e.g. the list row). +- **Canvas libraries** (xterm.js, charts): `resolveTokens()` gives the token colours of the current mode and theme as hex; + `useResolvedTokens()` updates when the mode, the theme or the system colour scheme changes. +- **More compact parts:** `Card size="sm"` for lists in dialogs, `Progress status="neutral"` for cancelled work, + `Avatar initials="MS"`, and `Code` for code in a sentence without shiki (the look of `InlineCode` from `/code`). diff --git a/docs/implementation-notes.md b/docs/implementation-notes.md index 4e1f659..3540757 100644 --- a/docs/implementation-notes.md +++ b/docs/implementation-notes.md @@ -152,6 +152,24 @@ Worth reading before changing the affected areas. or an effect (and React's `autoFocus`, which only calls `focus()`) is overridden. The trap looks for `[data-autofocus]`/`[autofocus]`; an `alertdialog` starts on its close button. In jsdom no field counts as focusable (no layout), so the test runs in the browser. +- **Positioner z-index in Vue:** Ark copies the z-index of a popup's content to its positioner as an inline style + (`z-index: var(--z-index)`), after the render. Vue patches the positioner's `style` object on every render and clears + keys that are null there – `zIndex` is one of them, so the inline z-index was gone after the first update and the list + lay behind a dialog. The theme sets the same z-index for `[data-scope][data-part='positioner']` in the base layer + (a z-index utility on a positioner still wins). +- **`data-autoselect` and the alertdialog:** the Dialog passes its own `initialFocusEl` to find the marked field – that + replaces Ark's default, which starts an `alertdialog` on its close button, so the Dialog takes the close button itself + for an alertdialog without a marked field. +- **Tabs without a selected tab:** Ark makes only the selected tab tabbable – with the value null no tab is in the Tab + order. When the value is null on purpose, the first enabled tab gets `tabIndex` 0 (found in the DOM after rendering); + Tabs without a value (uncontrolled, no default) stay as they were. +- **Tailwind's Vite plugin and added imports:** `@tailwindcss/vite` keeps the compiled CSS of a file until one of the + files it imports changes on disk – imports that a plugin adds to the code alone are ignored. In the dev server the + WireLayer plugin therefore writes them to a file in Vite's cache folder and imports that file. +- **Narrower emit types:** `required` (RadioGroup, SegmentedControl), the placeholder (NativeSelect), `emptyFallback` + (NumberInput) and `emptyValue` (Select) are extra generics that only change the type of v-model/onValueChange. + `emptyValue` is `NoInfer` in the value types – otherwise the bound value would be taken as `E` and remove null from + the type even without `emptyValue`. - **Controlled form elements:** Vue additionally writes the value of a `` does not know), React only sets the property – the DOM comparison ignores this attribute. For `