diff --git a/.changeset/finish-editor-kit-correctness.md b/.changeset/finish-editor-kit-correctness.md new file mode 100644 index 000000000..fed3d64e1 --- /dev/null +++ b/.changeset/finish-editor-kit-correctness.md @@ -0,0 +1,25 @@ +--- +"@pretable/react": minor +"@pretable/ui": minor +--- + +Finish editor adoption of the component kit. Text, number, enum and date +fields resolve TextInput; multiline fields resolve the new native Textarea +slot. Number steppers and date month buttons resolve IconButton. Preserve +forwarded refs, native props, handlers and ARIA/data hooks in replacements. + +Correct enum identity and clearing. Explicit choices retain their canonical +value even with duplicate labels; ambiguous typed labels/values require a +choice. Empty queries can commit null. Custom parsers receive canonical value +text for an explicit choice and query text for typed input. + +Composition keys retain native behavior. Shift+Tab commits left and Shift+Enter +up, including when controlled rows acknowledge a save later; cancelled or +replaced sessions ignore stale acknowledgements. Multiline plain Enter remains a newline. Calendar navigation now browses +without changing the draft: Enter accepts an intentionally navigated date, +while Tab/blur commit the typed/current value. Date inputs expose a complete +grid-popup combobox contract. + +Preserve editor error/focus/pending styling after kit adoption, prevent error +messages from squeezing fields sideways, and complete disabled and forced-color +calendar/action states. Upgrade React and UI together for the new site hooks. diff --git a/.changeset/guard-pending-edit-lifecycle.md b/.changeset/guard-pending-edit-lifecycle.md new file mode 100644 index 000000000..1446b3ac6 --- /dev/null +++ b/.changeset/guard-pending-edit-lifecycle.md @@ -0,0 +1,14 @@ +--- +"@pretable/react": patch +"@pretable/core": patch +--- + +Prevent cell edits from committing before permission resolves or dispatching +duplicate validation/save operations. Recover from permission, parsing, +validation, and save callback failures without leaving a pending editor stuck, +and ignore stale results after an edit session is replaced or cancelled. + +Draft writes during checking, validating, and saving are now ignored, including +writes from custom editors. This intentionally removes the previous behavior +where a draft write could unlock a pending edit. Enum label normalization waits +for edit permission, and pending editor commit keys do not submit another write. diff --git a/.changeset/repair-overlay-scope-and-states.md b/.changeset/repair-overlay-scope-and-states.md new file mode 100644 index 000000000..9f83fda04 --- /dev/null +++ b/.changeset/repair-overlay-scope-and-states.md @@ -0,0 +1,21 @@ +--- +"@pretable/react": minor +"@pretable/ui": minor +--- + +Add PretableOverlayProvider for an explicit popup container. Place the grid +and portal host in the same CSS scope to preserve custom tokens, direction +and live theme changes while escaping the grid's clipping viewport. The +provider does not copy trigger styles. Without a provider, popups still use +body; a provider with a null target defers popup content until attachment. + +Track nested portal ownership for outside presses. Sibling Selects close one +another, clicks inside nested lists preserve the parent dialog, and trigger +toggles work without hiding outside pointer events. +Keep Select and header popups anchored when live scope or layout changes move +their trigger, including delayed portal attachment. + +Rename the listbox active marker from data-active to data-pretable-active. +Update custom selectors and upgrade the React/UI packages together. Complete +disabled option styles and forced-colors active-option/checkbox state pairs, +including selected-disabled options and checked/mixed disabled checkboxes. diff --git a/.changeset/repair-shared-control-state.md b/.changeset/repair-shared-control-state.md new file mode 100644 index 000000000..555491297 --- /dev/null +++ b/.changeset/repair-shared-control-state.md @@ -0,0 +1,17 @@ +--- +"@pretable/react": minor +--- + +Keep Select highlights attached to option values when options reorder, and +choose an enabled fallback when an active option is removed or disabled. +Empty rosters close the Select; all-disabled lists have no active option. +Typeahead starts fresh each time the list opens. + +Rich Select option labels now require a plain `textValue` for typeahead. +String and number labels continue to infer their text. This tightens +`PretableSelectOption` from an interface to a union: add `textValue` to rich +labels and replace interfaces extending the old type with type intersections. + +Preserve callback-ref cleanup in Select, Checkbox and TextInput, including +ref replacement and StrictMode attachment cycles. Object refs and callbacks +without cleanup still receive their usual null detach. diff --git a/apps/website/app/fixtures/editor-kit/page.tsx b/apps/website/app/fixtures/editor-kit/page.tsx new file mode 100644 index 000000000..4d064744c --- /dev/null +++ b/apps/website/app/fixtures/editor-kit/page.tsx @@ -0,0 +1,132 @@ +"use client"; + +import { forwardRef, useState } from "react"; +import { + PretableSurface, + PretableOverlayProvider, + PretableTextInput, + PretableTextarea, + PretableIconButton, + type PretableColumn, + type PretableTextInputComponent, + type PretableTextareaComponent, + type PretableIconButtonComponent, +} from "@pretable/react"; + +interface Row { + id: string; + name: string; + quantity: number | null; + status: string | null; + date: string | null; + notes: string; +} +const INITIAL: Row[] = [ + { + id: "a", + name: "Alpha", + quantity: 2, + status: "b", + date: "2026-08-18", + notes: "First line", + }, + { + id: "b", + name: "Beta", + quantity: 3, + status: "a", + date: "2026-08-22", + notes: "Second line", + }, +]; +const COLUMNS: PretableColumn[] = [ + { id: "name", header: "Name", editable: true, widthPx: 140 }, + { + id: "quantity", + header: "Quantity", + type: "number", + editable: true, + widthPx: 150, + validate: (value) => + typeof value === "number" && value < 0 ? "Use a positive quantity" : true, + }, + { + id: "status", + header: "Status", + type: "enum", + editable: true, + widthPx: 160, + formatEditValue: () => "Same", + options: [ + { value: "a", label: "Same" }, + { value: "b", label: "Same" }, + { value: "Same", label: "Collision" }, + ], + }, + { id: "date", header: "Date", type: "date", editable: true, widthPx: 160 }, + { id: "notes", header: "Notes", editable: true, wrap: true, widthPx: 230 }, +]; +const getRowId = (row: Row) => row.id; +const Input: PretableTextInputComponent = forwardRef( + function Input(props, ref) { + return ; + }, +); +const Textarea: PretableTextareaComponent = forwardRef( + function Textarea(props, ref) { + return ; + }, +); +const IconButton: PretableIconButtonComponent = forwardRef( + function IconButton(props, ref) { + return ( + + ); + }, +); + +export default function EditorKitFixture() { + const [rows, setRows] = useState(INITIAL); + const [saved, setSaved] = useState(0); + const [dark, setDark] = useState(false); + const [compact, setCompact] = useState(false); + const [host, setHost] = useState(null); + return ( +
+

