@eifi1/ui-kit 0.32.0 → 0.32.2
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/account/delete-account-setting.d.ts +1 -1
- package/dist/admin/admin-action-confirm.d.ts +1 -1
- package/dist/admin/admin-action-log.d.ts +1 -1
- package/dist/admin/admin-action-log.js +4 -2
- package/dist/admin/admin-action-log.js.map +1 -1
- package/dist/admin/invitations-panel.d.ts +1 -1
- package/dist/admin/reviewer-scope-editor.d.ts +1 -1
- package/dist/admin/role-select.d.ts +1 -1
- package/dist/admin/transfer-ownership-dialog.d.ts +1 -1
- package/dist/admin/user-roster.d.ts +1 -1
- package/dist/auth/register-form.d.ts +1 -1
- package/dist/billing/billing-banner.d.ts +1 -1
- package/dist/billing/billing-labels.d.ts +1 -1
- package/dist/billing/billing-labels.js +3 -1
- package/dist/billing/billing-labels.js.map +1 -1
- package/dist/billing/billing-lock.d.ts +1 -1
- package/dist/billing/billing-standing.d.ts +1 -1
- package/dist/billing/plan-card.d.ts +1 -1
- package/dist/billing/plan-card.js +1 -1
- package/dist/billing/plan-card.js.map +1 -1
- package/dist/billing/plan-limit-notice.d.ts +1 -1
- package/dist/billing/subscription-actions.d.ts +1 -1
- package/dist/billing/subscription-status-chip.d.ts +1 -1
- package/dist/chart.d.ts +2 -2
- package/dist/chart.js.map +1 -1
- package/dist/components/alert-banner.js +1 -1
- package/dist/components/alert-banner.js.map +1 -1
- package/dist/components/amount-input.d.ts +1 -1
- package/dist/components/appearance-labels.d.ts +6 -0
- package/dist/components/appearance-labels.js +2 -1
- package/dist/components/appearance-labels.js.map +1 -1
- package/dist/components/button-group.d.ts +1 -1
- package/dist/components/button-group.js +7 -1
- package/dist/components/button-group.js.map +1 -1
- package/dist/components/calculator.d.ts +1 -1
- package/dist/components/chip.js +7 -2
- package/dist/components/chip.js.map +1 -1
- package/dist/components/choice-card.js +1 -1
- package/dist/components/choice-card.js.map +1 -1
- package/dist/components/confirm-dialog.d.ts +1 -1
- package/dist/components/copy-button.d.ts +1 -1
- package/dist/components/danger-confirm.d.ts +1 -1
- package/dist/components/data-table-pagination.d.ts +2 -1
- package/dist/components/data-table-pagination.js +15 -6
- package/dist/components/data-table-pagination.js.map +1 -1
- package/dist/components/data-table.js +2 -2
- package/dist/components/data-table.js.map +1 -1
- package/dist/components/date-picker.js +11 -1
- package/dist/components/date-picker.js.map +1 -1
- package/dist/components/facing-pair.d.ts +1 -1
- package/dist/components/file-button.d.ts +1 -1
- package/dist/components/file-dropzone.d.ts +1 -1
- package/dist/components/form-actions.d.ts +1 -1
- package/dist/components/iban-input.d.ts +1 -1
- package/dist/components/inline-edit-field.js +4 -1
- package/dist/components/inline-edit-field.js.map +1 -1
- package/dist/components/language-select.d.ts +1 -1
- package/dist/components/legal-page.d.ts +1 -1
- package/dist/components/legal.d.ts +1 -1
- package/dist/components/money-field.d.ts +1 -1
- package/dist/components/month-picker.js +41 -34
- package/dist/components/month-picker.js.map +1 -1
- package/dist/components/number-field.d.ts +1 -1
- package/dist/components/number-input.d.ts +1 -1
- package/dist/components/numpad-sheet.d.ts +1 -1
- package/dist/components/page-header.js +6 -1
- package/dist/components/page-header.js.map +1 -1
- package/dist/components/phone-input.d.ts +1 -1
- package/dist/components/phone-input.js +2 -2
- package/dist/components/phone-input.js.map +1 -1
- package/dist/components/popover.d.ts +1 -1
- package/dist/components/popover.js +2 -0
- package/dist/components/popover.js.map +1 -1
- package/dist/components/row-actions.d.ts +1 -1
- package/dist/components/row-actions.js +97 -37
- package/dist/components/row-actions.js.map +1 -1
- package/dist/components/series-chart.d.ts +1 -1
- package/dist/components/settings-fields.d.ts +7 -3
- package/dist/components/settings-fields.js +2 -1
- package/dist/components/settings-fields.js.map +1 -1
- package/dist/components/share-card.d.ts +1 -1
- package/dist/components/share-card.js +6 -2
- package/dist/components/share-card.js.map +1 -1
- package/dist/components/status-dot.js +9 -2
- package/dist/components/status-dot.js.map +1 -1
- package/dist/components/swipeable-row.js +8 -1
- package/dist/components/swipeable-row.js.map +1 -1
- package/dist/components/text-link.d.ts +1 -1
- package/dist/components/time-input.d.ts +1 -1
- package/dist/components/toggle-group.d.ts +9 -2
- package/dist/components/toggle-group.js +3 -3
- package/dist/components/toggle-group.js.map +1 -1
- package/dist/components/translation-review-editor.js +1 -1
- package/dist/components/translation-review-editor.js.map +1 -1
- package/dist/components/translation-review.js +2 -2
- package/dist/components/translation-review.js.map +1 -1
- package/dist/components/ui.d.ts +1 -1
- package/dist/components/ui.js +6 -6
- package/dist/components/ui.js.map +1 -1
- package/dist/components/wizard-stepper.js +20 -14
- package/dist/components/wizard-stepper.js.map +1 -1
- package/dist/{delete-account-setting-BvdQdbv3.d.ts → delete-account-setting-Dy4kJgUm.d.ts} +53 -14
- package/dist/feedback/feedback-attachment.d.ts +1 -1
- package/dist/feedback/feedback-context.d.ts +1 -1
- package/dist/feedback/feedback-crash.d.ts +1 -1
- package/dist/feedback/feedback-dialog.d.ts +1 -1
- package/dist/feedback/feedback-inbox.d.ts +1 -1
- package/dist/feedback/feedback-labels.d.ts +1 -1
- package/dist/feedback/feedback-menu.d.ts +1 -1
- package/dist/feedback/feedback-record.d.ts +1 -1
- package/dist/feedback/feedback-row-detail.d.ts +1 -1
- package/dist/feedback/feedback-status-undo.d.ts +1 -1
- package/dist/feedback/feedback-submit.d.ts +1 -1
- package/dist/feedback/feedback-swipe.d.ts +1 -1
- package/dist/feedback/feedback-table.d.ts +1 -1
- package/dist/feedback/feedback-thread.d.ts +1 -1
- package/dist/feedback.d.ts +1 -1
- package/dist/hooks/use-breakpoint.d.ts +19 -8
- package/dist/hooks/use-breakpoint.js +1 -1
- package/dist/hooks/use-breakpoint.js.map +1 -1
- package/dist/hooks/use-file-drop.d.ts +1 -1
- package/dist/i18n/defaults.d.ts +1 -1
- package/dist/i18n/german.d.ts +1 -1
- package/dist/i18n/german.js +7 -3
- package/dist/i18n/german.js.map +1 -1
- package/dist/i18n/kit-labels.d.ts +1 -1
- package/dist/i18n/languages.d.ts +1 -1
- package/dist/i18n/locales/de-CH.d.ts +1 -1
- package/dist/i18n/locales/en.d.ts +1 -1
- package/dist/i18n/locales/es.d.ts +1 -1
- package/dist/i18n/locales/es.js +7 -3
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.d.ts +1 -1
- package/dist/i18n/locales/fr.js +7 -3
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/hu.d.ts +1 -1
- package/dist/i18n/locales/hu.js +7 -3
- package/dist/i18n/locales/hu.js.map +1 -1
- package/dist/i18n/locales/it.d.ts +1 -1
- package/dist/i18n/locales/it.js +7 -3
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/i18n/locales/zh.d.ts +1 -1
- package/dist/i18n/locales/zh.js +7 -3
- package/dist/i18n/locales/zh.js.map +1 -1
- package/dist/i18n/review.d.ts +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/landing/landing-actions.d.ts +1 -1
- package/dist/landing/landing-sections.d.ts +1 -1
- package/dist/landing/landing-sections.js +7 -3
- package/dist/landing/landing-sections.js.map +1 -1
- package/dist/landing/public-header.d.ts +1 -1
- package/dist/landing/routing.d.ts +1 -1
- package/dist/rhf/fields.d.ts +1 -1
- package/dist/rhf/form.d.ts +1 -1
- package/dist/rhf.d.ts +1 -1
- package/dist/settings/settings-section.d.ts +1 -1
- package/dist/settings/use-account-appearance.d.ts +1 -1
- package/dist/settings/use-account-appearance.js.map +1 -1
- package/dist/shell/app-shell.d.ts +1 -1
- package/dist/shell/app-shell.js +2 -1
- package/dist/shell/app-shell.js.map +1 -1
- package/dist/shell/top-bar-brand.d.ts +1 -1
- package/dist/shell.d.ts +1 -1
- package/dist/theme/contrast.d.ts +3 -2
- package/dist/theme/contrast.js.map +1 -1
- package/dist/theme/text-size.d.ts +19 -3
- package/dist/theme/text-size.js.map +1 -1
- package/dist/wizard/stepper-nav.d.ts +1 -1
- package/dist/wizard/wizard-summary.js +1 -1
- package/dist/wizard/wizard-summary.js.map +1 -1
- package/dist/wizard.d.ts +1 -1
- package/package.json +1 -1
- package/src/admin/admin-action-log.tsx +6 -1
- package/src/billing/billing-labels.ts +16 -0
- package/src/billing/plan-card.tsx +1 -1
- package/src/chart.ts +2 -0
- package/src/components/alert-banner.tsx +5 -1
- package/src/components/appearance-labels.ts +7 -0
- package/src/components/button-group.tsx +21 -0
- package/src/components/chip.tsx +34 -6
- package/src/components/choice-card.tsx +1 -1
- package/src/components/data-table-pagination.tsx +60 -31
- package/src/components/data-table.tsx +8 -4
- package/src/components/date-picker.tsx +32 -1
- package/src/components/inline-edit-field.tsx +20 -1
- package/src/components/month-picker.tsx +6 -1
- package/src/components/page-header.tsx +15 -1
- package/src/components/phone-input.tsx +7 -2
- package/src/components/popover.tsx +4 -0
- package/src/components/row-actions.tsx +156 -49
- package/src/components/settings-fields.tsx +8 -3
- package/src/components/share-card.tsx +21 -2
- package/src/components/status-dot.tsx +25 -2
- package/src/components/swipeable-row.tsx +8 -1
- package/src/components/toggle-group.tsx +26 -12
- package/src/components/translation-review-editor.tsx +1 -1
- package/src/components/translation-review.tsx +5 -4
- package/src/components/ui.tsx +28 -9
- package/src/components/wizard-stepper.tsx +5 -1
- package/src/hooks/use-breakpoint.ts +20 -9
- package/src/i18n/german.ts +4 -0
- package/src/i18n/locales/es.ts +4 -0
- package/src/i18n/locales/fr.ts +4 -0
- package/src/i18n/locales/hu.ts +4 -0
- package/src/i18n/locales/it.ts +4 -0
- package/src/i18n/locales/zh.ts +4 -0
- package/src/index.ts +4 -2
- package/src/landing/landing-sections.tsx +7 -3
- package/src/settings/use-account-appearance.ts +1 -1
- package/src/shell/app-shell.tsx +11 -6
- package/src/theme/contrast.ts +3 -2
- package/src/theme/text-size.ts +19 -3
- package/src/wizard/wizard-summary.tsx +4 -2
- package/tokens.css +15 -7
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/inline-edit-field.tsx"],"sourcesContent":["import { useEffect, useId, useRef, useState } from \"react\";\nimport type { FocusEvent, InputHTMLAttributes, KeyboardEvent, ReactNode } from \"react\";\nimport { cn } from \"../lib/cn\";\nimport { useAnnounce } from \"../hooks/use-announce\";\nimport { useKitLabels } from \"../i18n/kit-labels\";\nimport { Input, Spinner } from \"./ui\";\nimport { FOCUS_RING } from \"./focus-ring\";\nimport { Tooltip } from \"./tooltip\";\nimport { useCommitReason } from \"./write-lock\";\n\n/**\n * Every string {@link InlineEditField} says on its own behalf — the `inlineEdit`\n * namespace of `<UiKitProvider labels>`, overridable per instance through `labels`.\n */\nexport interface InlineEditLabels {\n /** The display button's tooltip and description — what pressing it does. Receives\n * the field's `label` (\"Budget name\" → \"Edit Budget name\"). */\n edit: (label: string) => string;\n /** Shown (and spoken) when `onCommit` rejects and no `formatError` says better. */\n failed: string;\n /** What the display shows for an empty value, when no `placeholder` is given. */\n empty: string;\n}\n\nexport const DEFAULT_INLINE_EDIT_LABELS: InlineEditLabels = {\n edit: (label) => `Edit ${label}`,\n failed: \"The change could not be saved.\",\n empty: \"Empty\",\n};\n\n/** What an `editor` render prop is handed: the draft and the means to end the edit. */\nexport interface InlineEditorProps<T> {\n /** The draft — seeded from `value` each time the editor opens. */\n value: T;\n /** Replace the draft (every keystroke). */\n onChange: (next: T) => void;\n /**\n * End the edit and save. Without an argument it saves the draft; WITH one it saves\n * that — for an editor whose own commit hands over a resolved value (`NumberInput`'s\n * `onCommit` gives \"1050\" for a typed \"1100-50\"). Called twice (the editor's own\n * Enter, then the field's), the second call does nothing.\n */\n commit: (next?: T) => void;\n /** End the edit and drop the draft — what Escape does. */\n cancel: () => void;\n /** The save is in flight: make the control read-only (not disabled — it has focus). */\n pending: boolean;\n /** The field's `label`, for the control's accessible name. */\n label: string;\n /** The last save failed and the draft has not been touched since. */\n invalid: boolean;\n /** The id of the failure message while there is one, for `aria-describedby`. */\n describedBy: string | undefined;\n /** The four above as attributes — spread them on a kit field that takes the DOM\n * spelling: `<AmountInput {...p.inputProps} … />`. */\n inputProps: {\n \"aria-label\": string;\n \"aria-invalid\": true | undefined;\n \"aria-describedby\": string | undefined;\n readOnly: boolean;\n };\n}\n\ninterface InlineEditFieldBaseProps<T> {\n /** The saved value — what the display shows, and what the editor opens on. */\n value: T;\n /**\n * Save `next`. Return a promise and the field waits: the editor stays open and\n * read-only with a spinner until it settles, closes when it resolves, and STAYS OPEN\n * with the failure under it when it rejects (or throws) — the typed value is kept for\n * a retry. Not called when the draft equals `value` (see `isEqual`): opening the\n * editor and leaving it unchanged is not a write.\n */\n onCommit: (next: T) => void | Promise<unknown>;\n /** What the value is (\"Budget name\", \"Assigned\") — the editor's accessible name and\n * the subject of the display's \"Edit …\" description. Not drawn: inline, the row or\n * the column header is the visible label. */\n label: string;\n /** The value as the display shows it — a formatted amount, a date. Default: the\n * value as text. */\n display?: (value: T) => ReactNode;\n /** Whether the draft is unchanged, so leaving the editor is not a write. Default\n * `Object.is`. keksdose compares assigned amounts as NUMBERS — \"1100\" is the\n * server's \"1100.0000\". */\n isEqual?: (a: T, b: T) => boolean;\n /** What the display shows for an empty value (`\"\"`, `null`, an empty `display`).\n * Default `labels.empty`. */\n placeholder?: ReactNode;\n /** `\"end\"` for a figure in a right-aligned money column. Default `\"start\"`. */\n align?: \"start\" | \"end\";\n /** Look, do not touch — a guest's view: the value as plain text, no button. */\n readOnly?: boolean;\n /**\n * Why it cannot be edited now — the write lock's sentence, a closed period. The\n * value stays a BUTTON (focusable, `aria-disabled`) with the reason in the kit\n * Tooltip and its description; it opens no editor. An editor that is open when the\n * reason arrives closes, its draft dropped: a field you can type into must be one\n * whose value can be saved (keksdose dev#496).\n */\n disabledReason?: ReactNode;\n /** This field COMMITS — under a locked {@link WriteLockProvider} it takes the\n * `disabledReason` path with the lock's reason (which wins over its own). No\n * provider, or an unlocked one: no effect. */\n commit?: boolean;\n /** The value is the user's own data: `data-private` on the display and the editor,\n * so the app's demo-mode blur covers both. */\n redact?: boolean;\n /** Turns a rejection into words. Default `labels.failed`. */\n formatError?: (error: unknown) => ReactNode;\n /** Controlled open state — a row's swipe action that opens the editor from outside\n * (keksdose's assigned cell). Left out, the field keeps its own. */\n editing?: boolean;\n /** Called with the next open state: on open, on a save that resolved, on cancel. */\n onEditingChange?: (editing: boolean) => void;\n /** Attributes for the DEFAULT text editor's `<input>` — `maxLength`, `inputMode`,\n * `autoComplete`. Ignored with `editor`. */\n inputProps?: Omit<InputHTMLAttributes<HTMLInputElement>, \"value\" | \"defaultValue\" | \"onChange\" | \"type\">;\n /** Classes for the root. */\n className?: string;\n labels?: Partial<InlineEditLabels>;\n}\n\ntype EditorRender<T> = (props: InlineEditorProps<T>) => ReactNode;\n\n/**\n * The editor. Optional for a string value — the kit's `Input`, compact — and\n * REQUIRED for anything else, since only the caller knows how to edit a number or an\n * amount. Render a kit field from the props it is handed:\n *\n * ```tsx\n * editor={(p) => (\n * <NumberInput calculator={false} ariaLabel={p.label} value={p.value} onChange={p.onChange}\n * onCommit={p.commit} aria-invalid={p.invalid || undefined} aria-describedby={p.describedBy} />\n * )}\n * ```\n *\n * The field itself handles Enter (save), Escape (cancel) and focus leaving the editor\n * (save) around whatever is rendered — a key the editor has already handled\n * (`preventDefault`, as a listbox does for its Enter) is left to it. Focus counts as\n * \"inside\" across a portal, so a picker or a phone keypad the editor opens does not\n * end the edit.\n */\nexport type InlineEditFieldProps<T = string> = InlineEditFieldBaseProps<T> &\n ([T] extends [string] ? { editor?: EditorRender<T> } : { editor: EditorRender<T> });\n\nfunction hasContent(node: ReactNode): boolean {\n return node !== undefined && node !== null && node !== false && node !== \"\";\n}\n\n/** Calls the `editor` render prop as a component of its own: the props it is handed\n * close over the field's refs, and a render prop called in the field's own render\n * would read like a ref read during render. Module-level, so the type is stable and\n * the editor never remounts. */\nfunction RenderEditor<T>({ render, ...props }: InlineEditorProps<T> & { render: EditorRender<T> }) {\n return render(props);\n}\n\n/** `onCommit`'s result, if it is a promise. */\nfunction asPromise(result: unknown): Promise<unknown> | null {\n return result && typeof (result as Promise<unknown>).then === \"function\" ? (result as Promise<unknown>) : null;\n}\n\n/**\n * A value that edits in place: a quiet button showing the value; click it (or Enter\n * on it) and it becomes a field; Enter or leaving the field saves, Escape cancels\n * (keksdose K9).\n *\n * keksdose has three of them by hand — the budget's assigned cell (budget-cells.tsx),\n * the invoice line's value cells (invoice-lines-table.tsx `EditableCell`) and the\n * budget rename (budgets-page.tsx, an `Input` + Save + Cancel with no Escape) — and\n * each had to learn the same rules:\n *\n * - **The display's accessible name is the VALUE.** A cell named \"Edit\" reads a table\n * of \"edit, edit, edit\"; the action is the description instead (\"Edit Assigned\"),\n * in the kit Tooltip on hover and focus.\n * - **An unchanged draft is not a write.** Opening the editor and leaving it was a PUT\n * in keksdose until `isEqual` compared numbers, and on a sealed budget an outbox row\n * for a change nobody made.\n * - **A failed save keeps the editor.** The typed value is the user's work; the\n * failure is shown under it (attached with `aria-describedby`, and spoken), and the\n * next keystroke clears it. Escape still drops the draft.\n * - **Locked means a disabled BUTTON, not a disabled field** (keksdose dev#496): at\n * rest a real row shows this button, so a locked page shows the same, with the\n * reason — never a field that takes keys it cannot save.\n *\n * Inside a {@link DataTable} cell it needs nothing extra: the table already leaves a\n * click or a key on a control of the cell to that control. Focus returns to the value\n * after Enter or Escape; after a save that came from focus leaving, it stays where the\n * user put it.\n *\n * ```tsx\n * <InlineEditField label={t(\"budgets.name\")} value={budget.name}\n * onCommit={(name) => rename.mutateAsync({ id: budget.id, name })} inputProps={{ maxLength: 120 }} />\n * ```\n */\nexport function InlineEditField<T = string>(props: InlineEditFieldProps<T>) {\n const {\n value,\n onCommit,\n label,\n display,\n isEqual = Object.is,\n placeholder,\n align = \"start\",\n readOnly = false,\n disabledReason: ownDisabledReason,\n commit,\n redact = false,\n formatError,\n editing: editingProp,\n onEditingChange,\n inputProps,\n className,\n labels: labelsProp,\n } = props;\n const editor = props.editor as EditorRender<T> | undefined;\n const labels = useKitLabels(\"inlineEdit\", DEFAULT_INLINE_EDIT_LABELS, labelsProp);\n const disabledReason = useCommitReason(commit, ownDisabledReason);\n const locked = hasContent(disabledReason);\n\n const [openState, setOpenState] = useState(false);\n const open = editingProp ?? openState;\n // Locked (or read-only) wins over an open flag: a stale `editing` must never bring a\n // live commit-on-blur field back on a page that cannot save it.\n const editing = open && !locked && !readOnly;\n const [draft, setDraft] = useState<T>(value);\n const [pending, setPending] = useState(false);\n const [failure, setFailure] = useState<ReactNode>(null);\n const failed = hasContent(failure);\n\n const errorId = useId();\n const reasonId = useId();\n const displayRef = useRef<HTMLButtonElement>(null);\n const editorRef = useRef<HTMLDivElement>(null);\n const alert = useAnnounce({ politeness: \"assertive\" });\n\n // One edit ends once: Enter in a NumberInput commits through its own `onCommit` and\n // then reaches the field's Enter; a save that closes the editor unmounts the focused\n // control, which some browsers answer with a blur. The second ending is a no-op.\n const ended = useRef(false);\n const blurTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\n const mounted = useRef(true);\n // The latest draft for the deferred blur check, which runs after a render.\n const draftRef = useRef(draft);\n useEffect(() => {\n draftRef.current = draft;\n });\n useEffect(() => {\n mounted.current = true;\n return () => {\n mounted.current = false;\n if (blurTimer.current) clearTimeout(blurTimer.current);\n };\n }, []);\n\n // Seed the draft when the editor opens — from wherever the open came (the button, a\n // controlled `editing`). During render, like DangerConfirm's wipe, so no frame shows\n // the editor holding the previous edit's text.\n const [wasEditing, setWasEditing] = useState(editing);\n if (wasEditing !== editing) {\n setWasEditing(editing);\n if (editing) {\n setDraft(value);\n setFailure(null);\n setPending(false);\n }\n }\n // An open editor that a lock (or `readOnly`) took away is closed for good, not\n // merely hidden until the lock lifts.\n if (openState && !editing && editingProp === undefined) setOpenState(false);\n\n // Focus follows the edit: into the editor when it opens, back to the value when it\n // closes — but only if focus went down with the editor (Enter, Escape, a save that\n // resolved under it). A save that came from focus LEAVING keeps focus where it went.\n const prevEditing = useRef(editing);\n useEffect(() => {\n const changed = prevEditing.current !== editing;\n prevEditing.current = editing;\n if (!changed) return;\n if (editing) {\n // A fresh edit, which may end once. Here rather than in the seed above: a ref is\n // not written during render, and no event can end the edit before this runs.\n ended.current = false;\n const control = editorRef.current?.querySelector<HTMLElement>(\n \"input:not([type=hidden]), textarea, select, [contenteditable='true'], [tabindex]:not([tabindex='-1']), button\",\n );\n control?.focus();\n if (control instanceof HTMLInputElement || control instanceof HTMLTextAreaElement) control.select();\n return;\n }\n // However it closed — a save, Escape, a lock taking it away — a blur check still\n // queued from the editor has nothing left to save.\n ended.current = true;\n if (blurTimer.current) clearTimeout(blurTimer.current);\n blurTimer.current = null;\n const lost = document.activeElement === null || document.activeElement === document.body;\n if (lost) displayRef.current?.focus();\n }, [editing]);\n\n const setOpen = (next: boolean) => {\n if (editingProp === undefined) setOpenState(next);\n onEditingChange?.(next);\n };\n\n const fail = (caught: unknown) => {\n ended.current = false;\n const message = formatError ? formatError(caught) : labels.failed;\n setFailure(hasContent(message) ? message : labels.failed);\n alert.announce(typeof message === \"string\" && message !== \"\" ? message : labels.failed);\n // Focus back into the editor if the failure left it nowhere (a pending field that\n // the browser let go of); never pulled back from somewhere the user went.\n const active = document.activeElement;\n if (active === null || active === document.body) {\n editorRef.current?.querySelector<HTMLElement>(\"input, textarea, select, [tabindex]\")?.focus();\n }\n };\n\n const save = (next: T = draftRef.current) => {\n if (!editing || ended.current || pending) return;\n ended.current = true;\n if (next !== draftRef.current) setDraft(next);\n if (isEqual(next, value)) {\n setOpen(false);\n return;\n }\n let result: unknown;\n try {\n result = onCommit(next);\n } catch (caught) {\n fail(caught);\n return;\n }\n const promise = asPromise(result);\n if (!promise) {\n setOpen(false);\n return;\n }\n setPending(true);\n setFailure(null);\n promise.then(\n () => {\n if (!mounted.current) return;\n setPending(false);\n setOpen(false);\n },\n (caught: unknown) => {\n if (!mounted.current) return;\n setPending(false);\n fail(caught);\n },\n );\n };\n\n const cancel = () => {\n // A request that has left cannot be called back; Escape waits for it.\n if (!editing || pending) return;\n ended.current = true;\n setOpen(false);\n };\n\n const change = (next: T) => {\n if (pending) return;\n setDraft(next);\n // Now, not after the render: a blur check scheduled before that render reads it.\n draftRef.current = next;\n // The failure described the draft that was sent; once edited it describes nothing\n // on screen, and the next save (or blur) is a fresh attempt.\n setFailure(null);\n };\n\n const onKeyDown = (e: KeyboardEvent<HTMLDivElement>) => {\n if (e.defaultPrevented) return;\n if (e.key === \"Escape\") {\n // Ours, not the dialog's or the popover's around the field.\n e.preventDefault();\n e.stopPropagation();\n cancel();\n return;\n }\n // Not mid-composition: Enter there picks the IME's candidate (Japanese, Chinese).\n if (e.key === \"Enter\" && !e.nativeEvent.isComposing && !e.shiftKey) {\n e.preventDefault();\n save();\n }\n };\n\n // Leaving the editor saves. Deferred one task, and cancelled by any focus that lands\n // back inside: React reports focus moves through PORTALS to the component tree, so a\n // calculator popover or a phone keypad the editor opened counts as inside, which a\n // DOM `contains` check would get wrong. A failure on screen holds the editor: the\n // user is looking at why, and moving away is not a retry.\n const onBlur = (e: FocusEvent<HTMLDivElement>) => {\n if (e.currentTarget.contains(e.relatedTarget as Node | null)) return;\n if (blurTimer.current) clearTimeout(blurTimer.current);\n blurTimer.current = setTimeout(() => {\n blurTimer.current = null;\n if (!mounted.current || failed) return;\n save();\n }, 0);\n };\n const onFocus = () => {\n if (blurTimer.current) clearTimeout(blurTimer.current);\n blurTimer.current = null;\n };\n\n const private_ = redact ? { \"data-private\": true } : undefined;\n const shown = display ? display(value) : value === null || value === undefined ? \"\" : String(value);\n const content = hasContent(shown) ? (\n shown\n ) : (\n <span className=\"text-[var(--text-muted)]\">{placeholder ?? labels.empty}</span>\n );\n const alignClass = align === \"end\" ? \"text-end\" : \"text-start\";\n\n let body: ReactNode;\n if (readOnly) {\n body = (\n <span {...private_} className={cn(\"block min-w-0 truncate\", alignClass)}>\n {content}\n </span>\n );\n } else if (editing) {\n const describedBy = failed ? errorId : undefined;\n const editorProps: InlineEditorProps<T> = {\n value: draft,\n onChange: change,\n commit: save,\n cancel,\n pending,\n label,\n invalid: failed,\n describedBy,\n inputProps: {\n \"aria-label\": label,\n \"aria-invalid\": failed || undefined,\n \"aria-describedby\": describedBy,\n readOnly: pending,\n },\n };\n body = (\n // A wrapper for the keys and the focus, not a control: the keys belong to the\n // field inside it, which is what has focus.\n // eslint-disable-next-line jsx-a11y/no-static-element-interactions -- see above\n <div\n ref={editorRef}\n {...private_}\n onKeyDown={onKeyDown}\n onBlur={onBlur}\n onFocus={onFocus}\n aria-busy={pending || undefined}\n className=\"min-w-0\"\n >\n <div className=\"flex min-w-0 items-center gap-1.5\">\n <div className=\"min-w-0 flex-1\">\n {editor ? (\n <RenderEditor render={editor} {...editorProps} />\n ) : (\n <Input\n {...inputProps}\n {...editorProps.inputProps}\n value={String(draft ?? \"\")}\n onChange={(e) => change(e.target.value as unknown as T)}\n className={cn(\"px-2 py-1\", alignClass, inputProps?.className)}\n />\n )}\n </div>\n {/* Decorative: `aria-busy` on the wrapper says it. */}\n {pending && <Spinner label={null} className=\"size-4 shrink-0\" />}\n </div>\n {failed && (\n <p id={errorId} className=\"mt-1 text-caption leading-tight text-[var(--danger)]\">\n {failure}\n </p>\n )}\n </div>\n );\n } else if (locked) {\n // Button's `disabledReason` path, spelled out for a button that must look like\n // the value rather than like a Button: focusable, `aria-disabled`, every\n // activation swallowed, the reason in the kit Tooltip and in a `hidden` copy that\n // describes it. The button in a FRAGMENT so the Tooltip does not add the bubble as\n // a second description of the same sentence.\n body = (\n <Tooltip label={disabledReason} className=\"flex w-full min-w-0\">\n <>\n <button\n ref={displayRef}\n type=\"button\"\n {...private_}\n aria-disabled\n aria-describedby={reasonId}\n onClick={(e) => e.preventDefault()}\n className={cn(\n \"block w-full min-w-0 cursor-not-allowed truncate rounded-sm focus:outline-none\",\n FOCUS_RING,\n alignClass,\n )}\n >\n {content}\n </button>\n <span id={reasonId} hidden>\n {disabledReason}\n </span>\n </>\n </Tooltip>\n );\n } else {\n body = (\n // Lazy: a table of these mounts no bubble until one is hovered or focused, and\n // the closed bubble's text is in no ancestor's accessible name.\n <Tooltip label={labels.edit(label)} lazy className=\"flex w-full min-w-0\">\n <button\n ref={displayRef}\n type=\"button\"\n {...private_}\n onClick={() => setOpen(true)}\n className={cn(\n \"block w-full min-w-0 truncate rounded-sm underline-offset-2 hover:underline focus:outline-none\",\n FOCUS_RING,\n alignClass,\n )}\n >\n {content}\n </button>\n </Tooltip>\n );\n }\n\n return (\n <div className={cn(\"min-w-0\", className)} data-editing={editing || undefined}>\n {body}\n {/* Always mounted: a live region that mounts with its message is usually missed. */}\n <span {...alert.regionProps} />\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/components/inline-edit-field.tsx"],"sourcesContent":["import { useEffect, useId, useRef, useState } from \"react\";\nimport type { FocusEvent, InputHTMLAttributes, KeyboardEvent, ReactNode } from \"react\";\nimport { cn } from \"../lib/cn\";\nimport { useAnnounce } from \"../hooks/use-announce\";\nimport { useKitLabels } from \"../i18n/kit-labels\";\nimport { Input, Spinner } from \"./ui\";\nimport { FOCUS_RING } from \"./focus-ring\";\nimport { Tooltip } from \"./tooltip\";\nimport { useCommitReason } from \"./write-lock\";\n\n/**\n * Every string {@link InlineEditField} says on its own behalf — the `inlineEdit`\n * namespace of `<UiKitProvider labels>`, overridable per instance through `labels`.\n */\nexport interface InlineEditLabels {\n /** The display button's tooltip and description — what pressing it does. Receives\n * the field's `label` (\"Budget name\" → \"Edit Budget name\"). */\n edit: (label: string) => string;\n /** Shown (and spoken) when `onCommit` rejects and no `formatError` says better. */\n failed: string;\n /** What the display shows for an empty value, when no `placeholder` is given. */\n empty: string;\n}\n\nexport const DEFAULT_INLINE_EDIT_LABELS: InlineEditLabels = {\n edit: (label) => `Edit ${label}`,\n failed: \"The change could not be saved.\",\n empty: \"Empty\",\n};\n\n/** What an `editor` render prop is handed: the draft and the means to end the edit. */\nexport interface InlineEditorProps<T> {\n /** The draft — seeded from `value` each time the editor opens. */\n value: T;\n /** Replace the draft (every keystroke). */\n onChange: (next: T) => void;\n /**\n * End the edit and save. Without an argument it saves the draft; WITH one it saves\n * that — for an editor whose own commit hands over a resolved value (`NumberInput`'s\n * `onCommit` gives \"1050\" for a typed \"1100-50\"). Called twice (the editor's own\n * Enter, then the field's), the second call does nothing.\n */\n commit: (next?: T) => void;\n /** End the edit and drop the draft — what Escape does. */\n cancel: () => void;\n /** The save is in flight: make the control read-only (not disabled — it has focus). */\n pending: boolean;\n /** The field's `label`, for the control's accessible name. */\n label: string;\n /** The last save failed and the draft has not been touched since. */\n invalid: boolean;\n /** The id of the failure message while there is one, for `aria-describedby`. */\n describedBy: string | undefined;\n /** The four above as attributes — spread them on a kit field that takes the DOM\n * spelling: `<AmountInput {...p.inputProps} … />`. */\n inputProps: {\n \"aria-label\": string;\n \"aria-invalid\": true | undefined;\n \"aria-describedby\": string | undefined;\n readOnly: boolean;\n };\n}\n\ninterface InlineEditFieldBaseProps<T> {\n /** The saved value — what the display shows, and what the editor opens on. */\n value: T;\n /**\n * Save `next`. Return a promise and the field waits: the editor stays open and\n * read-only with a spinner until it settles, closes when it resolves, and STAYS OPEN\n * with the failure under it when it rejects (or throws) — the typed value is kept for\n * a retry. Not called when the draft equals `value` (see `isEqual`): opening the\n * editor and leaving it unchanged is not a write.\n */\n onCommit: (next: T) => void | Promise<unknown>;\n /** What the value is (\"Budget name\", \"Assigned\") — the editor's accessible name and\n * the subject of the display's \"Edit …\" description. Not drawn: inline, the row or\n * the column header is the visible label. */\n label: string;\n /** The value as the display shows it — a formatted amount, a date. Default: the\n * value as text. */\n display?: (value: T) => ReactNode;\n /** Whether the draft is unchanged, so leaving the editor is not a write. Default\n * `Object.is`. keksdose compares assigned amounts as NUMBERS — \"1100\" is the\n * server's \"1100.0000\". */\n isEqual?: (a: T, b: T) => boolean;\n /** What the display shows for an empty value (`\"\"`, `null`, an empty `display`).\n * Default `labels.empty`. */\n placeholder?: ReactNode;\n /** `\"end\"` for a figure in a right-aligned money column. Default `\"start\"`. */\n align?: \"start\" | \"end\";\n /** Look, do not touch — a guest's view: the value as plain text, no button. */\n readOnly?: boolean;\n /**\n * Why it cannot be edited now — the write lock's sentence, a closed period. The\n * value stays a BUTTON (focusable, `aria-disabled`) with the reason in the kit\n * Tooltip and its description; it opens no editor. An editor that is open when the\n * reason arrives closes, its draft dropped: a field you can type into must be one\n * whose value can be saved (keksdose dev#496).\n */\n disabledReason?: ReactNode;\n /** This field COMMITS — under a locked {@link WriteLockProvider} it takes the\n * `disabledReason` path with the lock's reason (which wins over its own). No\n * provider, or an unlocked one: no effect. */\n commit?: boolean;\n /** The value is the user's own data: `data-private` on the display and the editor,\n * so the app's demo-mode blur covers both. */\n redact?: boolean;\n /** Turns a rejection into words. Default `labels.failed`. */\n formatError?: (error: unknown) => ReactNode;\n /** Controlled open state — a row's swipe action that opens the editor from outside\n * (keksdose's assigned cell). Left out, the field keeps its own. */\n editing?: boolean;\n /** Called with the next open state: on open, on a save that resolved, on cancel. */\n onEditingChange?: (editing: boolean) => void;\n /** Attributes for the DEFAULT text editor's `<input>` — `maxLength`, `inputMode`,\n * `autoComplete`. Ignored with `editor`. */\n inputProps?: Omit<InputHTMLAttributes<HTMLInputElement>, \"value\" | \"defaultValue\" | \"onChange\" | \"type\">;\n /** Classes for the root. */\n className?: string;\n labels?: Partial<InlineEditLabels>;\n}\n\ntype EditorRender<T> = (props: InlineEditorProps<T>) => ReactNode;\n\n/**\n * The editor. Optional for a string value — the kit's `Input`, compact — and\n * REQUIRED for anything else, since only the caller knows how to edit a number or an\n * amount. Render a kit field from the props it is handed:\n *\n * ```tsx\n * editor={(p) => (\n * <NumberInput calculator={false} ariaLabel={p.label} value={p.value} onChange={p.onChange}\n * onCommit={p.commit} aria-invalid={p.invalid || undefined} aria-describedby={p.describedBy} />\n * )}\n * ```\n *\n * The field itself handles Enter (save), Escape (cancel) and focus leaving the editor\n * (save) around whatever is rendered — a key the editor has already handled\n * (`preventDefault`, as a listbox does for its Enter) is left to it. Focus counts as\n * \"inside\" across a portal, so a picker or a phone keypad the editor opens does not\n * end the edit.\n */\nexport type InlineEditFieldProps<T = string> = InlineEditFieldBaseProps<T> &\n ([T] extends [string] ? { editor?: EditorRender<T> } : { editor: EditorRender<T> });\n\nfunction hasContent(node: ReactNode): boolean {\n return node !== undefined && node !== null && node !== false && node !== \"\";\n}\n\n/** Calls the `editor` render prop as a component of its own: the props it is handed\n * close over the field's refs, and a render prop called in the field's own render\n * would read like a ref read during render. Module-level, so the type is stable and\n * the editor never remounts. */\nfunction RenderEditor<T>({ render, ...props }: InlineEditorProps<T> & { render: EditorRender<T> }) {\n return render(props);\n}\n\n/**\n * The value at Large and Extra large (0.32.1, keksdose's 0.32 report;\n * docs/text-size-harmonization.md §4 \"nothing truncates\"): the display button, the\n * locked one and the read-only text wrap instead of ending in \"…\", and break a word too\n * long for the cell, so an IBAN or an e-mail address shows to its last character. The\n * value IS the button's accessible name and the thing being edited: a reader who needs\n * big type cannot recover a cut-off end by squinting. A `large:` class, so a table of\n * these keeps its one-line cells at Normal.\n *\n * `break-words`, not `[overflow-wrap:anywhere]`: these live in table cells (keksdose's\n * budget and invoice lines), and `anywhere` lowers the value's min-content width to one\n * letter, so an auto-sized column could squeeze an amount to a digit per line. With\n * `break-word` a column keeps each word whole; the button is `w-full`, so a word wider\n * than a sized cell still breaks.\n */\nconst VALUE_WRAP_AT_LARGE = \"large:whitespace-normal large:break-words\";\n\n/** `onCommit`'s result, if it is a promise. */\nfunction asPromise(result: unknown): Promise<unknown> | null {\n return result && typeof (result as Promise<unknown>).then === \"function\" ? (result as Promise<unknown>) : null;\n}\n\n/**\n * A value that edits in place: a quiet button showing the value; click it (or Enter\n * on it) and it becomes a field; Enter or leaving the field saves, Escape cancels\n * (keksdose K9).\n *\n * keksdose has three of them by hand — the budget's assigned cell (budget-cells.tsx),\n * the invoice line's value cells (invoice-lines-table.tsx `EditableCell`) and the\n * budget rename (budgets-page.tsx, an `Input` + Save + Cancel with no Escape) — and\n * each had to learn the same rules:\n *\n * - **The display's accessible name is the VALUE.** A cell named \"Edit\" reads a table\n * of \"edit, edit, edit\"; the action is the description instead (\"Edit Assigned\"),\n * in the kit Tooltip on hover and focus.\n * - **An unchanged draft is not a write.** Opening the editor and leaving it was a PUT\n * in keksdose until `isEqual` compared numbers, and on a sealed budget an outbox row\n * for a change nobody made.\n * - **A failed save keeps the editor.** The typed value is the user's work; the\n * failure is shown under it (attached with `aria-describedby`, and spoken), and the\n * next keystroke clears it. Escape still drops the draft.\n * - **Locked means a disabled BUTTON, not a disabled field** (keksdose dev#496): at\n * rest a real row shows this button, so a locked page shows the same, with the\n * reason — never a field that takes keys it cannot save.\n *\n * Inside a {@link DataTable} cell it needs nothing extra: the table already leaves a\n * click or a key on a control of the cell to that control. Focus returns to the value\n * after Enter or Escape; after a save that came from focus leaving, it stays where the\n * user put it.\n *\n * ```tsx\n * <InlineEditField label={t(\"budgets.name\")} value={budget.name}\n * onCommit={(name) => rename.mutateAsync({ id: budget.id, name })} inputProps={{ maxLength: 120 }} />\n * ```\n */\nexport function InlineEditField<T = string>(props: InlineEditFieldProps<T>) {\n const {\n value,\n onCommit,\n label,\n display,\n isEqual = Object.is,\n placeholder,\n align = \"start\",\n readOnly = false,\n disabledReason: ownDisabledReason,\n commit,\n redact = false,\n formatError,\n editing: editingProp,\n onEditingChange,\n inputProps,\n className,\n labels: labelsProp,\n } = props;\n const editor = props.editor as EditorRender<T> | undefined;\n const labels = useKitLabels(\"inlineEdit\", DEFAULT_INLINE_EDIT_LABELS, labelsProp);\n const disabledReason = useCommitReason(commit, ownDisabledReason);\n const locked = hasContent(disabledReason);\n\n const [openState, setOpenState] = useState(false);\n const open = editingProp ?? openState;\n // Locked (or read-only) wins over an open flag: a stale `editing` must never bring a\n // live commit-on-blur field back on a page that cannot save it.\n const editing = open && !locked && !readOnly;\n const [draft, setDraft] = useState<T>(value);\n const [pending, setPending] = useState(false);\n const [failure, setFailure] = useState<ReactNode>(null);\n const failed = hasContent(failure);\n\n const errorId = useId();\n const reasonId = useId();\n const displayRef = useRef<HTMLButtonElement>(null);\n const editorRef = useRef<HTMLDivElement>(null);\n const alert = useAnnounce({ politeness: \"assertive\" });\n\n // One edit ends once: Enter in a NumberInput commits through its own `onCommit` and\n // then reaches the field's Enter; a save that closes the editor unmounts the focused\n // control, which some browsers answer with a blur. The second ending is a no-op.\n const ended = useRef(false);\n const blurTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\n const mounted = useRef(true);\n // The latest draft for the deferred blur check, which runs after a render.\n const draftRef = useRef(draft);\n useEffect(() => {\n draftRef.current = draft;\n });\n useEffect(() => {\n mounted.current = true;\n return () => {\n mounted.current = false;\n if (blurTimer.current) clearTimeout(blurTimer.current);\n };\n }, []);\n\n // Seed the draft when the editor opens — from wherever the open came (the button, a\n // controlled `editing`). During render, like DangerConfirm's wipe, so no frame shows\n // the editor holding the previous edit's text.\n const [wasEditing, setWasEditing] = useState(editing);\n if (wasEditing !== editing) {\n setWasEditing(editing);\n if (editing) {\n setDraft(value);\n setFailure(null);\n setPending(false);\n }\n }\n // An open editor that a lock (or `readOnly`) took away is closed for good, not\n // merely hidden until the lock lifts.\n if (openState && !editing && editingProp === undefined) setOpenState(false);\n\n // Focus follows the edit: into the editor when it opens, back to the value when it\n // closes — but only if focus went down with the editor (Enter, Escape, a save that\n // resolved under it). A save that came from focus LEAVING keeps focus where it went.\n const prevEditing = useRef(editing);\n useEffect(() => {\n const changed = prevEditing.current !== editing;\n prevEditing.current = editing;\n if (!changed) return;\n if (editing) {\n // A fresh edit, which may end once. Here rather than in the seed above: a ref is\n // not written during render, and no event can end the edit before this runs.\n ended.current = false;\n const control = editorRef.current?.querySelector<HTMLElement>(\n \"input:not([type=hidden]), textarea, select, [contenteditable='true'], [tabindex]:not([tabindex='-1']), button\",\n );\n control?.focus();\n if (control instanceof HTMLInputElement || control instanceof HTMLTextAreaElement) control.select();\n return;\n }\n // However it closed — a save, Escape, a lock taking it away — a blur check still\n // queued from the editor has nothing left to save.\n ended.current = true;\n if (blurTimer.current) clearTimeout(blurTimer.current);\n blurTimer.current = null;\n const lost = document.activeElement === null || document.activeElement === document.body;\n if (lost) displayRef.current?.focus();\n }, [editing]);\n\n const setOpen = (next: boolean) => {\n if (editingProp === undefined) setOpenState(next);\n onEditingChange?.(next);\n };\n\n const fail = (caught: unknown) => {\n ended.current = false;\n const message = formatError ? formatError(caught) : labels.failed;\n setFailure(hasContent(message) ? message : labels.failed);\n alert.announce(typeof message === \"string\" && message !== \"\" ? message : labels.failed);\n // Focus back into the editor if the failure left it nowhere (a pending field that\n // the browser let go of); never pulled back from somewhere the user went.\n const active = document.activeElement;\n if (active === null || active === document.body) {\n editorRef.current?.querySelector<HTMLElement>(\"input, textarea, select, [tabindex]\")?.focus();\n }\n };\n\n const save = (next: T = draftRef.current) => {\n if (!editing || ended.current || pending) return;\n ended.current = true;\n if (next !== draftRef.current) setDraft(next);\n if (isEqual(next, value)) {\n setOpen(false);\n return;\n }\n let result: unknown;\n try {\n result = onCommit(next);\n } catch (caught) {\n fail(caught);\n return;\n }\n const promise = asPromise(result);\n if (!promise) {\n setOpen(false);\n return;\n }\n setPending(true);\n setFailure(null);\n promise.then(\n () => {\n if (!mounted.current) return;\n setPending(false);\n setOpen(false);\n },\n (caught: unknown) => {\n if (!mounted.current) return;\n setPending(false);\n fail(caught);\n },\n );\n };\n\n const cancel = () => {\n // A request that has left cannot be called back; Escape waits for it.\n if (!editing || pending) return;\n ended.current = true;\n setOpen(false);\n };\n\n const change = (next: T) => {\n if (pending) return;\n setDraft(next);\n // Now, not after the render: a blur check scheduled before that render reads it.\n draftRef.current = next;\n // The failure described the draft that was sent; once edited it describes nothing\n // on screen, and the next save (or blur) is a fresh attempt.\n setFailure(null);\n };\n\n const onKeyDown = (e: KeyboardEvent<HTMLDivElement>) => {\n if (e.defaultPrevented) return;\n if (e.key === \"Escape\") {\n // Ours, not the dialog's or the popover's around the field.\n e.preventDefault();\n e.stopPropagation();\n cancel();\n return;\n }\n // Not mid-composition: Enter there picks the IME's candidate (Japanese, Chinese).\n if (e.key === \"Enter\" && !e.nativeEvent.isComposing && !e.shiftKey) {\n e.preventDefault();\n save();\n }\n };\n\n // Leaving the editor saves. Deferred one task, and cancelled by any focus that lands\n // back inside: React reports focus moves through PORTALS to the component tree, so a\n // calculator popover or a phone keypad the editor opened counts as inside, which a\n // DOM `contains` check would get wrong. A failure on screen holds the editor: the\n // user is looking at why, and moving away is not a retry.\n const onBlur = (e: FocusEvent<HTMLDivElement>) => {\n if (e.currentTarget.contains(e.relatedTarget as Node | null)) return;\n if (blurTimer.current) clearTimeout(blurTimer.current);\n blurTimer.current = setTimeout(() => {\n blurTimer.current = null;\n if (!mounted.current || failed) return;\n save();\n }, 0);\n };\n const onFocus = () => {\n if (blurTimer.current) clearTimeout(blurTimer.current);\n blurTimer.current = null;\n };\n\n const private_ = redact ? { \"data-private\": true } : undefined;\n const shown = display ? display(value) : value === null || value === undefined ? \"\" : String(value);\n const content = hasContent(shown) ? (\n shown\n ) : (\n <span className=\"text-[var(--text-muted)]\">{placeholder ?? labels.empty}</span>\n );\n const alignClass = align === \"end\" ? \"text-end\" : \"text-start\";\n\n let body: ReactNode;\n if (readOnly) {\n body = (\n <span {...private_} className={cn(\"block min-w-0 truncate\", VALUE_WRAP_AT_LARGE, alignClass)}>\n {content}\n </span>\n );\n } else if (editing) {\n const describedBy = failed ? errorId : undefined;\n const editorProps: InlineEditorProps<T> = {\n value: draft,\n onChange: change,\n commit: save,\n cancel,\n pending,\n label,\n invalid: failed,\n describedBy,\n inputProps: {\n \"aria-label\": label,\n \"aria-invalid\": failed || undefined,\n \"aria-describedby\": describedBy,\n readOnly: pending,\n },\n };\n body = (\n // A wrapper for the keys and the focus, not a control: the keys belong to the\n // field inside it, which is what has focus.\n // eslint-disable-next-line jsx-a11y/no-static-element-interactions -- see above\n <div\n ref={editorRef}\n {...private_}\n onKeyDown={onKeyDown}\n onBlur={onBlur}\n onFocus={onFocus}\n aria-busy={pending || undefined}\n className=\"min-w-0\"\n >\n <div className=\"flex min-w-0 items-center gap-1.5\">\n <div className=\"min-w-0 flex-1\">\n {editor ? (\n <RenderEditor render={editor} {...editorProps} />\n ) : (\n <Input\n {...inputProps}\n {...editorProps.inputProps}\n value={String(draft ?? \"\")}\n onChange={(e) => change(e.target.value as unknown as T)}\n className={cn(\"px-2 py-1\", alignClass, inputProps?.className)}\n />\n )}\n </div>\n {/* Decorative: `aria-busy` on the wrapper says it. */}\n {pending && <Spinner label={null} className=\"size-4 shrink-0\" />}\n </div>\n {failed && (\n <p id={errorId} className=\"mt-1 text-caption leading-tight text-[var(--danger)]\">\n {failure}\n </p>\n )}\n </div>\n );\n } else if (locked) {\n // Button's `disabledReason` path, spelled out for a button that must look like\n // the value rather than like a Button: focusable, `aria-disabled`, every\n // activation swallowed, the reason in the kit Tooltip and in a `hidden` copy that\n // describes it. The button in a FRAGMENT so the Tooltip does not add the bubble as\n // a second description of the same sentence.\n body = (\n <Tooltip label={disabledReason} className=\"flex w-full min-w-0\">\n <>\n <button\n ref={displayRef}\n type=\"button\"\n {...private_}\n aria-disabled\n aria-describedby={reasonId}\n onClick={(e) => e.preventDefault()}\n className={cn(\n \"block w-full min-w-0 cursor-not-allowed truncate rounded-sm focus:outline-none\",\n VALUE_WRAP_AT_LARGE,\n FOCUS_RING,\n alignClass,\n )}\n >\n {content}\n </button>\n <span id={reasonId} hidden>\n {disabledReason}\n </span>\n </>\n </Tooltip>\n );\n } else {\n body = (\n // Lazy: a table of these mounts no bubble until one is hovered or focused, and\n // the closed bubble's text is in no ancestor's accessible name.\n <Tooltip label={labels.edit(label)} lazy className=\"flex w-full min-w-0\">\n <button\n ref={displayRef}\n type=\"button\"\n {...private_}\n onClick={() => setOpen(true)}\n className={cn(\n \"block w-full min-w-0 truncate rounded-sm underline-offset-2 hover:underline focus:outline-none\",\n VALUE_WRAP_AT_LARGE,\n FOCUS_RING,\n alignClass,\n )}\n >\n {content}\n </button>\n </Tooltip>\n );\n }\n\n return (\n <div className={cn(\"min-w-0\", className)} data-editing={editing || undefined}>\n {body}\n {/* Always mounted: a live region that mounts with its message is usually missed. */}\n <span {...alert.regionProps} />\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
|
-
import { eY as SelectProps, d$ as KitLanguageCode, d_ as KitLanguage } from '../delete-account-setting-
|
|
2
|
+
import { eY as SelectProps, d$ as KitLanguageCode, d_ as KitLanguage } from '../delete-account-setting-Dy4kJgUm.js';
|
|
3
3
|
import './tooltip.js';
|
|
4
4
|
import '../hooks/use-anchored-rect.js';
|
|
5
5
|
import '../lib/clipping.js';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { Ref, ReactNode } from 'react';
|
|
3
|
-
import { ej as LegalPageKey, e4 as LabelOverride, ea as LegalLabels, eg as LegalOperator } from '../delete-account-setting-
|
|
3
|
+
import { ej as LegalPageKey, e4 as LabelOverride, ea as LegalLabels, eg as LegalOperator } from '../delete-account-setting-Dy4kJgUm.js';
|
|
4
4
|
import { CheckboxProps } from './checkbox.js';
|
|
5
5
|
import './tooltip.js';
|
|
6
6
|
import '../hooks/use-anchored-rect.js';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import 'react';
|
|
2
|
-
export { cF as DEFAULT_LEGAL_LABELS, e2 as LEGAL_HREFS, e6 as LegalDisclaimerSectionLabels, e7 as LegalFooter, e8 as LegalFooterProps, e9 as LegalFramedSectionLabels, ea as LegalLabels, eb as LegalLayout, ec as LegalLayoutProps, ed as LegalLink, ee as LegalLinks, ef as LegalLinksProps, eg as LegalOperator, eh as LegalOperatorSectionLabels, ei as LegalOperatorText, gk as LegalPageContext, gl as LegalPageContextValue, ej as LegalPageKey, gm as LegalRefTarget, ek as LegalSection, el as LegalSectionProps, gn as LegalSectionSlot, go as LegalSlotContext, gp as LegalText, em as LegalTextSectionLabels, gq as baseLanguage, fR as legalOperatorText, gr as resolveLegalRefs, ga as useLegalLabels, gs as warnLegalOnce } from '../delete-account-setting-
|
|
2
|
+
export { cF as DEFAULT_LEGAL_LABELS, e2 as LEGAL_HREFS, e6 as LegalDisclaimerSectionLabels, e7 as LegalFooter, e8 as LegalFooterProps, e9 as LegalFramedSectionLabels, ea as LegalLabels, eb as LegalLayout, ec as LegalLayoutProps, ed as LegalLink, ee as LegalLinks, ef as LegalLinksProps, eg as LegalOperator, eh as LegalOperatorSectionLabels, ei as LegalOperatorText, gk as LegalPageContext, gl as LegalPageContextValue, ej as LegalPageKey, gm as LegalRefTarget, ek as LegalSection, el as LegalSectionProps, gn as LegalSectionSlot, go as LegalSlotContext, gp as LegalText, em as LegalTextSectionLabels, gq as baseLanguage, fR as legalOperatorText, gr as resolveLegalRefs, ga as useLegalLabels, gs as warnLegalOnce } from '../delete-account-setting-Dy4kJgUm.js';
|
|
3
3
|
import './tooltip.js';
|
|
4
4
|
import '../hooks/use-anchored-rect.js';
|
|
5
5
|
import '../lib/clipping.js';
|
|
@@ -2,7 +2,7 @@ import * as react from 'react';
|
|
|
2
2
|
import { ComponentProps, Ref } from 'react';
|
|
3
3
|
import { AmountInput } from './amount-input.js';
|
|
4
4
|
import './calculator.js';
|
|
5
|
-
import '../delete-account-setting-
|
|
5
|
+
import '../delete-account-setting-Dy4kJgUm.js';
|
|
6
6
|
import './tooltip.js';
|
|
7
7
|
import '../hooks/use-anchored-rect.js';
|
|
8
8
|
import '../lib/clipping.js';
|
|
@@ -579,41 +579,48 @@ function MonthPicker(props) {
|
|
|
579
579
|
children: grid
|
|
580
580
|
}
|
|
581
581
|
);
|
|
582
|
-
return
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
582
|
+
return (
|
|
583
|
+
// `flex-wrap`: where the heading and the buttons do not fit one row, the buttons
|
|
584
|
+
// take the next line, at the end (0.32.1, the 360 px Extra-large sweep;
|
|
585
|
+
// docs/text-size-harmonization.md §4). In one row at 150 % "Today ‹ ›" left the
|
|
586
|
+
// heading 30 px on a 360 px phone, and "October 2026" was drawn under "Today".
|
|
587
|
+
// Where they fit, nothing changes.
|
|
588
|
+
/* @__PURE__ */ jsxs("div", { ...wrapperRest, ref: rootRef, className: cn("flex min-w-0 flex-wrap items-center gap-2", className), children: [
|
|
589
|
+
Heading ? /* @__PURE__ */ jsx(Heading, { className: "m-0 min-w-0 text-lg font-semibold", children: trigger }) : trigger,
|
|
590
|
+
/* @__PURE__ */ jsxs("div", { className: "ms-auto flex shrink-0 items-center gap-1", children: [
|
|
591
|
+
showToday && /* @__PURE__ */ jsx(
|
|
592
|
+
Button,
|
|
593
|
+
{
|
|
594
|
+
variant: "secondary",
|
|
595
|
+
size: "sm",
|
|
596
|
+
disabled: disabled || outside(nowKey),
|
|
597
|
+
onClick: () => {
|
|
598
|
+
if (nowKey !== shownKey) onChange(nowKey);
|
|
599
|
+
},
|
|
600
|
+
children: years ? labels.thisYear : labels.today
|
|
601
|
+
}
|
|
602
|
+
),
|
|
603
|
+
/* @__PURE__ */ jsx(
|
|
604
|
+
IconButton,
|
|
605
|
+
{
|
|
606
|
+
"aria-label": years ? labels.previousYear : labels.previousMonth,
|
|
607
|
+
disabled: disabled || outside(prevKey),
|
|
608
|
+
onClick: () => onChange(prevKey),
|
|
609
|
+
children: /* @__PURE__ */ jsx(ChevronLeft, { className: "rtl:-scale-x-100" })
|
|
610
|
+
}
|
|
611
|
+
),
|
|
612
|
+
/* @__PURE__ */ jsx(
|
|
613
|
+
IconButton,
|
|
614
|
+
{
|
|
615
|
+
"aria-label": years ? labels.nextYear : labels.nextMonth,
|
|
616
|
+
disabled: disabled || outside(nextKey),
|
|
617
|
+
onClick: () => onChange(nextKey),
|
|
618
|
+
children: /* @__PURE__ */ jsx(ChevronRight, { className: "rtl:-scale-x-100" })
|
|
619
|
+
}
|
|
620
|
+
)
|
|
621
|
+
] })
|
|
615
622
|
] })
|
|
616
|
-
|
|
623
|
+
);
|
|
617
624
|
}
|
|
618
625
|
const field = (
|
|
619
626
|
// `w-fit` for the toolbar size: the calendar glyph is placed against this box, and a
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/month-picker.tsx"],"sourcesContent":["import { useEffect, useId, useMemo, useRef, useState } from \"react\";\nimport type { ComponentPropsWithoutRef, KeyboardEvent, ReactNode, RefObject } from \"react\";\nimport { Calendar, ChevronDown, ChevronLeft, ChevronRight } from \"lucide-react\";\nimport { cn } from \"../lib/cn\";\nimport { dirOf, horizontalStep, type Direction } from \"../lib/direction\";\nimport { monthKey, pad } from \"../lib/dates\";\nimport { Button, FIELD_FLOATING_PAD, FIELD_INVALID, FIELD_TRIGGER, IconButton } from \"./ui\";\nimport { FOCUS_RING, FOCUS_RING_WIDTH } from \"./focus-ring\";\nimport { Popover } from \"./popover\";\nimport { splitTriggerAria } from \"./trigger-aria\";\nimport type { TriggerAria } from \"./trigger-aria\";\nimport { useKitDateFormatter, useKitLabels, useKitLocale } from \"../i18n/kit-labels\";\nimport { FieldBox, FieldLabelLine, hasContent, useFieldMessages } from \"./field-parts\";\n\n/**\n * Every string the month picker can speak. The month and year names are NOT here —\n * they come from `Intl` in the `locale` prop, so they are already in the user's\n * language. What is left is the two icon-only year arrows, whose `aria-label` is the\n * only name they have, the panel's name, and a month cell's name.\n */\nexport interface MonthPickerLabels {\n previousYear: string;\n nextYear: string;\n /** The popover's accessible name (`role=\"dialog\"`). */\n panel: string;\n /**\n * Accessible name of one month cell, given that month and its year formatted in\n * `locale` (\"August 2026\"). The cell's visible text is the short month alone, and\n * \"Aug\" read out on its own does not say which year the grid is on.\n */\n month: (monthYear: string) => string;\n /** `variant=\"stepper\"`: the icon-only arrows that step the value by one month. */\n previousMonth: string;\n nextMonth: string;\n /** `variant=\"stepper\"`: the button that jumps to the current month. Visible text. */\n today: string;\n /** `mode=\"year\"`: the popover's accessible name. */\n yearPanel: string;\n /** `mode=\"year\"`: the icon-only arrows that page the year grid back and forward by a\n * page of twelve years. */\n earlierYears: string;\n laterYears: string;\n /** `mode=\"year\"`, `variant=\"stepper\"`: the button that jumps to the current year.\n * Visible text. */\n thisYear: string;\n}\n\n/** The English starting point every override is merged onto. Exported (unlike the\n * calendar's) because it is asked for by name: a host building its own translation\n * table can spread it rather than re-typing four keys to change one. */\nexport const DEFAULT_MONTH_PICKER_LABELS: MonthPickerLabels = {\n previousYear: \"Previous year\",\n nextYear: \"Next year\",\n panel: \"Choose a month\",\n month: (monthYear) => monthYear,\n previousMonth: \"Previous month\",\n nextMonth: \"Next month\",\n today: \"Today\",\n yearPanel: \"Choose a year\",\n earlierYears: \"Earlier years\",\n laterYears: \"Later years\",\n thisYear: \"This year\",\n};\n\n/** Merge caller overrides onto the English defaults. */\nfunction useMonthPickerLabels(partial?: Partial<MonthPickerLabels>): MonthPickerLabels {\n // Prop > <UiKitProvider> > English, like every labelled component in the kit.\n return useKitLabels(\"monthPicker\", DEFAULT_MONTH_PICKER_LABELS, partial);\n}\n\n/**\n * `onChange` is omitted from the `<div>` attributes: the DOM event of that name carries\n * a `FormEvent`, and this one answers with a month key. Everything else reaches the\n * field's root, so a test id or a `data-tour` anchor can find it.\n */\nexport interface MonthPickerProps extends Omit<ComponentPropsWithoutRef<\"div\">, \"onChange\"> {\n /**\n * The selected month as a `\"YYYY-MM\"` key, or `\"\"` for none.\n *\n * A string rather than `{ year, month }` or a `Date`, for the reason every date in\n * this kit is one: it is what `monthKey()` in `lib/dates` produces, it sorts\n * chronologically so the bounds are plain `<` / `>`, and it crosses a URL or an API\n * without a serialiser. A `{ year, month }` caller converts with\n * `` `${year}-${pad(month)}` `` (month 1–12) and back with `key.split(\"-\").map(Number)`.\n */\n value: string;\n /** Called with the picked month as `\"YYYY-MM\"`. */\n onChange: (month: string) => void;\n /** Drives the month and year names (`Intl.DateTimeFormat`). Defaults to the\n * `<UiKitProvider>` locale, then the runtime's. */\n locale?: string;\n /**\n * Inclusive bounds, as `\"YYYY-MM\"`. A full ISO date (`\"YYYY-MM-DD\"`) is accepted and\n * read as its month, so a host holding \"the first day we have data for\" can pass it\n * straight in. Months outside are shown but refuse selection; a year arrow is\n * disabled once the whole year beyond it is out of bounds.\n */\n min?: string;\n max?: string;\n /** Embedded top-boundary label, matching the other field-shaped controls. */\n label?: ReactNode;\n /** Trigger text when `value` is `\"\"`. */\n placeholder?: string;\n disabled?: boolean;\n /** Required and unanswered — {@link FIELD_INVALID}. See {@link Input}'s `invalid`. */\n invalid?: boolean;\n /**\n * What is wrong with the month, in the caller's words — {@link Input}'s `error`\n * (keksdose K4): under the field, attached to the trigger with `aria-describedby`\n * (merged, after any caption), and implies `invalid`. Passing the key keeps the\n * field's box while there is no message, so the trigger is never remounted.\n */\n error?: ReactNode;\n /** Standing advice, as on a {@link Select}: text is a caption under the field; a\n * {@link FieldHint} rides the label line. */\n hint?: ReactNode;\n /**\n * `\"md\"` (default): the form field. `\"sm\"`: the toolbar trigger — 36px tall (an\n * `IconButton`'s height) and as wide as the month it shows, for a picker that sits\n * between two icon buttons in a page header (keksdose K13: the budget header wrote\n * `triggerClassName=\"h-9 w-auto … py-0\"` by hand, live #260 — 34px between two 36px\n * buttons, *\"the kind of 2px that reads as sloppy\"*). Unlabelled pickers only, as on\n * {@link Select}: a floating label needs the tall box to float in. The width follows\n * the month's name; add a `min-w-*` in `triggerClassName` if the buttons beside it\n * must not move as it changes.\n */\n size?: \"sm\" | \"md\";\n /**\n * `\"month\"` (default): the value is a month, `\"YYYY-MM\"`.\n *\n * `\"year\"`: the value is a YEAR, `\"YYYY\"`, and the panel is a grid of years — kastlan's\n * budget fiscal year (a `Select` of 2020 to three years ahead) and keksdose's tax tab\n * (\"this year and four before\", tax-tab.tsx:33). `min` / `max` are read as their year\n * (`\"2022\"`, or any longer key). A bounded span of twelve years or fewer is shown\n * whole, with no paging; otherwise the grid pages twelve years at a time (PageUp /\n * PageDown, or the arrows), starting from `min` when there is one. The stepper\n * variant steps a year, and its Today button reads \"This year\".\n *\n * A string rather than a number, for the reason the month key is one: it is the\n * same type across both modes, it crosses a URL as is, and it sorts. A numeric\n * caller converts with `String(year)` and `Number(key)`.\n */\n mode?: \"month\" | \"year\";\n /** Intl options for the trigger's text (default: long month + numeric year, or the\n * numeric year in `mode=\"year\"`). Win over the provider's `formatDate`. */\n formatOptions?: Intl.DateTimeFormatOptions;\n /**\n * The month to ring as \"now\", as `\"YYYY-MM\"`. Defaults to the local calendar's\n * current month; pass one to pin it (a test, a host with its own notion of today).\n */\n currentMonth?: string;\n /** User-facing strings; see {@link MonthPickerLabels}. */\n labels?: Partial<MonthPickerLabels>;\n /** Extra classes for the field's root. */\n className?: string;\n /** Extra classes for the trigger button — e.g. a compact `h-9 w-auto` trigger\n * sitting between two icon buttons in a toolbar. */\n triggerClassName?: string;\n /**\n * `\"field\"` (default): the field-shaped trigger, for a form.\n *\n * `\"stepper\"`: the header of a month PAGE — the month as a heading that opens the\n * grid, then Today and ‹ › stepping the value by one month. What a calendar page\n * assembled by hand around the field (overriding its trigger to look like a\n * heading). `label`, `placeholder`, `invalid`, `hint`, `error`, `size` and\n * `triggerClassName` are the field's and are not drawn here; with no `value` the\n * arrows step from the current month (or year).\n */\n variant?: \"field\" | \"stepper\";\n /** `variant=\"stepper\"`: the heading level the month is set in (`<h2>` by default), or\n * `false` for no heading element — the page already has one for the month. */\n headingLevel?: 1 | 2 | 3 | 4 | 5 | 6 | false;\n /** `variant=\"stepper\"`: draw the Today button. Default `true`. */\n showToday?: boolean;\n}\n\n// ── Month keys ────────────────────────────────────────────────────────────\n\ninterface YearMonth {\n year: number;\n /** 1–12, as in the key. */\n month: number;\n}\n\nconst KEY = /^(\\d{4})-(\\d{2})/;\n\nfunction parseMonth(key: string | undefined): YearMonth | null {\n const m = key ? KEY.exec(key) : null;\n if (!m) return null;\n const month = Number(m[2]);\n return month >= 1 && month <= 12 ? { year: Number(m[1]), month } : null;\n}\n\nfunction toKey({ year, month }: YearMonth): string {\n return `${year}-${pad(month)}`;\n}\n\n/** Move a month key by whole months, carrying into the year. */\nfunction shiftMonth(ym: YearMonth, delta: number): YearMonth {\n const index = ym.year * 12 + (ym.month - 1) + delta;\n return { year: Math.floor(index / 12), month: (index % 12) + 1 };\n}\n\n/** A bound, normalised to its month key — so `\"2024-03-15\"` compares as `\"2024-03\"`. */\nfunction boundKey(bound: string | undefined): string | null {\n const ym = parseMonth(bound);\n return ym ? toKey(ym) : null;\n}\n\n/** Local-calendar Date for the 1st of a month, for `Intl` to format. */\nfunction firstOf({ year, month }: YearMonth): Date {\n return new Date(year, month - 1, 1);\n}\n\nconst DEFAULT_TRIGGER_FORMAT: Intl.DateTimeFormatOptions = { month: \"long\", year: \"numeric\" };\nconst MONTH_NAME_FORMAT: Intl.DateTimeFormatOptions = { month: \"long\", year: \"numeric\" };\nconst YEAR_FORMAT: Intl.DateTimeFormatOptions = { year: \"numeric\" };\n\n// ── Year keys ─────────────────────────────────────────────────────────────\n\nconst YEAR = /^(\\d{4})/;\n\n/** The year of `\"YYYY\"`, `\"YYYY-MM\"` or `\"YYYY-MM-DD\"`, or null. */\nfunction parseYear(key: string | undefined): number | null {\n const m = key ? YEAR.exec(key) : null;\n return m ? Number(m[1]) : null;\n}\n\n// ── Trigger ───────────────────────────────────────────────────────────────\n\n/** Hand focus back to the trigger when the panel closes with focus still inside it. */\nfunction useFocusBackOnClose(open: boolean, triggerRef: RefObject<HTMLButtonElement | null>) {\n const wasOpen = useRef(false);\n useEffect(() => {\n const justClosed = wasOpen.current && !open;\n wasOpen.current = open;\n if (!justClosed) return;\n // Only when focus went down with the panel. An outside click has already put it\n // somewhere real, and dragging it back here would be worse than leaving it.\n const active = document.activeElement;\n if (!active || active === document.body) triggerRef.current?.focus();\n }, [open, triggerRef]);\n}\n\n/**\n * The stepper's trigger: the month as a heading-sized button that opens the grid.\n *\n * Its own text is its name (\"September 2026\"), so it needs no label of its own, and\n * it is `aria-live` for the reason `MiniCalendar`'s caption is: pressing ‹ or › leaves\n * focus on the arrow, and the heading is the only thing that says where the arrow\n * went. A plain button with `aria-haspopup`, not the field's `combobox` — it does not\n * look or behave like a field, and a heading reading \"combobox\" is a surprise.\n */\nfunction MonthHeadingTrigger({\n open,\n toggle,\n triggerRef,\n text,\n panelId,\n disabled,\n aria,\n}: {\n open: boolean;\n toggle: () => void;\n triggerRef: RefObject<HTMLButtonElement | null>;\n text: string;\n panelId: string;\n disabled?: boolean;\n aria: TriggerAria;\n}) {\n useFocusBackOnClose(open, triggerRef);\n return (\n <button\n ref={triggerRef}\n type=\"button\"\n disabled={disabled}\n onClick={toggle}\n aria-haspopup=\"dialog\"\n aria-controls={panelId}\n aria-expanded={open}\n id={aria.id}\n aria-label={aria[\"aria-label\"]}\n aria-labelledby={aria[\"aria-labelledby\"]}\n aria-describedby={aria[\"aria-describedby\"]}\n className={cn(\n \"-ms-1.5 inline-flex h-9 min-w-0 items-center gap-1 rounded-md px-1.5 text-lg font-semibold text-[var(--text-primary)] transition-colors\",\n \"hover:bg-[var(--bg-hover)]\",\n FOCUS_RING,\n disabled && \"cursor-not-allowed opacity-50\",\n )}\n >\n <span aria-live=\"polite\" className=\"truncate\">\n {text || \"\\u00a0\"}\n </span>\n <ChevronDown aria-hidden className=\"size-4 shrink-0 text-[var(--text-muted)]\" />\n </button>\n );\n}\n\n/**\n * The field-shaped button that opens the grid. The same shape as `DatePicker`'s\n * trigger, for the same reasons written down there: `role=\"combobox\"` so it may carry\n * `aria-expanded` / `aria-invalid`, a name made of the label AND the value, and focus\n * handed back here when the panel closes with focus still inside it.\n */\nfunction MonthFieldTrigger({\n open,\n toggle,\n triggerRef,\n triggerText,\n hasValue,\n labelledBy,\n valueId,\n panelId,\n padded,\n small,\n disabled,\n invalid,\n className,\n aria,\n}: {\n open: boolean;\n toggle: () => void;\n triggerRef: RefObject<HTMLButtonElement | null>;\n triggerText: string;\n hasValue: boolean;\n labelledBy: string;\n /** The caller's naming attributes — see `trigger-aria.ts`. */\n aria: TriggerAria;\n valueId: string;\n panelId: string;\n padded: boolean;\n /** `size=\"sm\"`: the 36px, content-wide toolbar trigger. */\n small: boolean;\n disabled?: boolean;\n invalid?: boolean;\n className?: string;\n}) {\n useFocusBackOnClose(open, triggerRef);\n\n return (\n <button\n ref={triggerRef}\n type=\"button\"\n disabled={disabled}\n onClick={toggle}\n role=\"combobox\"\n aria-haspopup=\"dialog\"\n aria-controls={panelId}\n aria-expanded={open}\n id={aria.id}\n aria-invalid={invalid || aria[\"aria-invalid\"] === true || aria[\"aria-invalid\"] === \"true\" || undefined}\n aria-required={aria[\"aria-required\"]}\n aria-labelledby={aria[\"aria-label\"] && !aria[\"aria-labelledby\"] ? undefined : labelledBy}\n aria-label={aria[\"aria-label\"]}\n aria-describedby={aria[\"aria-describedby\"]}\n className={cn(\n FIELD_TRIGGER,\n \"pe-9\",\n padded && FIELD_FLOATING_PAD,\n // keksdose K13 (live #260): an IconButton's 36px, and only as wide as the\n // month — `py-0` with a fixed height so the box is 36px whatever the type's\n // line height. `pe-9` stays: the calendar glyph still sits at the end.\n small && \"h-9 w-auto py-0\",\n disabled && \"cursor-not-allowed opacity-50\",\n invalid && FIELD_INVALID,\n className,\n )}\n >\n {/* The U+00A0 fallback keeps the line box when there is no text — see\n DatePicker. The value arrives capitalised (see `upperFirst`); this span does\n NOT use `capitalize`, which upper-cases every WORD — \"Choose A Month\",\n \"Septiembre De 2026\". */}\n <span\n id={valueId}\n className={cn(\"truncate\", !hasValue && \"text-[var(--text-placeholder)]\")}\n >\n {triggerText || \" \"}\n </span>\n </button>\n );\n}\n\n// ── Grid ──────────────────────────────────────────────────────────────────\n\nconst COLUMNS = 3;\n\n/**\n * The panel: a year caption between ‹ › arrows, over a 4×3 grid of months.\n *\n * Mounted only while the popover is open, so every opening starts from the current\n * value's year — a value moved from outside (a prev/next-month button beside the\n * field) is where the grid lands, with no effect needed to snap it back.\n *\n * A grid in the APG sense, like `MiniCalendar`: one tab stop (the roving tabindex),\n * the arrows walk the months — left/right by one, up/down by a row of three — and\n * walking off either end of the year MOVES the year rather than stopping. PageUp /\n * PageDown step a whole year. Out-of-bounds months are `aria-disabled`, not\n * `disabled`, so the roving tab stop never falls into a hole.\n */\nfunction MonthGrid({\n value,\n onPick,\n locale,\n minKey,\n maxKey,\n currentKey,\n labels,\n}: {\n value: YearMonth | null;\n onPick: (key: string) => void;\n locale: string | undefined;\n minKey: string | null;\n maxKey: string | null;\n currentKey: string;\n labels: MonthPickerLabels;\n}) {\n const outOfBounds = (key: string) =>\n Boolean((minKey && key < minKey) || (maxKey && key > maxKey));\n\n // The month holding the tab order, and with it the year on screen — one piece of\n // state, as in MiniCalendar, so the two cannot disagree. Opens on the value, or on\n // the current month, pulled inside the bounds.\n const [active, setActive] = useState<YearMonth>(() => {\n const start = value ? toKey(value) : currentKey;\n if (minKey && start < minKey) return parseMonth(minKey)!;\n if (maxKey && start > maxKey) return parseMonth(maxKey)!;\n return parseMonth(start)!;\n });\n const year = active.year;\n\n const gridRef = useRef<HTMLDivElement>(null);\n const captionId = `${useId()}-year`;\n\n // Short names for the cells, long \"Month YYYY\" for their accessible names.\n const shortNames = useMemo(() => {\n const fmt = new Intl.DateTimeFormat(locale, { month: \"short\" });\n return Array.from({ length: 12 }, (_, i) => fmt.format(new Date(2000, i, 1)));\n }, [locale]);\n const longName = useMemo(() => new Intl.DateTimeFormat(locale, MONTH_NAME_FORMAT), [locale]);\n const yearCaption = new Intl.DateTimeFormat(locale, { year: \"numeric\" }).format(\n firstOf({ year, month: 1 }),\n );\n\n // A month whose cell must take focus after the next commit. Arrowing across a year\n // boundary re-labels the same twelve buttons, so the one to focus is a DIFFERENT\n // button from the one the key landed on.\n const pendingFocus = useRef<string | null>(null);\n useEffect(() => {\n const key = pendingFocus.current;\n if (!key) return;\n pendingFocus.current = null;\n gridRef.current?.querySelector<HTMLElement>(`[data-month=\"${key}\"]`)?.focus();\n });\n\n // Opened because the user asked to pick a month: land them on it. Runs before the\n // popover's focus trap (a child's effect fires first), which then keeps this focus.\n useEffect(() => {\n gridRef.current?.querySelector<HTMLElement>('[tabindex=\"0\"]')?.focus();\n }, []);\n\n const moveTo = (next: YearMonth, focus: boolean) => {\n if (focus) pendingFocus.current = toKey(next);\n setActive(next);\n };\n\n const onCellKeyDown = (e: KeyboardEvent<HTMLButtonElement>) => {\n // Left and right are VISUAL directions: in a right-to-left page the grid runs the\n // other way, so \"right\" is the previous month there. The panel is portalled, but\n // it carries the field's `dir` (see MonthPicker), so the nearest `dir` is right.\n const step = horizontalStep(e.key, e.currentTarget);\n const col = (active.month - 1) % COLUMNS;\n let delta: number;\n switch (step ? \"horizontal\" : e.key) {\n case \"horizontal\":\n delta = step;\n break;\n case \"ArrowUp\":\n delta = -COLUMNS;\n break;\n case \"ArrowDown\":\n delta = COLUMNS;\n break;\n case \"Home\":\n delta = -col;\n break;\n case \"End\":\n delta = COLUMNS - 1 - col;\n break;\n case \"PageUp\":\n delta = -12;\n break;\n case \"PageDown\":\n delta = 12;\n break;\n default:\n return;\n }\n // Before any no-op check: these keys also scroll the panel, and a month that\n // cannot move still must not move the page.\n e.preventDefault();\n if (delta !== 0) moveTo(shiftMonth(active, delta), true);\n };\n\n // An arrow is dead once EVERY month of the year it leads to is out of bounds.\n const prevBlocked = Boolean(minKey && toKey({ year: year - 1, month: 12 }) < minKey);\n const nextBlocked = Boolean(maxKey && toKey({ year: year + 1, month: 1 }) > maxKey);\n const selectedKey = value ? toKey(value) : null;\n\n const arrow =\n \"flex size-7 items-center justify-center rounded text-[var(--text-muted)] hover:bg-[var(--bg-hover)] disabled:cursor-not-allowed disabled:opacity-30 disabled:hover:bg-transparent\";\n\n return (\n <div className=\"select-none\">\n <div className=\"mb-1 flex items-center justify-between\">\n <button\n type=\"button\"\n aria-label={labels.previousYear}\n disabled={prevBlocked}\n // Focus stays on the arrow being pressed — only the grid moves.\n onClick={() => moveTo(shiftMonth(active, -12), false)}\n className={arrow}\n >\n {/* Mirrored in RTL, where \"previous\" points the other way. */}\n <ChevronLeft className=\"size-4 rtl:-scale-x-100\" />\n </button>\n {/* Names the grid, and `aria-live` so stepping a year says which one you are\n on — the user pressing the arrow is not inside the grid to hear it change. */}\n <div\n id={captionId}\n aria-live=\"polite\"\n className=\"text-sm font-semibold tabular-nums text-[var(--text-primary)]\"\n >\n {yearCaption}\n </div>\n <button\n type=\"button\"\n aria-label={labels.nextYear}\n disabled={nextBlocked}\n onClick={() => moveTo(shiftMonth(active, 12), false)}\n className={arrow}\n >\n <ChevronRight className=\"size-4 rtl:-scale-x-100\" />\n </button>\n </div>\n <div ref={gridRef} role=\"grid\" aria-labelledby={captionId} className=\"grid gap-1\">\n {Array.from({ length: 12 / COLUMNS }, (_, row) => (\n <div key={row} role=\"row\" className=\"grid grid-cols-3 gap-1\">\n {Array.from({ length: COLUMNS }, (_, c) => {\n const ym = { year, month: row * COLUMNS + c + 1 };\n const key = toKey(ym);\n const disabled = outOfBounds(key);\n const selected = key === selectedKey;\n const current = key === currentKey;\n return (\n <button\n key={c}\n type=\"button\"\n role=\"gridcell\"\n data-month={key}\n tabIndex={ym.month === active.month ? 0 : -1}\n aria-label={labels.month(longName.format(firstOf(ym)))}\n aria-selected={selected}\n aria-disabled={disabled || undefined}\n aria-current={current ? \"date\" : undefined}\n onClick={() => {\n // The guard `disabled` would have been; see the component note.\n if (disabled) return;\n setActive(ym);\n onPick(key);\n }}\n onKeyDown={onCellKeyDown}\n className={cn(\n \"rounded px-2 py-1.5 text-sm capitalize transition-colors\",\n FOCUS_RING_WIDTH,\n \"focus-visible:outline-none focus-visible:ring-inset\",\n // A --brand ring on a --brand fill exists only in the DOM.\n selected && !disabled\n ? \"focus-visible:ring-[var(--brand-contrast)]\"\n : \"focus-visible:ring-[var(--brand)]\",\n disabled\n ? \"cursor-not-allowed text-[var(--text-placeholder)]\"\n : selected\n ? \"bg-[var(--brand)] text-[var(--brand-contrast)] hover:bg-[var(--brand-hover)]\"\n : \"text-[var(--text-primary)] hover:bg-[var(--bg-hover)]\",\n current && !selected && !disabled && \"ring-1 ring-inset ring-[var(--border-strong)]\",\n )}\n >\n {shortNames[ym.month - 1]}\n </button>\n );\n })}\n </div>\n ))}\n </div>\n </div>\n );\n}\n\n/** Years on one page of the year grid: the month grid's 4×3. */\nconst YEAR_PAGE = 12;\n\n/**\n * `mode=\"year\"`'s panel: a page of years between ‹ › arrows — {@link MonthGrid}'s shape\n * and keyboard, one level up (kastlan 7, keksdose tax-tab.tsx:33).\n *\n * Which years a page holds:\n * - Bounded on both sides by twelve years or fewer: exactly those years, and no\n * arrows — keksdose's \"this year and four before\" is five cells, not a page of\n * twelve with seven greyed out.\n * - Otherwise twelve at a time, starting from `min` when there is one (so the first\n * page opens on it), else ending at `max`, else on multiples of twelve.\n *\n * The arrow keys walk the years (up/down by a row of three), PageUp / PageDown a page,\n * Home / End the row; walking off a page turns it. Focus stops at the bounds rather\n * than walking into years that refuse selection: a page has no \"next year\" past the\n * last selectable one worth landing on.\n */\nfunction YearGrid({\n value,\n onPick,\n locale,\n minYear,\n maxYear,\n currentYear,\n labels,\n}: {\n value: number | null;\n onPick: (key: string) => void;\n locale: string | undefined;\n minYear: number | null;\n maxYear: number | null;\n currentYear: number;\n labels: MonthPickerLabels;\n}) {\n const clamp = (y: number) => Math.min(maxYear ?? y, Math.max(minYear ?? y, y));\n // A bounded span that fits one page is shown whole.\n const whole =\n minYear !== null && maxYear !== null && maxYear >= minYear && maxYear - minYear < YEAR_PAGE\n ? { first: minYear, count: maxYear - minYear + 1 }\n : null;\n // The year holding the tab order, and with it the page on screen — one piece of\n // state, as in MonthGrid. Opens on the value, or on this year, inside the bounds.\n const [active, setActive] = useState(() => clamp(value ?? currentYear));\n const anchor = minYear ?? (maxYear !== null ? maxYear - (YEAR_PAGE - 1) : 0);\n const first = whole ? whole.first : anchor + Math.floor((active - anchor) / YEAR_PAGE) * YEAR_PAGE;\n const count = whole ? whole.count : YEAR_PAGE;\n const years = Array.from({ length: count }, (_, i) => first + i);\n const outOfBounds = (y: number) => (minYear !== null && y < minYear) || (maxYear !== null && y > maxYear);\n\n const gridRef = useRef<HTMLDivElement>(null);\n const captionId = `${useId()}-years`;\n const yearFormat = useMemo(() => new Intl.DateTimeFormat(locale, YEAR_FORMAT), [locale]);\n // \"2016–2027\" in the locale's own range punctuation and digits.\n const caption = yearFormat.formatRange(new Date(first, 0, 1), new Date(first + count - 1, 0, 1));\n\n const pendingFocus = useRef<number | null>(null);\n useEffect(() => {\n const year = pendingFocus.current;\n if (year === null) return;\n pendingFocus.current = null;\n gridRef.current?.querySelector<HTMLElement>(`[data-year=\"${year}\"]`)?.focus();\n });\n // Opened to pick a year: land on it (before the popover's trap, which keeps it).\n useEffect(() => {\n gridRef.current?.querySelector<HTMLElement>('[tabindex=\"0\"]')?.focus();\n }, []);\n\n const moveTo = (next: number, focus: boolean) => {\n const year = clamp(next);\n if (focus) pendingFocus.current = year;\n setActive(year);\n };\n\n const onCellKeyDown = (e: KeyboardEvent<HTMLButtonElement>) => {\n const step = horizontalStep(e.key, e.currentTarget);\n const col = (active - first) % COLUMNS;\n let delta: number;\n switch (step ? \"horizontal\" : e.key) {\n case \"horizontal\":\n delta = step;\n break;\n case \"ArrowUp\":\n delta = -COLUMNS;\n break;\n case \"ArrowDown\":\n delta = COLUMNS;\n break;\n case \"Home\":\n delta = -col;\n break;\n case \"End\":\n delta = Math.min(COLUMNS - 1 - col, first + count - 1 - active);\n break;\n case \"PageUp\":\n delta = -YEAR_PAGE;\n break;\n case \"PageDown\":\n delta = YEAR_PAGE;\n break;\n default:\n return;\n }\n e.preventDefault();\n if (delta !== 0) moveTo(active + delta, true);\n };\n\n const prevBlocked = minYear !== null && first - 1 < minYear;\n const nextBlocked = maxYear !== null && first + count > maxYear;\n const arrow =\n \"flex size-7 items-center justify-center rounded text-[var(--text-muted)] hover:bg-[var(--bg-hover)] disabled:cursor-not-allowed disabled:opacity-30 disabled:hover:bg-transparent\";\n\n return (\n <div className=\"select-none\">\n <div className={cn(\"mb-1 flex items-center\", whole ? \"h-7 justify-center\" : \"justify-between\")}>\n {!whole && (\n <button\n type=\"button\"\n aria-label={labels.earlierYears}\n disabled={prevBlocked}\n onClick={() => moveTo(active - YEAR_PAGE, false)}\n className={arrow}\n >\n <ChevronLeft className=\"size-4 rtl:-scale-x-100\" />\n </button>\n )}\n <div id={captionId} aria-live=\"polite\" className=\"text-sm font-semibold tabular-nums text-[var(--text-primary)]\">\n {caption}\n </div>\n {!whole && (\n <button\n type=\"button\"\n aria-label={labels.laterYears}\n disabled={nextBlocked}\n onClick={() => moveTo(active + YEAR_PAGE, false)}\n className={arrow}\n >\n <ChevronRight className=\"size-4 rtl:-scale-x-100\" />\n </button>\n )}\n </div>\n <div ref={gridRef} role=\"grid\" aria-labelledby={captionId} className=\"grid gap-1\">\n {Array.from({ length: Math.ceil(count / COLUMNS) }, (_, row) => (\n <div key={row} role=\"row\" className=\"grid grid-cols-3 gap-1\">\n {years.slice(row * COLUMNS, row * COLUMNS + COLUMNS).map((year) => {\n const disabled = outOfBounds(year);\n const selected = year === value;\n const current = year === currentYear;\n return (\n <button\n key={year}\n type=\"button\"\n role=\"gridcell\"\n data-year={year}\n tabIndex={year === active ? 0 : -1}\n aria-selected={selected}\n aria-disabled={disabled || undefined}\n aria-current={current ? \"date\" : undefined}\n onClick={() => {\n if (disabled) return;\n setActive(year);\n onPick(String(year));\n }}\n onKeyDown={onCellKeyDown}\n className={cn(\n \"rounded px-2 py-1.5 text-sm tabular-nums transition-colors\",\n FOCUS_RING_WIDTH,\n \"focus-visible:outline-none focus-visible:ring-inset\",\n selected && !disabled\n ? \"focus-visible:ring-[var(--brand-contrast)]\"\n : \"focus-visible:ring-[var(--brand)]\",\n disabled\n ? \"cursor-not-allowed text-[var(--text-placeholder)]\"\n : selected\n ? \"bg-[var(--brand)] text-[var(--brand-contrast)] hover:bg-[var(--brand-hover)]\"\n : \"text-[var(--text-primary)] hover:bg-[var(--bg-hover)]\",\n current && !selected && !disabled && \"ring-1 ring-inset ring-[var(--border-strong)]\",\n )}\n >\n {yearFormat.format(new Date(year, 0, 1))}\n </button>\n );\n })}\n </div>\n ))}\n </div>\n </div>\n );\n}\n\n// ── Field ─────────────────────────────────────────────────────────────────\n\n/**\n * Month picker: a field showing the selected month (\"August 2026\"), opening a 12-month\n * grid with year arrows. For a value that IS a month — a budget period, a calendar\n * view, a statement — where a day picker would ask for a day nobody means.\n *\n * One component for the two app-local pickers it replaces: keksdose's budget-header\n * popover (grid + year steppers + a floor) and kastlan's calendar header (a month list\n * and a year list in two popovers). The grid does both jobs — the year arrows and\n * PageUp/PageDown cover what the year list did, without a fixed ±5-year window.\n */\n/** Upper-case the first letter only, in `locale`. Several locales (fr, es, it, pt, …)\n * write month names in lower case, which at the start of a field reads as a typo —\n * but only the FIRST letter is the field's business: \"septiembre de 2026\" becomes\n * \"Septiembre de 2026\", not \"Septiembre De 2026\". */\nfunction upperFirst(text: string, locale: string | undefined): string {\n return text.charAt(0).toLocaleUpperCase(locale) + text.slice(1);\n}\n\nexport function MonthPicker(props: MonthPickerProps) {\n const {\n value,\n onChange,\n locale: localeProp,\n min,\n max,\n label,\n placeholder,\n disabled,\n invalid,\n error,\n hint,\n size = \"md\",\n mode = \"month\",\n formatOptions,\n currentMonth,\n labels: labelsProp,\n className,\n triggerClassName,\n variant = \"field\",\n headingLevel = 2,\n showToday = true,\n ...rest\n } = props;\n const labels = useMonthPickerLabels(labelsProp);\n const locale = useKitLocale(localeProp);\n const fromProvider = useKitDateFormatter();\n const years = mode === \"year\";\n const selected = years ? null : parseMonth(value);\n const selectedYear = years ? parseYear(value) : null;\n const minKey = boundKey(min);\n const maxKey = boundKey(max);\n const minYear = parseYear(min);\n const maxYear = parseYear(max);\n const currentKey = boundKey(currentMonth) ?? monthKey(new Date());\n const currentYear = parseYear(currentKey)!;\n // The key the trigger shows, in the mode's own shape: \"YYYY-MM\" or \"YYYY\".\n const shownKey = years ? (selectedYear !== null ? String(selectedYear) : null) : selected ? toKey(selected) : null;\n\n // The kit's order: `formatOptions` (this field's own Intl look), then the provider's\n // `formatDate` (keksdose K12 — the app's one way of writing a month), then the default.\n const triggerText = shownKey\n ? (formatOptions === undefined &&\n fromProvider?.(shownKey, { unit: years ? \"year\" : \"month\", source: \"monthPicker\", locale, weekday: false })) ||\n upperFirst(\n new Intl.DateTimeFormat(locale, formatOptions ?? (years ? YEAR_FORMAT : DEFAULT_TRIGGER_FORMAT)).format(\n years ? new Date(selectedYear!, 0, 1) : firstOf(selected!),\n ),\n locale,\n )\n : (placeholder ?? \"\");\n\n const id = useId();\n // keksdose K4: the caption and the error, on the trigger after the caller's own\n // description. Painted for `invalid` and a message — not for a bare `aria-invalid`,\n // which this field has never painted for.\n const messages = useFieldMessages({ hint, error, describedBy: rest[\"aria-describedby\"] });\n const [ownAria, wrapperRest] = splitTriggerAria(rest);\n // The field's trigger is described by the caption and the error too; the stepper\n // draws neither, so its heading keeps the caller's own description.\n const aria: TriggerAria = { ...ownAria, \"aria-describedby\": messages.describedBy };\n const labelId = `${id}-label`;\n const valueId = `${id}-value`;\n const panelId = `${id}-panel`;\n // Only a STRING label can double as a name; a ReactNode may hold its own controls.\n const named = typeof label === \"string\";\n // `sm` is the toolbar trigger, for an unlabelled picker only (see `size`).\n const small = size === \"sm\" && label === undefined;\n // The panel is portalled to <body>, out of the subtree whose `dir` it inherited — an\n // RTL form got an LTR grid. Read when opening (`toggle` is the popover's only way\n // open) and put back on the panel. Also what the arrow keys above read.\n const rootRef = useRef<HTMLDivElement>(null);\n const [dir, setDir] = useState<Direction>(\"ltr\");\n const panelLabel = years ? labels.yearPanel : labels.panel;\n\n const grid = (close: () => void) =>\n years ? (\n <YearGrid\n value={selectedYear}\n locale={locale}\n minYear={minYear}\n maxYear={maxYear}\n currentYear={currentYear}\n labels={labels}\n onPick={(key) => {\n onChange(key);\n close();\n }}\n />\n ) : (\n <MonthGrid\n value={selected}\n locale={locale}\n minKey={minKey}\n maxKey={maxKey}\n currentKey={currentKey}\n labels={labels}\n onPick={(key) => {\n onChange(key);\n close();\n }}\n />\n );\n\n if (variant === \"stepper\") {\n // The arrows step from the value, or from \"now\" when there is none; each is dead\n // once the month (or year) it leads to is out of bounds, and Today once \"now\" is.\n let prevKey: string;\n let nextKey: string;\n let nowKey: string;\n let outside: (key: string) => boolean;\n if (years) {\n const base = selectedYear ?? currentYear;\n prevKey = String(base - 1);\n nextKey = String(base + 1);\n nowKey = String(currentYear);\n outside = (key) => {\n const y = Number(key);\n return (minYear !== null && y < minYear) || (maxYear !== null && y > maxYear);\n };\n } else {\n const base = selected ?? parseMonth(currentKey)!;\n prevKey = toKey(shiftMonth(base, -1));\n nextKey = toKey(shiftMonth(base, 1));\n nowKey = currentKey;\n outside = (key) => Boolean((minKey && key < minKey) || (maxKey && key > maxKey));\n }\n const Heading = headingLevel ? (`h${headingLevel}` as const) : null;\n const trigger = (\n <Popover\n width=\"16rem\"\n panelId={panelId}\n labels={{ panel: panelLabel }}\n dir={dir}\n trigger={({ open, toggle, ref }) => (\n <MonthHeadingTrigger\n open={open}\n toggle={() => {\n setDir(dirOf(rootRef.current));\n toggle();\n }}\n triggerRef={ref}\n text={triggerText}\n panelId={panelId}\n disabled={disabled}\n aria={ownAria}\n />\n )}\n >\n {grid}\n </Popover>\n );\n return (\n <div {...wrapperRest} ref={rootRef} className={cn(\"flex min-w-0 items-center gap-2\", className)}>\n {Heading ? <Heading className=\"m-0 min-w-0 text-lg font-semibold\">{trigger}</Heading> : trigger}\n <div className=\"ms-auto flex shrink-0 items-center gap-1\">\n {showToday && (\n <Button\n variant=\"secondary\"\n size=\"sm\"\n disabled={disabled || outside(nowKey)}\n onClick={() => {\n if (nowKey !== shownKey) onChange(nowKey);\n }}\n >\n {years ? labels.thisYear : labels.today}\n </Button>\n )}\n <IconButton\n aria-label={years ? labels.previousYear : labels.previousMonth}\n disabled={disabled || outside(prevKey)}\n onClick={() => onChange(prevKey)}\n >\n {/* Mirrored in RTL, where \"previous\" points the other way. */}\n <ChevronLeft className=\"rtl:-scale-x-100\" />\n </IconButton>\n <IconButton\n aria-label={years ? labels.nextYear : labels.nextMonth}\n disabled={disabled || outside(nextKey)}\n onClick={() => onChange(nextKey)}\n >\n <ChevronRight className=\"rtl:-scale-x-100\" />\n </IconButton>\n </div>\n </div>\n );\n }\n\n const field = (\n // `w-fit` for the toolbar size: the calendar glyph is placed against this box, and a\n // full-width box would park it at the far end of the row, away from its trigger.\n <div {...wrapperRest} ref={rootRef} className={cn(\"relative\", small && \"w-fit max-w-full\", className)}>\n {label !== undefined && <FieldLabelLine label={label} hint={messages.labelHint} />}\n {/* The hidden twin the trigger is named by — see DateField. `sr-only-fixed`,\n and inside this `relative` root either way. */}\n {named && (\n <span id={labelId} aria-hidden className=\"sr-only-fixed\">\n {label}\n </span>\n )}\n <Popover\n width=\"16rem\"\n panelId={panelId}\n labels={{ panel: panelLabel }}\n dir={dir}\n trigger={({ open, toggle, ref }) => (\n <MonthFieldTrigger\n open={open}\n toggle={() => {\n setDir(dirOf(rootRef.current));\n toggle();\n }}\n triggerRef={ref}\n triggerText={triggerText}\n hasValue={shownKey !== null}\n // As DatePicker: a caller's reference first; an `id` without a label of our\n // own references the trigger itself, so an external <label htmlFor> names it.\n labelledBy={[\n aria[\"aria-labelledby\"],\n named ? labelId : !aria[\"aria-labelledby\"] && aria.id ? aria.id : undefined,\n valueId,\n ]\n .filter(Boolean)\n .join(\" \")}\n aria={aria}\n valueId={valueId}\n panelId={panelId}\n padded={label !== undefined}\n small={small}\n disabled={disabled}\n invalid={invalid || hasContent(error)}\n className={triggerClassName}\n />\n )}\n >\n {grid}\n </Popover>\n <Calendar\n aria-hidden\n className=\"pointer-events-none absolute end-2.5 top-1/2 size-4 -translate-y-1/2 text-[var(--text-placeholder)]\"\n />\n </div>\n );\n // An unlabelled picker has no label line: a FieldHint goes at its end, outside the\n // box, as on an unlabelled Select.\n const endHint = label === undefined ? messages.labelHint : undefined;\n return (\n <FieldBox below={messages.below} reserve={\"error\" in props || \"hint\" in props}>\n {endHint === undefined ? (\n field\n ) : (\n <div className=\"flex items-center gap-1.5\">\n <div className={cn(\"min-w-0\", !small && \"flex-1\")}>{field}</div>\n <span className=\"flex shrink-0 items-center\">{endHint}</span>\n </div>\n )}\n </FieldBox>\n );\n}\n"],"mappings":"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","names":["_"]}
|
|
1
|
+
{"version":3,"sources":["../../src/components/month-picker.tsx"],"sourcesContent":["import { useEffect, useId, useMemo, useRef, useState } from \"react\";\nimport type { ComponentPropsWithoutRef, KeyboardEvent, ReactNode, RefObject } from \"react\";\nimport { Calendar, ChevronDown, ChevronLeft, ChevronRight } from \"lucide-react\";\nimport { cn } from \"../lib/cn\";\nimport { dirOf, horizontalStep, type Direction } from \"../lib/direction\";\nimport { monthKey, pad } from \"../lib/dates\";\nimport { Button, FIELD_FLOATING_PAD, FIELD_INVALID, FIELD_TRIGGER, IconButton } from \"./ui\";\nimport { FOCUS_RING, FOCUS_RING_WIDTH } from \"./focus-ring\";\nimport { Popover } from \"./popover\";\nimport { splitTriggerAria } from \"./trigger-aria\";\nimport type { TriggerAria } from \"./trigger-aria\";\nimport { useKitDateFormatter, useKitLabels, useKitLocale } from \"../i18n/kit-labels\";\nimport { FieldBox, FieldLabelLine, hasContent, useFieldMessages } from \"./field-parts\";\n\n/**\n * Every string the month picker can speak. The month and year names are NOT here —\n * they come from `Intl` in the `locale` prop, so they are already in the user's\n * language. What is left is the two icon-only year arrows, whose `aria-label` is the\n * only name they have, the panel's name, and a month cell's name.\n */\nexport interface MonthPickerLabels {\n previousYear: string;\n nextYear: string;\n /** The popover's accessible name (`role=\"dialog\"`). */\n panel: string;\n /**\n * Accessible name of one month cell, given that month and its year formatted in\n * `locale` (\"August 2026\"). The cell's visible text is the short month alone, and\n * \"Aug\" read out on its own does not say which year the grid is on.\n */\n month: (monthYear: string) => string;\n /** `variant=\"stepper\"`: the icon-only arrows that step the value by one month. */\n previousMonth: string;\n nextMonth: string;\n /** `variant=\"stepper\"`: the button that jumps to the current month. Visible text. */\n today: string;\n /** `mode=\"year\"`: the popover's accessible name. */\n yearPanel: string;\n /** `mode=\"year\"`: the icon-only arrows that page the year grid back and forward by a\n * page of twelve years. */\n earlierYears: string;\n laterYears: string;\n /** `mode=\"year\"`, `variant=\"stepper\"`: the button that jumps to the current year.\n * Visible text. */\n thisYear: string;\n}\n\n/** The English starting point every override is merged onto. Exported (unlike the\n * calendar's) because it is asked for by name: a host building its own translation\n * table can spread it rather than re-typing four keys to change one. */\nexport const DEFAULT_MONTH_PICKER_LABELS: MonthPickerLabels = {\n previousYear: \"Previous year\",\n nextYear: \"Next year\",\n panel: \"Choose a month\",\n month: (monthYear) => monthYear,\n previousMonth: \"Previous month\",\n nextMonth: \"Next month\",\n today: \"Today\",\n yearPanel: \"Choose a year\",\n earlierYears: \"Earlier years\",\n laterYears: \"Later years\",\n thisYear: \"This year\",\n};\n\n/** Merge caller overrides onto the English defaults. */\nfunction useMonthPickerLabels(partial?: Partial<MonthPickerLabels>): MonthPickerLabels {\n // Prop > <UiKitProvider> > English, like every labelled component in the kit.\n return useKitLabels(\"monthPicker\", DEFAULT_MONTH_PICKER_LABELS, partial);\n}\n\n/**\n * `onChange` is omitted from the `<div>` attributes: the DOM event of that name carries\n * a `FormEvent`, and this one answers with a month key. Everything else reaches the\n * field's root, so a test id or a `data-tour` anchor can find it.\n */\nexport interface MonthPickerProps extends Omit<ComponentPropsWithoutRef<\"div\">, \"onChange\"> {\n /**\n * The selected month as a `\"YYYY-MM\"` key, or `\"\"` for none.\n *\n * A string rather than `{ year, month }` or a `Date`, for the reason every date in\n * this kit is one: it is what `monthKey()` in `lib/dates` produces, it sorts\n * chronologically so the bounds are plain `<` / `>`, and it crosses a URL or an API\n * without a serialiser. A `{ year, month }` caller converts with\n * `` `${year}-${pad(month)}` `` (month 1–12) and back with `key.split(\"-\").map(Number)`.\n */\n value: string;\n /** Called with the picked month as `\"YYYY-MM\"`. */\n onChange: (month: string) => void;\n /** Drives the month and year names (`Intl.DateTimeFormat`). Defaults to the\n * `<UiKitProvider>` locale, then the runtime's. */\n locale?: string;\n /**\n * Inclusive bounds, as `\"YYYY-MM\"`. A full ISO date (`\"YYYY-MM-DD\"`) is accepted and\n * read as its month, so a host holding \"the first day we have data for\" can pass it\n * straight in. Months outside are shown but refuse selection; a year arrow is\n * disabled once the whole year beyond it is out of bounds.\n */\n min?: string;\n max?: string;\n /** Embedded top-boundary label, matching the other field-shaped controls. */\n label?: ReactNode;\n /** Trigger text when `value` is `\"\"`. */\n placeholder?: string;\n disabled?: boolean;\n /** Required and unanswered — {@link FIELD_INVALID}. See {@link Input}'s `invalid`. */\n invalid?: boolean;\n /**\n * What is wrong with the month, in the caller's words — {@link Input}'s `error`\n * (keksdose K4): under the field, attached to the trigger with `aria-describedby`\n * (merged, after any caption), and implies `invalid`. Passing the key keeps the\n * field's box while there is no message, so the trigger is never remounted.\n */\n error?: ReactNode;\n /** Standing advice, as on a {@link Select}: text is a caption under the field; a\n * {@link FieldHint} rides the label line. */\n hint?: ReactNode;\n /**\n * `\"md\"` (default): the form field. `\"sm\"`: the toolbar trigger — 36px tall (an\n * `IconButton`'s height) and as wide as the month it shows, for a picker that sits\n * between two icon buttons in a page header (keksdose K13: the budget header wrote\n * `triggerClassName=\"h-9 w-auto … py-0\"` by hand, live #260 — 34px between two 36px\n * buttons, *\"the kind of 2px that reads as sloppy\"*). Unlabelled pickers only, as on\n * {@link Select}: a floating label needs the tall box to float in. The width follows\n * the month's name; add a `min-w-*` in `triggerClassName` if the buttons beside it\n * must not move as it changes.\n */\n size?: \"sm\" | \"md\";\n /**\n * `\"month\"` (default): the value is a month, `\"YYYY-MM\"`.\n *\n * `\"year\"`: the value is a YEAR, `\"YYYY\"`, and the panel is a grid of years — kastlan's\n * budget fiscal year (a `Select` of 2020 to three years ahead) and keksdose's tax tab\n * (\"this year and four before\", tax-tab.tsx:33). `min` / `max` are read as their year\n * (`\"2022\"`, or any longer key). A bounded span of twelve years or fewer is shown\n * whole, with no paging; otherwise the grid pages twelve years at a time (PageUp /\n * PageDown, or the arrows), starting from `min` when there is one. The stepper\n * variant steps a year, and its Today button reads \"This year\".\n *\n * A string rather than a number, for the reason the month key is one: it is the\n * same type across both modes, it crosses a URL as is, and it sorts. A numeric\n * caller converts with `String(year)` and `Number(key)`.\n */\n mode?: \"month\" | \"year\";\n /** Intl options for the trigger's text (default: long month + numeric year, or the\n * numeric year in `mode=\"year\"`). Win over the provider's `formatDate`. */\n formatOptions?: Intl.DateTimeFormatOptions;\n /**\n * The month to ring as \"now\", as `\"YYYY-MM\"`. Defaults to the local calendar's\n * current month; pass one to pin it (a test, a host with its own notion of today).\n */\n currentMonth?: string;\n /** User-facing strings; see {@link MonthPickerLabels}. */\n labels?: Partial<MonthPickerLabels>;\n /** Extra classes for the field's root. */\n className?: string;\n /** Extra classes for the trigger button — e.g. a compact `h-9 w-auto` trigger\n * sitting between two icon buttons in a toolbar. */\n triggerClassName?: string;\n /**\n * `\"field\"` (default): the field-shaped trigger, for a form.\n *\n * `\"stepper\"`: the header of a month PAGE — the month as a heading that opens the\n * grid, then Today and ‹ › stepping the value by one month. What a calendar page\n * assembled by hand around the field (overriding its trigger to look like a\n * heading). `label`, `placeholder`, `invalid`, `hint`, `error`, `size` and\n * `triggerClassName` are the field's and are not drawn here; with no `value` the\n * arrows step from the current month (or year).\n */\n variant?: \"field\" | \"stepper\";\n /** `variant=\"stepper\"`: the heading level the month is set in (`<h2>` by default), or\n * `false` for no heading element — the page already has one for the month. */\n headingLevel?: 1 | 2 | 3 | 4 | 5 | 6 | false;\n /** `variant=\"stepper\"`: draw the Today button. Default `true`. */\n showToday?: boolean;\n}\n\n// ── Month keys ────────────────────────────────────────────────────────────\n\ninterface YearMonth {\n year: number;\n /** 1–12, as in the key. */\n month: number;\n}\n\nconst KEY = /^(\\d{4})-(\\d{2})/;\n\nfunction parseMonth(key: string | undefined): YearMonth | null {\n const m = key ? KEY.exec(key) : null;\n if (!m) return null;\n const month = Number(m[2]);\n return month >= 1 && month <= 12 ? { year: Number(m[1]), month } : null;\n}\n\nfunction toKey({ year, month }: YearMonth): string {\n return `${year}-${pad(month)}`;\n}\n\n/** Move a month key by whole months, carrying into the year. */\nfunction shiftMonth(ym: YearMonth, delta: number): YearMonth {\n const index = ym.year * 12 + (ym.month - 1) + delta;\n return { year: Math.floor(index / 12), month: (index % 12) + 1 };\n}\n\n/** A bound, normalised to its month key — so `\"2024-03-15\"` compares as `\"2024-03\"`. */\nfunction boundKey(bound: string | undefined): string | null {\n const ym = parseMonth(bound);\n return ym ? toKey(ym) : null;\n}\n\n/** Local-calendar Date for the 1st of a month, for `Intl` to format. */\nfunction firstOf({ year, month }: YearMonth): Date {\n return new Date(year, month - 1, 1);\n}\n\nconst DEFAULT_TRIGGER_FORMAT: Intl.DateTimeFormatOptions = { month: \"long\", year: \"numeric\" };\nconst MONTH_NAME_FORMAT: Intl.DateTimeFormatOptions = { month: \"long\", year: \"numeric\" };\nconst YEAR_FORMAT: Intl.DateTimeFormatOptions = { year: \"numeric\" };\n\n// ── Year keys ─────────────────────────────────────────────────────────────\n\nconst YEAR = /^(\\d{4})/;\n\n/** The year of `\"YYYY\"`, `\"YYYY-MM\"` or `\"YYYY-MM-DD\"`, or null. */\nfunction parseYear(key: string | undefined): number | null {\n const m = key ? YEAR.exec(key) : null;\n return m ? Number(m[1]) : null;\n}\n\n// ── Trigger ───────────────────────────────────────────────────────────────\n\n/** Hand focus back to the trigger when the panel closes with focus still inside it. */\nfunction useFocusBackOnClose(open: boolean, triggerRef: RefObject<HTMLButtonElement | null>) {\n const wasOpen = useRef(false);\n useEffect(() => {\n const justClosed = wasOpen.current && !open;\n wasOpen.current = open;\n if (!justClosed) return;\n // Only when focus went down with the panel. An outside click has already put it\n // somewhere real, and dragging it back here would be worse than leaving it.\n const active = document.activeElement;\n if (!active || active === document.body) triggerRef.current?.focus();\n }, [open, triggerRef]);\n}\n\n/**\n * The stepper's trigger: the month as a heading-sized button that opens the grid.\n *\n * Its own text is its name (\"September 2026\"), so it needs no label of its own, and\n * it is `aria-live` for the reason `MiniCalendar`'s caption is: pressing ‹ or › leaves\n * focus on the arrow, and the heading is the only thing that says where the arrow\n * went. A plain button with `aria-haspopup`, not the field's `combobox` — it does not\n * look or behave like a field, and a heading reading \"combobox\" is a surprise.\n */\nfunction MonthHeadingTrigger({\n open,\n toggle,\n triggerRef,\n text,\n panelId,\n disabled,\n aria,\n}: {\n open: boolean;\n toggle: () => void;\n triggerRef: RefObject<HTMLButtonElement | null>;\n text: string;\n panelId: string;\n disabled?: boolean;\n aria: TriggerAria;\n}) {\n useFocusBackOnClose(open, triggerRef);\n return (\n <button\n ref={triggerRef}\n type=\"button\"\n disabled={disabled}\n onClick={toggle}\n aria-haspopup=\"dialog\"\n aria-controls={panelId}\n aria-expanded={open}\n id={aria.id}\n aria-label={aria[\"aria-label\"]}\n aria-labelledby={aria[\"aria-labelledby\"]}\n aria-describedby={aria[\"aria-describedby\"]}\n className={cn(\n \"-ms-1.5 inline-flex h-9 min-w-0 items-center gap-1 rounded-md px-1.5 text-lg font-semibold text-[var(--text-primary)] transition-colors\",\n \"hover:bg-[var(--bg-hover)]\",\n FOCUS_RING,\n disabled && \"cursor-not-allowed opacity-50\",\n )}\n >\n <span aria-live=\"polite\" className=\"truncate\">\n {text || \"\\u00a0\"}\n </span>\n <ChevronDown aria-hidden className=\"size-4 shrink-0 text-[var(--text-muted)]\" />\n </button>\n );\n}\n\n/**\n * The field-shaped button that opens the grid. The same shape as `DatePicker`'s\n * trigger, for the same reasons written down there: `role=\"combobox\"` so it may carry\n * `aria-expanded` / `aria-invalid`, a name made of the label AND the value, and focus\n * handed back here when the panel closes with focus still inside it.\n */\nfunction MonthFieldTrigger({\n open,\n toggle,\n triggerRef,\n triggerText,\n hasValue,\n labelledBy,\n valueId,\n panelId,\n padded,\n small,\n disabled,\n invalid,\n className,\n aria,\n}: {\n open: boolean;\n toggle: () => void;\n triggerRef: RefObject<HTMLButtonElement | null>;\n triggerText: string;\n hasValue: boolean;\n labelledBy: string;\n /** The caller's naming attributes — see `trigger-aria.ts`. */\n aria: TriggerAria;\n valueId: string;\n panelId: string;\n padded: boolean;\n /** `size=\"sm\"`: the 36px, content-wide toolbar trigger. */\n small: boolean;\n disabled?: boolean;\n invalid?: boolean;\n className?: string;\n}) {\n useFocusBackOnClose(open, triggerRef);\n\n return (\n <button\n ref={triggerRef}\n type=\"button\"\n disabled={disabled}\n onClick={toggle}\n role=\"combobox\"\n aria-haspopup=\"dialog\"\n aria-controls={panelId}\n aria-expanded={open}\n id={aria.id}\n aria-invalid={invalid || aria[\"aria-invalid\"] === true || aria[\"aria-invalid\"] === \"true\" || undefined}\n aria-required={aria[\"aria-required\"]}\n aria-labelledby={aria[\"aria-label\"] && !aria[\"aria-labelledby\"] ? undefined : labelledBy}\n aria-label={aria[\"aria-label\"]}\n aria-describedby={aria[\"aria-describedby\"]}\n className={cn(\n FIELD_TRIGGER,\n \"pe-9\",\n padded && FIELD_FLOATING_PAD,\n // keksdose K13 (live #260): an IconButton's 36px, and only as wide as the\n // month — `py-0` with a fixed height so the box is 36px whatever the type's\n // line height. `pe-9` stays: the calendar glyph still sits at the end.\n small && \"h-9 w-auto py-0\",\n disabled && \"cursor-not-allowed opacity-50\",\n invalid && FIELD_INVALID,\n className,\n )}\n >\n {/* The U+00A0 fallback keeps the line box when there is no text — see\n DatePicker. The value arrives capitalised (see `upperFirst`); this span does\n NOT use `capitalize`, which upper-cases every WORD — \"Choose A Month\",\n \"Septiembre De 2026\". */}\n <span\n id={valueId}\n className={cn(\"truncate\", !hasValue && \"text-[var(--text-placeholder)]\")}\n >\n {triggerText || \" \"}\n </span>\n </button>\n );\n}\n\n// ── Grid ──────────────────────────────────────────────────────────────────\n\nconst COLUMNS = 3;\n\n/**\n * The panel: a year caption between ‹ › arrows, over a 4×3 grid of months.\n *\n * Mounted only while the popover is open, so every opening starts from the current\n * value's year — a value moved from outside (a prev/next-month button beside the\n * field) is where the grid lands, with no effect needed to snap it back.\n *\n * A grid in the APG sense, like `MiniCalendar`: one tab stop (the roving tabindex),\n * the arrows walk the months — left/right by one, up/down by a row of three — and\n * walking off either end of the year MOVES the year rather than stopping. PageUp /\n * PageDown step a whole year. Out-of-bounds months are `aria-disabled`, not\n * `disabled`, so the roving tab stop never falls into a hole.\n */\nfunction MonthGrid({\n value,\n onPick,\n locale,\n minKey,\n maxKey,\n currentKey,\n labels,\n}: {\n value: YearMonth | null;\n onPick: (key: string) => void;\n locale: string | undefined;\n minKey: string | null;\n maxKey: string | null;\n currentKey: string;\n labels: MonthPickerLabels;\n}) {\n const outOfBounds = (key: string) =>\n Boolean((minKey && key < minKey) || (maxKey && key > maxKey));\n\n // The month holding the tab order, and with it the year on screen — one piece of\n // state, as in MiniCalendar, so the two cannot disagree. Opens on the value, or on\n // the current month, pulled inside the bounds.\n const [active, setActive] = useState<YearMonth>(() => {\n const start = value ? toKey(value) : currentKey;\n if (minKey && start < minKey) return parseMonth(minKey)!;\n if (maxKey && start > maxKey) return parseMonth(maxKey)!;\n return parseMonth(start)!;\n });\n const year = active.year;\n\n const gridRef = useRef<HTMLDivElement>(null);\n const captionId = `${useId()}-year`;\n\n // Short names for the cells, long \"Month YYYY\" for their accessible names.\n const shortNames = useMemo(() => {\n const fmt = new Intl.DateTimeFormat(locale, { month: \"short\" });\n return Array.from({ length: 12 }, (_, i) => fmt.format(new Date(2000, i, 1)));\n }, [locale]);\n const longName = useMemo(() => new Intl.DateTimeFormat(locale, MONTH_NAME_FORMAT), [locale]);\n const yearCaption = new Intl.DateTimeFormat(locale, { year: \"numeric\" }).format(\n firstOf({ year, month: 1 }),\n );\n\n // A month whose cell must take focus after the next commit. Arrowing across a year\n // boundary re-labels the same twelve buttons, so the one to focus is a DIFFERENT\n // button from the one the key landed on.\n const pendingFocus = useRef<string | null>(null);\n useEffect(() => {\n const key = pendingFocus.current;\n if (!key) return;\n pendingFocus.current = null;\n gridRef.current?.querySelector<HTMLElement>(`[data-month=\"${key}\"]`)?.focus();\n });\n\n // Opened because the user asked to pick a month: land them on it. Runs before the\n // popover's focus trap (a child's effect fires first), which then keeps this focus.\n useEffect(() => {\n gridRef.current?.querySelector<HTMLElement>('[tabindex=\"0\"]')?.focus();\n }, []);\n\n const moveTo = (next: YearMonth, focus: boolean) => {\n if (focus) pendingFocus.current = toKey(next);\n setActive(next);\n };\n\n const onCellKeyDown = (e: KeyboardEvent<HTMLButtonElement>) => {\n // Left and right are VISUAL directions: in a right-to-left page the grid runs the\n // other way, so \"right\" is the previous month there. The panel is portalled, but\n // it carries the field's `dir` (see MonthPicker), so the nearest `dir` is right.\n const step = horizontalStep(e.key, e.currentTarget);\n const col = (active.month - 1) % COLUMNS;\n let delta: number;\n switch (step ? \"horizontal\" : e.key) {\n case \"horizontal\":\n delta = step;\n break;\n case \"ArrowUp\":\n delta = -COLUMNS;\n break;\n case \"ArrowDown\":\n delta = COLUMNS;\n break;\n case \"Home\":\n delta = -col;\n break;\n case \"End\":\n delta = COLUMNS - 1 - col;\n break;\n case \"PageUp\":\n delta = -12;\n break;\n case \"PageDown\":\n delta = 12;\n break;\n default:\n return;\n }\n // Before any no-op check: these keys also scroll the panel, and a month that\n // cannot move still must not move the page.\n e.preventDefault();\n if (delta !== 0) moveTo(shiftMonth(active, delta), true);\n };\n\n // An arrow is dead once EVERY month of the year it leads to is out of bounds.\n const prevBlocked = Boolean(minKey && toKey({ year: year - 1, month: 12 }) < minKey);\n const nextBlocked = Boolean(maxKey && toKey({ year: year + 1, month: 1 }) > maxKey);\n const selectedKey = value ? toKey(value) : null;\n\n const arrow =\n \"flex size-7 items-center justify-center rounded text-[var(--text-muted)] hover:bg-[var(--bg-hover)] disabled:cursor-not-allowed disabled:opacity-30 disabled:hover:bg-transparent\";\n\n return (\n <div className=\"select-none\">\n <div className=\"mb-1 flex items-center justify-between\">\n <button\n type=\"button\"\n aria-label={labels.previousYear}\n disabled={prevBlocked}\n // Focus stays on the arrow being pressed — only the grid moves.\n onClick={() => moveTo(shiftMonth(active, -12), false)}\n className={arrow}\n >\n {/* Mirrored in RTL, where \"previous\" points the other way. */}\n <ChevronLeft className=\"size-4 rtl:-scale-x-100\" />\n </button>\n {/* Names the grid, and `aria-live` so stepping a year says which one you are\n on — the user pressing the arrow is not inside the grid to hear it change. */}\n <div\n id={captionId}\n aria-live=\"polite\"\n className=\"text-sm font-semibold tabular-nums text-[var(--text-primary)]\"\n >\n {yearCaption}\n </div>\n <button\n type=\"button\"\n aria-label={labels.nextYear}\n disabled={nextBlocked}\n onClick={() => moveTo(shiftMonth(active, 12), false)}\n className={arrow}\n >\n <ChevronRight className=\"size-4 rtl:-scale-x-100\" />\n </button>\n </div>\n <div ref={gridRef} role=\"grid\" aria-labelledby={captionId} className=\"grid gap-1\">\n {Array.from({ length: 12 / COLUMNS }, (_, row) => (\n <div key={row} role=\"row\" className=\"grid grid-cols-3 gap-1\">\n {Array.from({ length: COLUMNS }, (_, c) => {\n const ym = { year, month: row * COLUMNS + c + 1 };\n const key = toKey(ym);\n const disabled = outOfBounds(key);\n const selected = key === selectedKey;\n const current = key === currentKey;\n return (\n <button\n key={c}\n type=\"button\"\n role=\"gridcell\"\n data-month={key}\n tabIndex={ym.month === active.month ? 0 : -1}\n aria-label={labels.month(longName.format(firstOf(ym)))}\n aria-selected={selected}\n aria-disabled={disabled || undefined}\n aria-current={current ? \"date\" : undefined}\n onClick={() => {\n // The guard `disabled` would have been; see the component note.\n if (disabled) return;\n setActive(ym);\n onPick(key);\n }}\n onKeyDown={onCellKeyDown}\n className={cn(\n \"rounded px-2 py-1.5 text-sm capitalize transition-colors\",\n FOCUS_RING_WIDTH,\n \"focus-visible:outline-none focus-visible:ring-inset\",\n // A --brand ring on a --brand fill exists only in the DOM.\n selected && !disabled\n ? \"focus-visible:ring-[var(--brand-contrast)]\"\n : \"focus-visible:ring-[var(--brand)]\",\n disabled\n ? \"cursor-not-allowed text-[var(--text-placeholder)]\"\n : selected\n ? \"bg-[var(--brand)] text-[var(--brand-contrast)] hover:bg-[var(--brand-hover)]\"\n : \"text-[var(--text-primary)] hover:bg-[var(--bg-hover)]\",\n current && !selected && !disabled && \"ring-1 ring-inset ring-[var(--border-strong)]\",\n )}\n >\n {shortNames[ym.month - 1]}\n </button>\n );\n })}\n </div>\n ))}\n </div>\n </div>\n );\n}\n\n/** Years on one page of the year grid: the month grid's 4×3. */\nconst YEAR_PAGE = 12;\n\n/**\n * `mode=\"year\"`'s panel: a page of years between ‹ › arrows — {@link MonthGrid}'s shape\n * and keyboard, one level up (kastlan 7, keksdose tax-tab.tsx:33).\n *\n * Which years a page holds:\n * - Bounded on both sides by twelve years or fewer: exactly those years, and no\n * arrows — keksdose's \"this year and four before\" is five cells, not a page of\n * twelve with seven greyed out.\n * - Otherwise twelve at a time, starting from `min` when there is one (so the first\n * page opens on it), else ending at `max`, else on multiples of twelve.\n *\n * The arrow keys walk the years (up/down by a row of three), PageUp / PageDown a page,\n * Home / End the row; walking off a page turns it. Focus stops at the bounds rather\n * than walking into years that refuse selection: a page has no \"next year\" past the\n * last selectable one worth landing on.\n */\nfunction YearGrid({\n value,\n onPick,\n locale,\n minYear,\n maxYear,\n currentYear,\n labels,\n}: {\n value: number | null;\n onPick: (key: string) => void;\n locale: string | undefined;\n minYear: number | null;\n maxYear: number | null;\n currentYear: number;\n labels: MonthPickerLabels;\n}) {\n const clamp = (y: number) => Math.min(maxYear ?? y, Math.max(minYear ?? y, y));\n // A bounded span that fits one page is shown whole.\n const whole =\n minYear !== null && maxYear !== null && maxYear >= minYear && maxYear - minYear < YEAR_PAGE\n ? { first: minYear, count: maxYear - minYear + 1 }\n : null;\n // The year holding the tab order, and with it the page on screen — one piece of\n // state, as in MonthGrid. Opens on the value, or on this year, inside the bounds.\n const [active, setActive] = useState(() => clamp(value ?? currentYear));\n const anchor = minYear ?? (maxYear !== null ? maxYear - (YEAR_PAGE - 1) : 0);\n const first = whole ? whole.first : anchor + Math.floor((active - anchor) / YEAR_PAGE) * YEAR_PAGE;\n const count = whole ? whole.count : YEAR_PAGE;\n const years = Array.from({ length: count }, (_, i) => first + i);\n const outOfBounds = (y: number) => (minYear !== null && y < minYear) || (maxYear !== null && y > maxYear);\n\n const gridRef = useRef<HTMLDivElement>(null);\n const captionId = `${useId()}-years`;\n const yearFormat = useMemo(() => new Intl.DateTimeFormat(locale, YEAR_FORMAT), [locale]);\n // \"2016–2027\" in the locale's own range punctuation and digits.\n const caption = yearFormat.formatRange(new Date(first, 0, 1), new Date(first + count - 1, 0, 1));\n\n const pendingFocus = useRef<number | null>(null);\n useEffect(() => {\n const year = pendingFocus.current;\n if (year === null) return;\n pendingFocus.current = null;\n gridRef.current?.querySelector<HTMLElement>(`[data-year=\"${year}\"]`)?.focus();\n });\n // Opened to pick a year: land on it (before the popover's trap, which keeps it).\n useEffect(() => {\n gridRef.current?.querySelector<HTMLElement>('[tabindex=\"0\"]')?.focus();\n }, []);\n\n const moveTo = (next: number, focus: boolean) => {\n const year = clamp(next);\n if (focus) pendingFocus.current = year;\n setActive(year);\n };\n\n const onCellKeyDown = (e: KeyboardEvent<HTMLButtonElement>) => {\n const step = horizontalStep(e.key, e.currentTarget);\n const col = (active - first) % COLUMNS;\n let delta: number;\n switch (step ? \"horizontal\" : e.key) {\n case \"horizontal\":\n delta = step;\n break;\n case \"ArrowUp\":\n delta = -COLUMNS;\n break;\n case \"ArrowDown\":\n delta = COLUMNS;\n break;\n case \"Home\":\n delta = -col;\n break;\n case \"End\":\n delta = Math.min(COLUMNS - 1 - col, first + count - 1 - active);\n break;\n case \"PageUp\":\n delta = -YEAR_PAGE;\n break;\n case \"PageDown\":\n delta = YEAR_PAGE;\n break;\n default:\n return;\n }\n e.preventDefault();\n if (delta !== 0) moveTo(active + delta, true);\n };\n\n const prevBlocked = minYear !== null && first - 1 < minYear;\n const nextBlocked = maxYear !== null && first + count > maxYear;\n const arrow =\n \"flex size-7 items-center justify-center rounded text-[var(--text-muted)] hover:bg-[var(--bg-hover)] disabled:cursor-not-allowed disabled:opacity-30 disabled:hover:bg-transparent\";\n\n return (\n <div className=\"select-none\">\n <div className={cn(\"mb-1 flex items-center\", whole ? \"h-7 justify-center\" : \"justify-between\")}>\n {!whole && (\n <button\n type=\"button\"\n aria-label={labels.earlierYears}\n disabled={prevBlocked}\n onClick={() => moveTo(active - YEAR_PAGE, false)}\n className={arrow}\n >\n <ChevronLeft className=\"size-4 rtl:-scale-x-100\" />\n </button>\n )}\n <div id={captionId} aria-live=\"polite\" className=\"text-sm font-semibold tabular-nums text-[var(--text-primary)]\">\n {caption}\n </div>\n {!whole && (\n <button\n type=\"button\"\n aria-label={labels.laterYears}\n disabled={nextBlocked}\n onClick={() => moveTo(active + YEAR_PAGE, false)}\n className={arrow}\n >\n <ChevronRight className=\"size-4 rtl:-scale-x-100\" />\n </button>\n )}\n </div>\n <div ref={gridRef} role=\"grid\" aria-labelledby={captionId} className=\"grid gap-1\">\n {Array.from({ length: Math.ceil(count / COLUMNS) }, (_, row) => (\n <div key={row} role=\"row\" className=\"grid grid-cols-3 gap-1\">\n {years.slice(row * COLUMNS, row * COLUMNS + COLUMNS).map((year) => {\n const disabled = outOfBounds(year);\n const selected = year === value;\n const current = year === currentYear;\n return (\n <button\n key={year}\n type=\"button\"\n role=\"gridcell\"\n data-year={year}\n tabIndex={year === active ? 0 : -1}\n aria-selected={selected}\n aria-disabled={disabled || undefined}\n aria-current={current ? \"date\" : undefined}\n onClick={() => {\n if (disabled) return;\n setActive(year);\n onPick(String(year));\n }}\n onKeyDown={onCellKeyDown}\n className={cn(\n \"rounded px-2 py-1.5 text-sm tabular-nums transition-colors\",\n FOCUS_RING_WIDTH,\n \"focus-visible:outline-none focus-visible:ring-inset\",\n selected && !disabled\n ? \"focus-visible:ring-[var(--brand-contrast)]\"\n : \"focus-visible:ring-[var(--brand)]\",\n disabled\n ? \"cursor-not-allowed text-[var(--text-placeholder)]\"\n : selected\n ? \"bg-[var(--brand)] text-[var(--brand-contrast)] hover:bg-[var(--brand-hover)]\"\n : \"text-[var(--text-primary)] hover:bg-[var(--bg-hover)]\",\n current && !selected && !disabled && \"ring-1 ring-inset ring-[var(--border-strong)]\",\n )}\n >\n {yearFormat.format(new Date(year, 0, 1))}\n </button>\n );\n })}\n </div>\n ))}\n </div>\n </div>\n );\n}\n\n// ── Field ─────────────────────────────────────────────────────────────────\n\n/**\n * Month picker: a field showing the selected month (\"August 2026\"), opening a 12-month\n * grid with year arrows. For a value that IS a month — a budget period, a calendar\n * view, a statement — where a day picker would ask for a day nobody means.\n *\n * One component for the two app-local pickers it replaces: keksdose's budget-header\n * popover (grid + year steppers + a floor) and kastlan's calendar header (a month list\n * and a year list in two popovers). The grid does both jobs — the year arrows and\n * PageUp/PageDown cover what the year list did, without a fixed ±5-year window.\n */\n/** Upper-case the first letter only, in `locale`. Several locales (fr, es, it, pt, …)\n * write month names in lower case, which at the start of a field reads as a typo —\n * but only the FIRST letter is the field's business: \"septiembre de 2026\" becomes\n * \"Septiembre de 2026\", not \"Septiembre De 2026\". */\nfunction upperFirst(text: string, locale: string | undefined): string {\n return text.charAt(0).toLocaleUpperCase(locale) + text.slice(1);\n}\n\nexport function MonthPicker(props: MonthPickerProps) {\n const {\n value,\n onChange,\n locale: localeProp,\n min,\n max,\n label,\n placeholder,\n disabled,\n invalid,\n error,\n hint,\n size = \"md\",\n mode = \"month\",\n formatOptions,\n currentMonth,\n labels: labelsProp,\n className,\n triggerClassName,\n variant = \"field\",\n headingLevel = 2,\n showToday = true,\n ...rest\n } = props;\n const labels = useMonthPickerLabels(labelsProp);\n const locale = useKitLocale(localeProp);\n const fromProvider = useKitDateFormatter();\n const years = mode === \"year\";\n const selected = years ? null : parseMonth(value);\n const selectedYear = years ? parseYear(value) : null;\n const minKey = boundKey(min);\n const maxKey = boundKey(max);\n const minYear = parseYear(min);\n const maxYear = parseYear(max);\n const currentKey = boundKey(currentMonth) ?? monthKey(new Date());\n const currentYear = parseYear(currentKey)!;\n // The key the trigger shows, in the mode's own shape: \"YYYY-MM\" or \"YYYY\".\n const shownKey = years ? (selectedYear !== null ? String(selectedYear) : null) : selected ? toKey(selected) : null;\n\n // The kit's order: `formatOptions` (this field's own Intl look), then the provider's\n // `formatDate` (keksdose K12 — the app's one way of writing a month), then the default.\n const triggerText = shownKey\n ? (formatOptions === undefined &&\n fromProvider?.(shownKey, { unit: years ? \"year\" : \"month\", source: \"monthPicker\", locale, weekday: false })) ||\n upperFirst(\n new Intl.DateTimeFormat(locale, formatOptions ?? (years ? YEAR_FORMAT : DEFAULT_TRIGGER_FORMAT)).format(\n years ? new Date(selectedYear!, 0, 1) : firstOf(selected!),\n ),\n locale,\n )\n : (placeholder ?? \"\");\n\n const id = useId();\n // keksdose K4: the caption and the error, on the trigger after the caller's own\n // description. Painted for `invalid` and a message — not for a bare `aria-invalid`,\n // which this field has never painted for.\n const messages = useFieldMessages({ hint, error, describedBy: rest[\"aria-describedby\"] });\n const [ownAria, wrapperRest] = splitTriggerAria(rest);\n // The field's trigger is described by the caption and the error too; the stepper\n // draws neither, so its heading keeps the caller's own description.\n const aria: TriggerAria = { ...ownAria, \"aria-describedby\": messages.describedBy };\n const labelId = `${id}-label`;\n const valueId = `${id}-value`;\n const panelId = `${id}-panel`;\n // Only a STRING label can double as a name; a ReactNode may hold its own controls.\n const named = typeof label === \"string\";\n // `sm` is the toolbar trigger, for an unlabelled picker only (see `size`).\n const small = size === \"sm\" && label === undefined;\n // The panel is portalled to <body>, out of the subtree whose `dir` it inherited — an\n // RTL form got an LTR grid. Read when opening (`toggle` is the popover's only way\n // open) and put back on the panel. Also what the arrow keys above read.\n const rootRef = useRef<HTMLDivElement>(null);\n const [dir, setDir] = useState<Direction>(\"ltr\");\n const panelLabel = years ? labels.yearPanel : labels.panel;\n\n const grid = (close: () => void) =>\n years ? (\n <YearGrid\n value={selectedYear}\n locale={locale}\n minYear={minYear}\n maxYear={maxYear}\n currentYear={currentYear}\n labels={labels}\n onPick={(key) => {\n onChange(key);\n close();\n }}\n />\n ) : (\n <MonthGrid\n value={selected}\n locale={locale}\n minKey={minKey}\n maxKey={maxKey}\n currentKey={currentKey}\n labels={labels}\n onPick={(key) => {\n onChange(key);\n close();\n }}\n />\n );\n\n if (variant === \"stepper\") {\n // The arrows step from the value, or from \"now\" when there is none; each is dead\n // once the month (or year) it leads to is out of bounds, and Today once \"now\" is.\n let prevKey: string;\n let nextKey: string;\n let nowKey: string;\n let outside: (key: string) => boolean;\n if (years) {\n const base = selectedYear ?? currentYear;\n prevKey = String(base - 1);\n nextKey = String(base + 1);\n nowKey = String(currentYear);\n outside = (key) => {\n const y = Number(key);\n return (minYear !== null && y < minYear) || (maxYear !== null && y > maxYear);\n };\n } else {\n const base = selected ?? parseMonth(currentKey)!;\n prevKey = toKey(shiftMonth(base, -1));\n nextKey = toKey(shiftMonth(base, 1));\n nowKey = currentKey;\n outside = (key) => Boolean((minKey && key < minKey) || (maxKey && key > maxKey));\n }\n const Heading = headingLevel ? (`h${headingLevel}` as const) : null;\n const trigger = (\n <Popover\n width=\"16rem\"\n panelId={panelId}\n labels={{ panel: panelLabel }}\n dir={dir}\n trigger={({ open, toggle, ref }) => (\n <MonthHeadingTrigger\n open={open}\n toggle={() => {\n setDir(dirOf(rootRef.current));\n toggle();\n }}\n triggerRef={ref}\n text={triggerText}\n panelId={panelId}\n disabled={disabled}\n aria={ownAria}\n />\n )}\n >\n {grid}\n </Popover>\n );\n return (\n // `flex-wrap`: where the heading and the buttons do not fit one row, the buttons\n // take the next line, at the end (0.32.1, the 360 px Extra-large sweep;\n // docs/text-size-harmonization.md §4). In one row at 150 % \"Today ‹ ›\" left the\n // heading 30 px on a 360 px phone, and \"October 2026\" was drawn under \"Today\".\n // Where they fit, nothing changes.\n <div {...wrapperRest} ref={rootRef} className={cn(\"flex min-w-0 flex-wrap items-center gap-2\", className)}>\n {Heading ? <Heading className=\"m-0 min-w-0 text-lg font-semibold\">{trigger}</Heading> : trigger}\n <div className=\"ms-auto flex shrink-0 items-center gap-1\">\n {showToday && (\n <Button\n variant=\"secondary\"\n size=\"sm\"\n disabled={disabled || outside(nowKey)}\n onClick={() => {\n if (nowKey !== shownKey) onChange(nowKey);\n }}\n >\n {years ? labels.thisYear : labels.today}\n </Button>\n )}\n <IconButton\n aria-label={years ? labels.previousYear : labels.previousMonth}\n disabled={disabled || outside(prevKey)}\n onClick={() => onChange(prevKey)}\n >\n {/* Mirrored in RTL, where \"previous\" points the other way. */}\n <ChevronLeft className=\"rtl:-scale-x-100\" />\n </IconButton>\n <IconButton\n aria-label={years ? labels.nextYear : labels.nextMonth}\n disabled={disabled || outside(nextKey)}\n onClick={() => onChange(nextKey)}\n >\n <ChevronRight className=\"rtl:-scale-x-100\" />\n </IconButton>\n </div>\n </div>\n );\n }\n\n const field = (\n // `w-fit` for the toolbar size: the calendar glyph is placed against this box, and a\n // full-width box would park it at the far end of the row, away from its trigger.\n <div {...wrapperRest} ref={rootRef} className={cn(\"relative\", small && \"w-fit max-w-full\", className)}>\n {label !== undefined && <FieldLabelLine label={label} hint={messages.labelHint} />}\n {/* The hidden twin the trigger is named by — see DateField. `sr-only-fixed`,\n and inside this `relative` root either way. */}\n {named && (\n <span id={labelId} aria-hidden className=\"sr-only-fixed\">\n {label}\n </span>\n )}\n <Popover\n width=\"16rem\"\n panelId={panelId}\n labels={{ panel: panelLabel }}\n dir={dir}\n trigger={({ open, toggle, ref }) => (\n <MonthFieldTrigger\n open={open}\n toggle={() => {\n setDir(dirOf(rootRef.current));\n toggle();\n }}\n triggerRef={ref}\n triggerText={triggerText}\n hasValue={shownKey !== null}\n // As DatePicker: a caller's reference first; an `id` without a label of our\n // own references the trigger itself, so an external <label htmlFor> names it.\n labelledBy={[\n aria[\"aria-labelledby\"],\n named ? labelId : !aria[\"aria-labelledby\"] && aria.id ? aria.id : undefined,\n valueId,\n ]\n .filter(Boolean)\n .join(\" \")}\n aria={aria}\n valueId={valueId}\n panelId={panelId}\n padded={label !== undefined}\n small={small}\n disabled={disabled}\n invalid={invalid || hasContent(error)}\n className={triggerClassName}\n />\n )}\n >\n {grid}\n </Popover>\n <Calendar\n aria-hidden\n className=\"pointer-events-none absolute end-2.5 top-1/2 size-4 -translate-y-1/2 text-[var(--text-placeholder)]\"\n />\n </div>\n );\n // An unlabelled picker has no label line: a FieldHint goes at its end, outside the\n // box, as on an unlabelled Select.\n const endHint = label === undefined ? messages.labelHint : undefined;\n return (\n <FieldBox below={messages.below} reserve={\"error\" in props || \"hint\" in props}>\n {endHint === undefined ? (\n field\n ) : (\n <div className=\"flex items-center gap-1.5\">\n <div className={cn(\"min-w-0\", !small && \"flex-1\")}>{field}</div>\n <span className=\"flex shrink-0 items-center\">{endHint}</span>\n </div>\n )}\n </FieldBox>\n );\n}\n"],"mappings":"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","names":["_"]}
|
|
@@ -2,7 +2,7 @@ import * as react from 'react';
|
|
|
2
2
|
import { ComponentProps, ReactNode } from 'react';
|
|
3
3
|
import { NumberInput } from './number-input.js';
|
|
4
4
|
import './calculator.js';
|
|
5
|
-
import '../delete-account-setting-
|
|
5
|
+
import '../delete-account-setting-Dy4kJgUm.js';
|
|
6
6
|
import './tooltip.js';
|
|
7
7
|
import '../hooks/use-anchored-rect.js';
|
|
8
8
|
import '../lib/clipping.js';
|
|
@@ -2,7 +2,7 @@ import * as react from 'react';
|
|
|
2
2
|
import { ReactNode } from 'react';
|
|
3
3
|
import { CalculatorButtonLabels } from './calculator.js';
|
|
4
4
|
import { NumberPadSheetLabels } from './numpad-sheet.js';
|
|
5
|
-
import '../delete-account-setting-
|
|
5
|
+
import '../delete-account-setting-Dy4kJgUm.js';
|
|
6
6
|
import './tooltip.js';
|
|
7
7
|
import '../hooks/use-anchored-rect.js';
|
|
8
8
|
import '../lib/clipping.js';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { ReactNode } from 'react';
|
|
3
|
-
import { bP as CalculatorLabels } from '../delete-account-setting-
|
|
3
|
+
import { bP as CalculatorLabels } from '../delete-account-setting-Dy4kJgUm.js';
|
|
4
4
|
import './tooltip.js';
|
|
5
5
|
import '../hooks/use-anchored-rect.js';
|
|
6
6
|
import '../lib/clipping.js';
|