Editor kit verification

+ + {" "} + +
+ + { + setRows((previous) => + previous.map((item) => (item.id === rowId ? row : item)), + ); + setSaved((count) => count + 1); + }} + /> + +
+
+ {saved} +
{JSON.stringify(rows, null, 2)}
+
+ ); +} diff --git a/apps/website/app/fixtures/overlay-scope/page.tsx b/apps/website/app/fixtures/overlay-scope/page.tsx new file mode 100644 index 000000000..7d4e957d2 --- /dev/null +++ b/apps/website/app/fixtures/overlay-scope/page.tsx @@ -0,0 +1,119 @@ +"use client"; + +import { + PretableOverlayProvider, + PretableSelect, + PretableSurface, + type PretableColumn, +} from "@pretable/react"; +import { useState, type CSSProperties } from "react"; + +const OPTIONS = [ + { value: "alpha", label: "Alpha" }, + { value: "beta", label: "Beta" }, + { value: "gamma", label: "Gamma" }, + { value: "disabled", label: "Unavailable", disabled: true }, +]; +const ROWS = [ + { id: "one", name: "Alpha" }, + { id: "two", name: "Beta" }, +]; +const getRowId = (row: (typeof ROWS)[number]) => row.id; +const COLUMNS: PretableColumn<(typeof ROWS)[number]>[] = [ + { id: "name", header: "Name", type: "text", widthPx: 300 }, +]; + +function Scope({ + name, + initiallyDark, +}: { + name: string; + initiallyDark: boolean; +}) { + const [host, setHost] = useState(null); + const [dark, setDark] = useState(initiallyDark); + const [rtl, setRtl] = useState(initiallyDark); + const [first, setFirst] = useState("alpha"); + const [second, setSecond] = useState("beta"); + const scopeStyle = { + "--pretable-bg-grid": dark ? "rgb(17, 34, 51)" : "rgb(240, 248, 255)", + "--pretable-bg-grid-alt": dark ? "rgb(17, 34, 51)" : "rgb(240, 248, 255)", + "--pretable-text-cell": dark ? "rgb(255, 248, 220)" : "rgb(20, 40, 60)", + "--pretable-rule-strong": dark ? "rgb(150, 180, 210)" : "rgb(50, 70, 90)", + colorScheme: dark ? "dark" : "light", + padding: 16, + border: "1px solid #888", + } as CSSProperties; + return ( +
+

{name}

+ {" "} + + +
+ +
+ + +
+
+
+
+
+ ); +} + +export default function OverlayScopeFixture() { + return ( +
+

Scoped overlay fixture

+ +
+ + +
+
+ ); +} diff --git a/apps/website/content/docs/grid/components.mdx b/apps/website/content/docs/grid/components.mdx index ea79f7635..0b32e5cf1 100644 --- a/apps/website/content/docs/grid/components.mdx +++ b/apps/website/content/docs/grid/components.mdx @@ -1,26 +1,28 @@ --- title: Components -description: "The kit the grid renders its own chrome from — Button, IconButton, Select, TextInput and Checkbox — how to style them, and how to replace them." +description: "The kit the grid renders its own chrome from — Button, IconButton, Select, TextInput, Textarea and Checkbox — how to style them, and how to replace them." nav: Grid --- `@pretable/react` renders its own controls from a small component kit, and ships those components for you to use and to replace. This page covers the -five that ship today: `PretableButton`, `PretableIconButton`, -`PretableSelect`, `PretableTextInput` and `PretableCheckbox`. Here is a grid -whose every labelled button is the app's own: +six that ship today: `PretableButton`, `PretableIconButton`, +`PretableSelect`, `PretableTextInput`, `PretableTextarea` and `PretableCheckbox`. Here is a grid +whose kit buttons use the app's own component: ## The components -Four of the five render `