@alfadocs/ui-kit-debug 1.11.0 → 1.12.5
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/_chunks/actions-cell-renderer-DnSLrgQD.js +1945 -0
- package/dist/_chunks/{actions-cell-renderer-xfSYiTXQ.js.map → actions-cell-renderer-DnSLrgQD.js.map} +1 -1
- package/dist/_chunks/{anamnesis-summary-rows-d44s_whc.js → anamnesis-summary-rows-BfhxWPO3.js} +1421 -1418
- package/dist/_chunks/anamnesis-summary-rows-BfhxWPO3.js.map +1 -0
- package/dist/_chunks/appointment-card-Dzr3GeVz.js.map +1 -1
- package/dist/_chunks/appointment-timeline-DCn0Jx55.js +403 -0
- package/dist/_chunks/appointment-timeline-DCn0Jx55.js.map +1 -0
- package/dist/_chunks/{balance-badge-cell-C_Qz76x7.js → balance-badge-cell-7L6nzB29.js} +317 -290
- package/dist/_chunks/balance-badge-cell-7L6nzB29.js.map +1 -0
- package/dist/_chunks/{chart-Bkr1Sh4o.js → chart-C6eHTxkO.js} +2 -2
- package/dist/_chunks/{chart-Bkr1Sh4o.js.map → chart-C6eHTxkO.js.map} +1 -1
- package/dist/_chunks/{control-room-rail-Cj2iX9h_.js → control-room-rail-iO_BCaq1.js} +2 -2
- package/dist/_chunks/{control-room-rail-Cj2iX9h_.js.map → control-room-rail-iO_BCaq1.js.map} +1 -1
- package/dist/_chunks/{editable-currency-cell-renderer-zLf_bXvL.js → editable-currency-cell-renderer-B7TVZ5ZJ.js} +2 -2
- package/dist/_chunks/{editable-currency-cell-renderer-zLf_bXvL.js.map → editable-currency-cell-renderer-B7TVZ5ZJ.js.map} +1 -1
- package/dist/_chunks/{face-scheme-BfDi4YkW.js → face-scheme-CZaKm6fG.js} +2 -2
- package/dist/_chunks/{face-scheme-BfDi4YkW.js.map → face-scheme-CZaKm6fG.js.map} +1 -1
- package/dist/_chunks/{file-manager-Df6hyUYg.js → file-manager-C0y7HBAN.js} +3 -3
- package/dist/_chunks/{file-manager-Df6hyUYg.js.map → file-manager-C0y7HBAN.js.map} +1 -1
- package/dist/_chunks/{header-settings-CVC_davE.js → header-settings-J-ZCAfir.js} +2 -2
- package/dist/_chunks/{header-settings-CVC_davE.js.map → header-settings-J-ZCAfir.js.map} +1 -1
- package/dist/_chunks/{icon-button-group-D6SVMteH.js → icon-button-group-VCCod3Hz.js} +154 -145
- package/dist/_chunks/icon-button-group-VCCod3Hz.js.map +1 -0
- package/dist/_chunks/{link-cell-renderer-Cp2MSlJl.js → link-cell-renderer-CenJx-_7.js} +197 -181
- package/dist/_chunks/link-cell-renderer-CenJx-_7.js.map +1 -0
- package/dist/_chunks/{marketplace-app-shell-Chfdia7m.js → marketplace-app-shell-DDQXQ8hX.js} +2 -2
- package/dist/_chunks/{marketplace-app-shell-Chfdia7m.js.map → marketplace-app-shell-DDQXQ8hX.js.map} +1 -1
- package/dist/_chunks/{notes-panel-e9cJWVRg.js → notes-panel-CymneXtl.js} +93 -91
- package/dist/_chunks/notes-panel-CymneXtl.js.map +1 -0
- package/dist/_chunks/{signature-request-CW30B77U.js → signature-request-DVDTKLjM.js} +2 -2
- package/dist/_chunks/{signature-request-CW30B77U.js.map → signature-request-DVDTKLjM.js.map} +1 -1
- package/dist/_chunks/{theme-toggle-DuMhQNFS.js → theme-toggle-uEwoKjl3.js} +2 -2
- package/dist/_chunks/{theme-toggle-DuMhQNFS.js.map → theme-toggle-uEwoKjl3.js.map} +1 -1
- package/dist/_chunks/{tool-rail-CHyUmulN.js → tool-rail-8Scslq3X.js} +2 -2
- package/dist/_chunks/tool-rail-8Scslq3X.js.map +1 -0
- package/dist/_chunks/{whatsapp-button-Ca9towA4.js → whatsapp-button-CuFIHJzG.js} +27 -19
- package/dist/_chunks/{whatsapp-button-Ca9towA4.js.map → whatsapp-button-CuFIHJzG.js.map} +1 -1
- package/dist/agent-catalog.json +2 -2
- package/dist/agent-i18n/en.json +1 -1
- package/dist/components/appointment-card/appointment-card.d.ts +1 -1
- package/dist/components/appointment-card/appointment-card.d.ts.map +1 -1
- package/dist/components/appointment-timeline/appointment-timeline.agent.d.ts.map +1 -1
- package/dist/components/appointment-timeline/appointment-timeline.d.ts +8 -0
- package/dist/components/appointment-timeline/appointment-timeline.d.ts.map +1 -1
- package/dist/components/appointment-timeline/index.js +1 -1
- package/dist/components/chart/index.js +1 -1
- package/dist/components/control-room-rail/index.js +1 -1
- package/dist/components/data-table/cell-renderers/actions-cell-renderer.d.ts +7 -0
- package/dist/components/data-table/cell-renderers/actions-cell-renderer.d.ts.map +1 -1
- package/dist/components/data-table/filters/date-range-filter.d.ts +22 -0
- package/dist/components/data-table/filters/date-range-filter.d.ts.map +1 -1
- package/dist/components/data-table/index.js +3 -3
- package/dist/components/face-scheme/index.js +1 -1
- package/dist/components/file-manager/index.js +1 -1
- package/dist/components/header-settings/index.js +1 -1
- package/dist/components/icon-button-group/icon-button-group.d.ts.map +1 -1
- package/dist/components/icon-button-group/index.js +1 -1
- package/dist/components/notes-panel/index.js +1 -1
- package/dist/components/notes-panel/notes-panel.agent.d.ts.map +1 -1
- package/dist/components/notes-panel/notes-panel.d.ts +6 -0
- package/dist/components/notes-panel/notes-panel.d.ts.map +1 -1
- package/dist/components/patient-table/columns.d.ts.map +1 -1
- package/dist/components/patient-table/index.js +1 -1
- package/dist/components/theme-toggle/index.js +1 -1
- package/dist/components/tool-rail/index.js +1 -1
- package/dist/components/tool-rail/tool-rail.d.ts +1 -1
- package/dist/components/tool-rail/tool-rail.d.ts.map +1 -1
- package/dist/components/whatsapp-button/index.js +1 -1
- package/dist/components/whatsapp-button/whatsapp-button.d.ts.map +1 -1
- package/dist/host-compat.css +7 -1
- package/dist/index.js +16 -16
- package/dist/patterns/anamnesis/anamnesis-catalogue.d.ts.map +1 -1
- package/dist/patterns/anamnesis/anamnesis-compile.d.ts.map +1 -1
- package/dist/patterns/anamnesis/anamnesis-config.d.ts +18 -7
- package/dist/patterns/anamnesis/anamnesis-config.d.ts.map +1 -1
- package/dist/patterns/anamnesis/anamnesis-types.d.ts +8 -0
- package/dist/patterns/anamnesis/anamnesis-types.d.ts.map +1 -1
- package/dist/patterns/anamnesis/index.js +1 -1
- package/dist/patterns/marketplace-app-shell/index.js +1 -1
- package/dist/patterns/signature-request/index.js +1 -1
- package/dist/tokens.css +1 -1
- package/package.json +1 -1
- package/dist/_chunks/actions-cell-renderer-xfSYiTXQ.js +0 -1887
- package/dist/_chunks/anamnesis-summary-rows-d44s_whc.js.map +0 -1
- package/dist/_chunks/appointment-timeline-0mwtpErq.js +0 -388
- package/dist/_chunks/appointment-timeline-0mwtpErq.js.map +0 -1
- package/dist/_chunks/balance-badge-cell-C_Qz76x7.js.map +0 -1
- package/dist/_chunks/icon-button-group-D6SVMteH.js.map +0 -1
- package/dist/_chunks/link-cell-renderer-Cp2MSlJl.js.map +0 -1
- package/dist/_chunks/notes-panel-e9cJWVRg.js.map +0 -1
- package/dist/_chunks/tool-rail-CHyUmulN.js.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"link-cell-renderer-Cp2MSlJl.js","sources":["../../src/components/data-table/filters/date-range-filter.tsx","../../src/components/data-table/filters/select-filter.tsx","../../src/components/data-table/filters/typeahead-filter.tsx","../../src/components/data-table/filters/number-filter.tsx","../../src/components/data-table/cell-renderers/status-cell-renderer.tsx","../../src/components/data-table/cell-renderers/user-cell-renderer.tsx","../../src/components/data-table/cell-renderers/image-cell-renderer.tsx","../../src/components/data-table/cell-renderers/tag-list-cell-renderer.tsx","../../src/components/data-table/cell-renderers/date-cell-renderer.tsx","../../src/components/data-table/cell-renderers/link-cell-renderer.tsx"],"sourcesContent":["import { useEffect, useState } from 'react';\nimport type {\n CustomFilterProps,\n CustomFloatingFilterProps,\n} from 'ag-grid-react';\nimport { useGridFilter } from 'ag-grid-react';\nimport type { IFilter } from 'ag-grid-community';\nimport { DateRangePicker } from '../../date-range-picker';\nimport type { DateRangeValue } from '../../date-range-picker';\n\n/**\n * Custom AG Grid filter component for date *ranges*. AG Grid Community's\n * built-in `agDateColumnFilter` only handles single-date comparison —\n * platform needs \"between X and Y\" for invoice / transaction lists.\n *\n * Wire on a colDef:\n * ```tsx\n * { field: 'date', filter: DateRangeFilter }\n * ```\n *\n * The filter model has shape `{ type: 'inRange', dateFrom, dateTo }` to\n * stay close to AG Grid's own date-filter convention so server-side\n * adapters can interpret it the same way.\n */\nexport interface DateRangeFilterModel {\n type: 'inRange';\n dateFrom: string | null;\n dateTo: string | null;\n}\n\n/**\n * `filterParams` a colDef may pass to `DateRangeFilter` /\n * `DateRangeFloatingFilter`.\n *\n * `commitMode` is forwarded to the wrapped `DateRangePicker`. Leave it unset\n * (picker default `'auto'`, commit-on-complete-range) for cheap client-side row\n * models; set `'apply'` for SERVER-side row models so each range edit defers to\n * one Apply and fires a single datasource round-trip instead of one per commit.\n */\nexport interface DateRangeFilterParams {\n placeholder?: string;\n commitMode?: 'auto' | 'apply';\n}\n\nfunction toIsoDate(d: Date | undefined): string | null {\n if (!d) return null;\n return d.toISOString().slice(0, 10);\n}\n\nfunction fromIsoDate(s: string | null | undefined): Date | undefined {\n if (!s) return undefined;\n const d = new Date(s);\n return Number.isNaN(d.getTime()) ? undefined : d;\n}\n\nexport function DateRangeFilter(\n props: CustomFilterProps<unknown, unknown, DateRangeFilterModel>,\n) {\n const { model, onModelChange, getValue, doesRowPassOtherFilter } = props;\n const filterParams =\n (props.colDef?.filterParams as DateRangeFilterParams | undefined) ?? {};\n // Picker UI state, seeded from the model and kept in sync when the model\n // changes externally — a programmatic default (e.g. \"this week\"), a server\n // restore, or an AG Grid reset. Mirrors `SelectFilter`'s `pending` sync.\n const [range, setRange] = useState<DateRangeValue>(() => ({\n from: fromIsoDate(model?.dateFrom),\n to: fromIsoDate(model?.dateTo),\n }));\n useEffect(() => {\n setRange({\n from: fromIsoDate(model?.dateFrom),\n to: fromIsoDate(model?.dateTo),\n });\n }, [model?.dateFrom, model?.dateTo]);\n\n // Tell AG Grid how to evaluate a row against the current model. AG Grid\n // calls this for each row; we read the cell value via `getValue(node)`\n // (a Date or ISO string), compare to the bounds. Bounds come from the\n // MODEL (the source of truth) — not the picker state — so a model set\n // programmatically applies even before the picker UI has mounted, exactly\n // as `SelectFilter` reads `model.values` directly.\n useGridFilter({\n doesFilterPass: (params) => {\n const from = fromIsoDate(model?.dateFrom);\n const to = fromIsoDate(model?.dateTo);\n if (!from && !to) return true;\n const cellValue = getValue(params.node) as\n | Date\n | string\n | null\n | undefined;\n if (!cellValue) return false;\n const cellDate =\n cellValue instanceof Date ? cellValue : fromIsoDate(String(cellValue));\n if (!cellDate) return false;\n if (from && cellDate < from) return false;\n if (to && cellDate > to) return false;\n // Respect other filters via the supplied callback if AG Grid uses\n // it (some grid setups call `doesRowPassOtherFilter` before us).\n void doesRowPassOtherFilter;\n return true;\n },\n });\n\n function apply(next: DateRangeValue) {\n setRange(next);\n if (!next.from && !next.to) {\n onModelChange(null);\n return;\n }\n onModelChange({\n type: 'inRange',\n dateFrom: toIsoDate(next.from),\n dateTo: toIsoDate(next.to),\n } satisfies DateRangeFilterModel);\n }\n\n return (\n <div\n data-component=\"date-range-filter\"\n className=\"ds:p-[var(--spacing-sm)] ds:min-w-[280px]\"\n >\n <DateRangePicker\n value={range}\n onChange={apply}\n commitMode={filterParams.commitMode}\n size=\"sm\"\n />\n </div>\n );\n}\n\n/**\n * Floating-filter companion for `DateRangeFilter`.\n *\n * Renders the kit's `<DateRangePicker>` directly as the floating-filter\n * input — its built-in trigger button + calendar popover IS the filter\n * UI. Earlier versions wrapped the picker in a second `<Popover>`,\n * producing the \"click → empty popover with another trigger → click\n * again → calendar\" double-step that read as duplicated UI.\n */\nexport function DateRangeFloatingFilter(\n props: CustomFloatingFilterProps<\n IFilter,\n unknown,\n unknown,\n DateRangeFilterModel\n >,\n) {\n const { model, onModelChange, column } = props;\n // Let a colDef supply a short `filterParams.placeholder` for the in-row\n // trigger — the default \"Select date range\" is long for a narrow filter\n // column. (The label truncates cleanly either way; a short placeholder just\n // reads better.)\n const filterParams =\n (column?.getColDef?.()?.filterParams as\n | DateRangeFilterParams\n | undefined) ?? {};\n const [range, setRange] = useState<DateRangeValue>(() => ({\n from: fromIsoDate(model?.dateFrom),\n to: fromIsoDate(model?.dateTo),\n }));\n // Reflect external model changes (a programmatic default, a parent-filter\n // edit, an AG Grid reset) in the in-row picker trigger.\n useEffect(() => {\n setRange({\n from: fromIsoDate(model?.dateFrom),\n to: fromIsoDate(model?.dateTo),\n });\n }, [model?.dateFrom, model?.dateTo]);\n\n function apply(next: DateRangeValue) {\n setRange(next);\n if (!next.from && !next.to) {\n onModelChange(null);\n return;\n }\n onModelChange({\n type: 'inRange',\n dateFrom: toIsoDate(next.from),\n dateTo: toIsoDate(next.to),\n } satisfies DateRangeFilterModel);\n }\n\n return (\n <div data-component=\"date-range-floating-filter\" className=\"ds:w-full\">\n <DateRangePicker\n value={range}\n onChange={apply}\n commitMode={filterParams.commitMode}\n size=\"sm\"\n placeholder={filterParams.placeholder}\n />\n </div>\n );\n}\n","import { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport type {\n CustomFilterProps,\n CustomFloatingFilterProps,\n} from 'ag-grid-react';\nimport { useGridFilter } from 'ag-grid-react';\nimport type { GridApi, IFilter, IRowNode } from 'ag-grid-community';\nimport { useTranslation } from 'react-i18next';\nimport { Checkbox } from '../../checkbox';\nimport { Button } from '../../button';\nimport { MultiSelect } from '../../multi-select';\nimport { ScrollArea } from '../../scroll-area/scroll-area';\n\n/**\n * Custom AG Grid filter component — multi-select facet filter.\n *\n * The Community-friendly equivalent of AG Grid Enterprise's\n * `agSetColumnFilter`. Surfaces a checkbox list of every value the column\n * can take, so users tick to include / untick to exclude. Much faster than\n * the default `agTextColumnFilter` for closed enums (status, room,\n * operator).\n *\n * Wire on a colDef:\n * ```tsx\n * {\n * field: 'status',\n * filter: SelectFilter,\n * filterParams: {\n * options: [\n * { value: 'open', label: 'Aperte' },\n * { value: 'paid', label: 'Pagate' },\n * ],\n * },\n * }\n * ```\n *\n * Model shape `{ type: 'set', values: Array<string | number> }` matches\n * AG Grid Enterprise's set-filter convention so server-side adapters can\n * read either implementation the same way.\n */\n\nexport interface SelectFilterOption {\n value: string | number;\n label: string;\n}\n\nexport interface SelectFilterModel {\n type: 'set';\n values: Array<string | number>;\n}\n\nexport interface SelectFilterParams {\n options?: SelectFilterOption[];\n placeholder?: string;\n}\n\ntype SelectFilterValue = string | number;\n\nfunction toValueKey(v: SelectFilterValue): string {\n return typeof v === 'number' ? `n:${v}` : `s:${v}`;\n}\n\n/** How a filter surface reads one row's value for its column. */\ntype SelectValueReader = (node: IRowNode) => unknown;\n\n/** Numeric collation, so a derived numeric column lists 1, 2, 3, 10 rather\n * than the lexicographic 1, 10, 2, 3. */\nconst LABEL_COLLATOR = new Intl.Collator(undefined, { numeric: true });\n\n/** Distinct values the column actually holds, label-sorted. `forEachNode`\n * walks the whole client-side row model, so the list does not shrink as\n * other columns filter rows out. */\nfunction collectColumnOptions(\n api: GridApi | undefined,\n readValue: SelectValueReader,\n): SelectFilterOption[] {\n if (!api) return [];\n const seen = new Map<string, SelectFilterOption>();\n api.forEachNode((node: IRowNode) => {\n const raw = readValue(node);\n if (raw === null || raw === undefined) return;\n const value =\n typeof raw === 'string' || typeof raw === 'number' ? raw : String(raw);\n const key = toValueKey(value);\n if (!seen.has(key)) seen.set(key, { value, label: String(value) });\n });\n return Array.from(seen.values()).sort((a, b) =>\n LABEL_COLLATOR.compare(String(a.label), String(b.label)),\n );\n}\n\nfunction sameOptions(\n a: SelectFilterOption[],\n b: SelectFilterOption[],\n): boolean {\n if (a.length !== b.length) return false;\n return a.every(\n (opt, i) =>\n toValueKey(opt.value) === toValueKey(b[i].value) &&\n opt.label === b[i].label,\n );\n}\n\n/**\n * The universe of choices for one Select-filtered column.\n *\n * An explicit `options` filterParam always wins — INCLUDING an empty one,\n * which means \"this column has no facets\", not \"derive them for me\". Omit\n * `options` entirely and the list is DERIVED from the values the column\n * actually holds, and re-derived as the row data changes, so both the popup\n * panel and its floating-filter twin offer the same, data-accurate choices.\n */\nfunction useSelectFilterOptions(\n api: GridApi | undefined,\n options: SelectFilterOption[] | undefined,\n readValue: SelectValueReader,\n): SelectFilterOption[] {\n const explicit = options;\n const hasExplicit = explicit !== undefined;\n const [derived, setDerived] = useState<SelectFilterOption[]>([]);\n\n // Latest-reader ref so a new `getValue` identity per grid render does not\n // tear down and rebuild the grid event subscription below.\n const readValueRef = useRef(readValue);\n useEffect(() => {\n readValueRef.current = readValue;\n });\n\n useEffect(() => {\n if (hasExplicit || !api) return;\n const recompute = () => {\n setDerived((prev) => {\n const next = collectColumnOptions(api, readValueRef.current);\n return sameOptions(prev, next) ? prev : next;\n });\n };\n recompute();\n api.addEventListener('modelUpdated', recompute);\n api.addEventListener('cellValueChanged', recompute);\n return () => {\n if (api.isDestroyed()) return;\n api.removeEventListener('modelUpdated', recompute);\n api.removeEventListener('cellValueChanged', recompute);\n };\n }, [api, hasExplicit]);\n\n return explicit ?? derived;\n}\n\nexport function SelectFilter(\n props: CustomFilterProps<unknown, unknown, SelectFilterModel> &\n SelectFilterParams,\n) {\n const { model, onModelChange, getValue, api, options, placeholder } = props;\n const { t } = useTranslation('ui');\n\n const readValue = useCallback<SelectValueReader>(\n (node) => getValue(node) as unknown,\n [getValue],\n );\n const derivedOptions = useSelectFilterOptions(api, options, readValue);\n\n // Working set — checkboxes mutate `pending` only; we commit to the\n // grid via `onModelChange` when the user clicks Apply (or clears).\n const initialSelected = useMemo<Set<string>>(() => {\n const set = new Set<string>();\n const incoming = model?.values ?? [];\n for (const v of incoming) set.add(toValueKey(v));\n return set;\n }, [model]);\n\n const [pending, setPending] = useState<Set<string>>(initialSelected);\n\n // Sync local `pending` if the model changes externally (e.g. when AG\n // Grid pushes a reset). `model` is the dependency — Set instances are\n // compared by reference in React, so the memo above is sufficient.\n useEffect(() => {\n setPending(initialSelected);\n }, [initialSelected]);\n\n useGridFilter({\n doesFilterPass: (params) => {\n const active = model?.values;\n if (!active || active.length === 0) return true;\n const raw = getValue(params.node) as unknown;\n if (raw === null || raw === undefined) return false;\n const value: SelectFilterValue =\n typeof raw === 'string' || typeof raw === 'number' ? raw : String(raw);\n const key = toValueKey(value);\n return active.some((v) => toValueKey(v) === key);\n },\n });\n\n function toggle(optionKey: string) {\n setPending((prev) => {\n const next = new Set(prev);\n if (next.has(optionKey)) next.delete(optionKey);\n else next.add(optionKey);\n return next;\n });\n }\n\n function apply() {\n if (pending.size === 0) {\n onModelChange(null);\n return;\n }\n const values: SelectFilterValue[] = [];\n for (const opt of derivedOptions) {\n if (pending.has(toValueKey(opt.value))) values.push(opt.value);\n }\n // A ticked value can leave the data before Apply (the derived list is\n // live). Committing `values: []` would read as an active filter that\n // matches every row, so clear instead.\n if (values.length === 0) {\n onModelChange(null);\n return;\n }\n onModelChange({ type: 'set', values });\n }\n\n function clear() {\n setPending(new Set());\n onModelChange(null);\n }\n\n const placeholderLabel = placeholder ?? t('dataTable.selectFilter.all');\n\n return (\n <div\n data-component=\"select-filter\"\n className=\"ds:flex ds:flex-col ds:gap-[var(--spacing-sm)] ds:p-[var(--spacing-sm)] ds:min-w-[200px] ds:max-w-[260px]\"\n >\n <ScrollArea orientation=\"vertical\" className=\"ds:max-h-[240px]\">\n <div\n role=\"group\"\n aria-label={placeholderLabel}\n className=\"ds:flex ds:flex-col ds:gap-[var(--spacing-xs)]\"\n >\n {derivedOptions.length === 0 ? (\n <p className=\"ds:text-[length:var(--font-size-sm)] ds:text-[var(--muted-foreground)] ds:py-[var(--spacing-xs)]\">\n {t('dataTable.agGrid.noRowsToShow')}\n </p>\n ) : (\n derivedOptions.map((option) => {\n const key = toValueKey(option.value);\n return (\n <Checkbox\n key={key}\n size=\"sm\"\n label={option.label}\n checked={pending.has(key)}\n onCheckedChange={() => toggle(key)}\n />\n );\n })\n )}\n </div>\n </ScrollArea>\n <div className=\"ds:flex ds:items-center ds:justify-end ds:gap-[var(--spacing-xs)] ds:pt-[var(--spacing-xs)] ds:border-t ds:border-[var(--border)]\">\n <Button intent=\"ghost\" size=\"sm\" onClick={clear} type=\"button\">\n {t('dataTable.selectFilter.clear')}\n </Button>\n <Button intent=\"primary\" size=\"sm\" onClick={apply} type=\"button\">\n {t('dataTable.selectFilter.apply')}\n </Button>\n </div>\n </div>\n );\n}\n\n/**\n * Floating-filter companion for `SelectFilter`.\n *\n * Renders the kit's `<MultiSelect>` directly as the floating-filter\n * input — its built-in trigger + checkbox dropdown IS the filter UI.\n * Earlier versions wrapped MultiSelect in another `<Popover>`,\n * producing the \"click trigger → empty popover with another trigger →\n * click → checkbox list\" double-step that read as duplicated UI.\n *\n * MultiSelect's options use `string` values; we string-convert the\n * model's `(string | number)[]` for the trigger and parse back on\n * apply. The grid model still tracks the original primitive types so\n * `doesFilterPass` keeps matching numeric columns correctly.\n *\n * Choices come from the same `useSelectFilterOptions` universe the popup\n * panel uses: an explicit `options` filterParam if the consumer declared\n * one, otherwise the column's own distinct values. Reading only\n * `filterParams.options` used to leave the trigger empty beside a working\n * panel whenever the consumer relied on derivation.\n */\nexport function SelectFloatingFilter(\n props: CustomFloatingFilterProps<\n IFilter,\n unknown,\n unknown,\n SelectFilterModel\n >,\n) {\n const { model, onModelChange, column, api } = props;\n\n const colDef = column?.getColDef?.();\n const filterParams =\n (colDef?.filterParams as SelectFilterParams | undefined) ?? {};\n\n // The floating filter has no `getValue`; read through the grid instead.\n // A column with a `filterValueGetter` should declare `options` — the two\n // reads would otherwise disagree with the panel's.\n const readValue = useCallback<SelectValueReader>(\n (node) =>\n api && column\n ? (api.getCellValue<unknown>({\n rowNode: node,\n colKey: column,\n }) as unknown)\n : undefined,\n [api, column],\n );\n const availableOptions = useSelectFilterOptions(\n api,\n filterParams.options,\n readValue,\n );\n // Name the floating-filter trigger after its column so the per-column Select\n // filters are distinguishable to assistive tech — they otherwise all read as\n // the generic MultiSelect placeholder. An explicit `placeholder` wins.\n const accessibleLabel =\n filterParams.placeholder ??\n (typeof colDef?.headerName === 'string' ? colDef.headerName : undefined);\n\n // MultiSelect's `value` is `string[]`. SelectFilter's model carries\n // `(string | number)[]` so we string-convert in / out. Build a lookup\n // from the stringified key back to the original primitive so the\n // model retains the consumer's typing.\n const valueByKey = useMemo(() => {\n const map = new Map<string, SelectFilterValue>();\n for (const opt of availableOptions) map.set(String(opt.value), opt.value);\n return map;\n }, [availableOptions]);\n\n const selectOptions = useMemo(\n () =>\n availableOptions.map((opt) => ({\n value: String(opt.value),\n label: opt.label,\n })),\n [availableOptions],\n );\n\n const currentValue = useMemo(\n () => (model?.values ?? []).map((v) => String(v)),\n [model],\n );\n\n function handleChange(next: string[]) {\n if (next.length === 0) {\n onModelChange(null);\n return;\n }\n const values: SelectFilterValue[] = [];\n for (const k of next) {\n const original = valueByKey.get(k);\n if (original !== undefined) values.push(original);\n }\n // Same live-list guard as the panel's Apply: an empty set is a cleared\n // filter, never an active one that matches everything.\n if (values.length === 0) {\n onModelChange(null);\n return;\n }\n onModelChange({ type: 'set', values });\n }\n\n return (\n <div data-component=\"select-floating-filter\" className=\"ds:w-full\">\n <MultiSelect\n size=\"sm\"\n options={selectOptions}\n value={currentValue}\n onChange={handleChange}\n placeholder={filterParams.placeholder}\n aria-label={accessibleLabel}\n allowClear\n />\n </div>\n );\n}\n","import { useEffect, useMemo, useState } from 'react';\nimport type {\n CustomFilterProps,\n CustomFloatingFilterProps,\n} from 'ag-grid-react';\nimport { useGridFilter } from 'ag-grid-react';\nimport type { IFilter, IRowNode } from 'ag-grid-community';\nimport { useTranslation } from 'react-i18next';\nimport { Autocomplete } from '../../autocomplete';\nimport { Button } from '../../button';\nimport type { OptionShape } from '../../_shared/option';\n\n/**\n * Custom AG Grid filter component — single-select typeahead filter.\n *\n * Wraps the kit's existing `<Autocomplete>` so the user types to filter a\n * bounded-but-large set of values (e.g. patient names), picks one\n * suggestion, and the grid filters to rows matching that value exactly.\n *\n * Wire on a colDef:\n * ```tsx\n * {\n * field: 'patient',\n * filter: TypeaheadFilter,\n * filterParams: {\n * options: PATIENTS.map(p => ({ value: p.name, label: p.name })),\n * },\n * }\n * ```\n *\n * Model shape `{ type: 'equals', value: string }` matches AG Grid's\n * text-filter `equals` convention so server-side adapters can read either\n * implementation the same way.\n */\n\nexport interface TypeaheadFilterOption {\n value: string;\n label: string;\n}\n\nexport interface TypeaheadFilterModel {\n type: 'equals';\n value: string;\n}\n\nexport interface TypeaheadFilterParams {\n options?: TypeaheadFilterOption[];\n loadOptions?: (\n input: string,\n opts: { signal: AbortSignal; locale: string },\n ) => Promise<TypeaheadFilterOption[]>;\n placeholder?: string;\n}\n\nfunction normalise(s: string): string {\n return s.trim().toLocaleLowerCase();\n}\n\nexport function TypeaheadFilter(\n props: CustomFilterProps<unknown, unknown, TypeaheadFilterModel> &\n TypeaheadFilterParams,\n) {\n const {\n model,\n onModelChange,\n getValue,\n api,\n column,\n options,\n loadOptions,\n placeholder,\n } = props;\n const { t } = useTranslation('ui');\n\n // Universe of choices. Prefer explicit `options`; fall back to the\n // column's distinct values when neither `options` nor `loadOptions` is\n // supplied. Same shape as SelectFilter's derivation so the two filters\n // present a consistent experience when consumers don't enumerate.\n const derivedOptions = useMemo<TypeaheadFilterOption[]>(() => {\n if (options && options.length > 0) return options;\n if (!api || !column) return [];\n const seen = new Map<string, TypeaheadFilterOption>();\n api.forEachNode((node: IRowNode) => {\n const raw = getValue(node) as unknown;\n if (raw === null || raw === undefined) return;\n const value = typeof raw === 'string' ? raw : String(raw);\n if (!seen.has(value)) {\n seen.set(value, { value, label: value });\n }\n });\n return Array.from(seen.values()).sort((a, b) =>\n a.label.localeCompare(b.label),\n );\n }, [options, api, column, getValue]);\n\n // Build the async `loadOptions` adapter that <Autocomplete> consumes.\n // Local-options path filters the snapshot synchronously and wraps it\n // in Promise.resolve(); explicit `loadOptions` is passed through.\n const adapterLoadOptions = useMemo(() => {\n return async (\n query: string,\n opts: { signal: AbortSignal; locale: string },\n ): Promise<OptionShape[]> => {\n if (loadOptions) {\n const result = await loadOptions(query, opts);\n return result.map((o) => ({ value: o.value, label: o.label }));\n }\n const q = normalise(query);\n const filtered = q\n ? derivedOptions.filter((o) => normalise(o.label).includes(q))\n : derivedOptions;\n return filtered\n .slice(0, 50)\n .map((o) => ({ value: o.value, label: o.label }));\n };\n }, [loadOptions, derivedOptions]);\n\n const [draft, setDraft] = useState<string>(model?.value ?? '');\n\n useEffect(() => {\n setDraft(model?.value ?? '');\n }, [model?.value]);\n\n useGridFilter({\n doesFilterPass: (params) => {\n const target = model?.value;\n if (!target) return true;\n const raw = getValue(params.node) as unknown;\n if (raw === null || raw === undefined) return false;\n const value = typeof raw === 'string' ? raw : String(raw);\n return normalise(value) === normalise(target);\n },\n });\n\n function handleSelect(option: OptionShape) {\n // Autocomplete itself commits `option.label` to the input value; we\n // mirror that here so the displayed text matches what the user just\n // picked, even when label and value diverge (e.g. id vs name).\n setDraft(option.label);\n onModelChange({ type: 'equals', value: option.value });\n }\n\n function handleChange(next: string) {\n setDraft(next);\n // Clear the applied model when the user empties the input so the\n // grid stops filtering as soon as the field is blank.\n if (next === '' && model) onModelChange(null);\n }\n\n function clear() {\n setDraft('');\n onModelChange(null);\n }\n\n const placeholderLabel =\n placeholder ?? t('dataTable.typeaheadFilter.placeholder');\n\n return (\n <div\n data-component=\"typeahead-filter\"\n className=\"ds:flex ds:flex-col ds:gap-[var(--spacing-sm)] ds:p-[var(--spacing-sm)] ds:min-w-[240px]\"\n >\n <Autocomplete\n size=\"sm\"\n value={draft}\n onChange={handleChange}\n onSelect={handleSelect}\n loadOptions={adapterLoadOptions}\n placeholder={placeholderLabel}\n aria-label={placeholderLabel}\n />\n <div className=\"ds:flex ds:items-center ds:justify-end ds:pt-[var(--spacing-xs)] ds:border-t ds:border-[var(--border)]\">\n <Button intent=\"ghost\" size=\"sm\" onClick={clear} type=\"button\">\n {t('dataTable.typeaheadFilter.clear')}\n </Button>\n </div>\n </div>\n );\n}\n\n/**\n * Floating-filter companion for `TypeaheadFilter`.\n *\n * Renders the kit's `<Autocomplete>` directly as the floating-filter\n * input — its built-in text input + portaled dropdown IS the filter\n * UI. Earlier versions wrapped Autocomplete in a second `<Popover>`,\n * producing the \"click trigger → popover with another search input →\n * type → list appears\" double-step that read as duplicated UI.\n */\nexport function TypeaheadFloatingFilter(\n props: CustomFloatingFilterProps<\n IFilter,\n unknown,\n unknown,\n TypeaheadFilterModel\n >,\n) {\n const { model, onModelChange, column } = props;\n const { t } = useTranslation('ui');\n\n const filterParams =\n (column?.getColDef?.()?.filterParams as\n | TypeaheadFilterParams\n | undefined) ?? {};\n const availableOptions = filterParams.options ?? [];\n const loadOptions = filterParams.loadOptions;\n const placeholderLabel =\n filterParams.placeholder ?? t('dataTable.typeaheadFilter.placeholder');\n\n const adapterLoadOptions = useMemo(() => {\n return async (\n query: string,\n opts: { signal: AbortSignal; locale: string },\n ): Promise<OptionShape[]> => {\n if (loadOptions) {\n const result = await loadOptions(query, opts);\n return result.map((o) => ({ value: o.value, label: o.label }));\n }\n const q = normalise(query);\n const filtered = q\n ? availableOptions.filter((o) => normalise(o.label).includes(q))\n : availableOptions;\n return filtered\n .slice(0, 50)\n .map((o) => ({ value: o.value, label: o.label }));\n };\n }, [loadOptions, availableOptions]);\n\n // Track the input's display value separately from the applied model.\n // The user can type freely; we only apply when they pick an option\n // from the dropdown (committing a known value to the model). Clearing\n // the input clears the filter.\n const [draft, setDraft] = useState<string>(model?.value ?? '');\n useEffect(() => {\n setDraft(model?.value ?? '');\n }, [model?.value]);\n\n function handleSelect(option: OptionShape) {\n setDraft(option.label);\n onModelChange({ type: 'equals', value: option.value });\n }\n\n function handleChange(next: string) {\n setDraft(next);\n if (next === '' && model) onModelChange(null);\n }\n\n return (\n <div data-component=\"typeahead-floating-filter\" className=\"ds:w-full\">\n <Autocomplete\n size=\"sm\"\n value={draft}\n onChange={handleChange}\n onSelect={handleSelect}\n loadOptions={adapterLoadOptions}\n placeholder={placeholderLabel}\n aria-label={placeholderLabel}\n />\n </div>\n );\n}\n","import { useEffect, useMemo, useState } from 'react';\nimport type {\n CustomFilterProps,\n CustomFloatingFilterProps,\n} from 'ag-grid-react';\nimport { useGridFilter } from 'ag-grid-react';\nimport type { IFilter } from 'ag-grid-community';\nimport { useTranslation } from 'react-i18next';\nimport { NumberInput } from '../../number-input';\nimport { Select } from '../../select';\nimport { Button } from '../../button';\n\n/**\n * Custom AG Grid filter component for numeric columns. AG Grid Community's\n * built-in `agNumberColumnFilter` paints a stock browser-skinned\n * `<input type=\"number\">` that does not match the kit's form language —\n * this wraps the DS `<NumberInput>` + `<Select>` so totals, prices,\n * quantities, etc. line up visually with neighbouring DS-styled cells.\n *\n * Wire on a colDef:\n * ```tsx\n * {\n * field: 'total',\n * filter: NumberFilter,\n * floatingFilterComponent: NumberFloatingFilter,\n * filterParams: { defaultOperator: 'greaterThan', min: 0 },\n * }\n * ```\n *\n * The full-popover variant exposes the operator dropdown (equals,\n * greaterThan, inRange, blank, …); the floating-filter row is a single\n * compact input whose operator is fixed to `defaultOperator`. Both speak\n * the same `NumberFilterModel` so server-side adapters can read either.\n *\n * Model shape `{ type, filter, filterTo? }` matches AG Grid's own\n * `agNumberColumnFilter` vocabulary so adapters can interchange.\n */\n\nexport type NumberFilterOperator =\n | 'equals'\n | 'notEqual'\n | 'greaterThan'\n | 'greaterThanOrEqual'\n | 'lessThan'\n | 'lessThanOrEqual'\n | 'inRange'\n | 'blank'\n | 'notBlank';\n\nexport interface NumberFilterModel {\n type: NumberFilterOperator;\n filter: number;\n filterTo?: number;\n}\n\nexport interface NumberFilterParams {\n defaultOperator?: NumberFilterOperator;\n min?: number;\n max?: number;\n step?: number;\n placeholder?: string;\n}\n\nconst ALL_OPERATORS: NumberFilterOperator[] = [\n 'equals',\n 'notEqual',\n 'greaterThan',\n 'greaterThanOrEqual',\n 'lessThan',\n 'lessThanOrEqual',\n 'inRange',\n 'blank',\n 'notBlank',\n];\n\nfunction operatorTakesValue(op: NumberFilterOperator): boolean {\n return op !== 'blank' && op !== 'notBlank';\n}\n\nfunction operatorTakesRange(op: NumberFilterOperator): boolean {\n return op === 'inRange';\n}\n\nfunction evaluate(\n op: NumberFilterOperator,\n cell: number | null,\n primary: number | null,\n secondary: number | null,\n): boolean {\n if (op === 'blank') return cell === null;\n if (op === 'notBlank') return cell !== null;\n if (cell === null) return false;\n if (primary === null) return true;\n switch (op) {\n case 'equals':\n return cell === primary;\n case 'notEqual':\n return cell !== primary;\n case 'greaterThan':\n return cell > primary;\n case 'greaterThanOrEqual':\n return cell >= primary;\n case 'lessThan':\n return cell < primary;\n case 'lessThanOrEqual':\n return cell <= primary;\n case 'inRange': {\n if (secondary === null) return cell >= primary;\n const lo = Math.min(primary, secondary);\n const hi = Math.max(primary, secondary);\n return cell >= lo && cell <= hi;\n }\n default:\n return true;\n }\n}\n\nfunction coerceCell(raw: unknown): number | null {\n if (raw === null || raw === undefined || raw === '') return null;\n const n = typeof raw === 'number' ? raw : Number(raw);\n return Number.isNaN(n) ? null : n;\n}\n\nexport function NumberFilter(\n props: CustomFilterProps<unknown, unknown, NumberFilterModel> &\n NumberFilterParams,\n) {\n const {\n model,\n onModelChange,\n getValue,\n defaultOperator,\n min,\n max,\n step,\n placeholder,\n } = props;\n const { t } = useTranslation('ui');\n\n const initialOperator: NumberFilterOperator =\n model?.type ?? defaultOperator ?? 'equals';\n const [operator, setOperator] =\n useState<NumberFilterOperator>(initialOperator);\n const [primary, setPrimary] = useState<number | null>(model?.filter ?? null);\n const [secondary, setSecondary] = useState<number | null>(\n model?.filterTo ?? null,\n );\n\n // If AG Grid pushes an externally-cleared / reset model, mirror it in\n // local state so the UI stays consistent with the applied filter. The\n // cleared model arrives as null OR undefined (removed from a setFilterModel\n // map), so a loose check is load-bearing here.\n useEffect(() => {\n if (model == null) {\n setOperator(defaultOperator ?? 'equals');\n setPrimary(null);\n setSecondary(null);\n return;\n }\n setOperator(model.type);\n setPrimary(model.filter ?? null);\n setSecondary(model.filterTo ?? null);\n }, [model, defaultOperator]);\n\n useGridFilter({\n doesFilterPass: (params) => {\n if (!model) return true;\n const cell = coerceCell(getValue(params.node));\n return evaluate(\n model.type,\n cell,\n model.filter ?? null,\n model.filterTo ?? null,\n );\n },\n });\n\n const operatorOptions = useMemo(\n () =>\n ALL_OPERATORS.map((op) => ({\n value: op,\n label: t(`dataTable.numberFilter.operators.${op}`),\n })),\n [t],\n );\n\n const placeholderLabel =\n placeholder ?? t('dataTable.numberFilter.placeholder');\n\n function apply() {\n if (!operatorTakesValue(operator)) {\n onModelChange({ type: operator, filter: 0 });\n return;\n }\n if (primary === null) {\n onModelChange(null);\n return;\n }\n if (operatorTakesRange(operator)) {\n onModelChange({\n type: operator,\n filter: primary,\n filterTo: secondary ?? undefined,\n });\n return;\n }\n onModelChange({ type: operator, filter: primary });\n }\n\n function clear() {\n setOperator(defaultOperator ?? 'equals');\n setPrimary(null);\n setSecondary(null);\n onModelChange(null);\n }\n\n const showValue = operatorTakesValue(operator);\n const showRange = operatorTakesRange(operator);\n\n return (\n <div\n data-component=\"number-filter\"\n className=\"ds:flex ds:flex-col ds:gap-[var(--spacing-sm)] ds:p-[var(--spacing-sm)] ds:min-w-[240px]\"\n >\n <Select\n size=\"sm\"\n options={operatorOptions}\n value={operator}\n onValueChange={(next) => {\n if (next === '') return;\n setOperator(next as NumberFilterOperator);\n }}\n aria-label={t('dataTable.numberFilter.operators.equals')}\n />\n {showValue && !showRange && (\n <NumberInput\n size=\"sm\"\n value={primary}\n onChange={setPrimary}\n min={min}\n max={max}\n step={step}\n placeholder={placeholderLabel}\n aria-label={placeholderLabel}\n />\n )}\n {showRange && (\n <div className=\"ds:flex ds:items-center ds:gap-[var(--spacing-xs)]\">\n <NumberInput\n size=\"sm\"\n value={primary}\n onChange={setPrimary}\n min={min}\n max={max}\n step={step}\n placeholder={t('dataTable.numberFilter.from')}\n aria-label={t('dataTable.numberFilter.from')}\n />\n <NumberInput\n size=\"sm\"\n value={secondary}\n onChange={setSecondary}\n min={min}\n max={max}\n step={step}\n placeholder={t('dataTable.numberFilter.to')}\n aria-label={t('dataTable.numberFilter.to')}\n />\n </div>\n )}\n <div className=\"ds:flex ds:items-center ds:justify-end ds:gap-[var(--spacing-xs)] ds:pt-[var(--spacing-xs)] ds:border-t ds:border-[var(--border)]\">\n <Button intent=\"ghost\" size=\"sm\" onClick={clear} type=\"button\">\n {t('dataTable.numberFilter.clear')}\n </Button>\n <Button intent=\"primary\" size=\"sm\" onClick={apply} type=\"button\">\n {t('dataTable.numberFilter.apply')}\n </Button>\n </div>\n </div>\n );\n}\n\n/**\n * Floating-filter companion for `NumberFilter`.\n *\n * Renders the kit's `<NumberInput>` directly as the inline filter input\n * with a fixed operator (`defaultOperator`, or `'equals'`). The operator\n * dropdown lives only in the full popover — the floating-filter row is\n * deliberately compact, matching the way `agNumberColumnFilter` ships\n * with AG Grid Community.\n *\n * Debounced commit at 200 ms so each keystroke does not re-run the grid\n * filter. Empty input or invalid number commits `null` so the column\n * reverts to \"no filter\" rather than `filter: NaN`.\n */\nexport function NumberFloatingFilter(\n props: CustomFloatingFilterProps<\n IFilter,\n unknown,\n unknown,\n NumberFilterModel\n >,\n) {\n const { model, onModelChange, column } = props;\n const { t } = useTranslation('ui');\n\n const filterParams =\n (column?.getColDef?.()?.filterParams as NumberFilterParams | undefined) ??\n {};\n const operator: NumberFilterOperator =\n filterParams.defaultOperator ?? 'equals';\n const placeholderLabel =\n filterParams.placeholder ?? t('dataTable.numberFilter.placeholder');\n\n const [draft, setDraft] = useState<number | null>(model?.filter ?? null);\n\n // Sync local draft when the model is reset externally (e.g. a global\n // clear from the column-header menu's \"Clear\" button).\n useEffect(() => {\n setDraft(model?.filter ?? null);\n }, [model?.filter]);\n\n // Debounced commit. We don't pull a hook in for this — a single\n // useEffect + setTimeout is cheaper than the cost of importing one,\n // and AG Grid only re-renders this floating filter per keystroke.\n useEffect(() => {\n if (draft === (model?.filter ?? null)) return;\n const handle = setTimeout(() => {\n if (draft === null || Number.isNaN(draft)) {\n onModelChange(null);\n return;\n }\n onModelChange({ type: operator, filter: draft });\n }, 200);\n return () => clearTimeout(handle);\n // `model` intentionally excluded — we only want to react to user\n // edits of `draft`. Model resets sync via the effect above.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [draft, operator]);\n\n return (\n <div data-component=\"number-floating-filter\" className=\"ds:w-full\">\n <NumberInput\n size=\"sm\"\n value={draft}\n onChange={setDraft}\n min={filterParams.min}\n max={filterParams.max}\n step={filterParams.step}\n placeholder={placeholderLabel}\n aria-label={placeholderLabel}\n />\n </div>\n );\n}\n","import type { CustomCellRendererProps } from 'ag-grid-react';\nimport { Badge } from '../../badge/badge';\n\nexport type BadgeVariant = 'neutral' | 'info' | 'success' | 'warning' | 'error';\n\nexport interface StatusCellRendererParams {\n variantMap?: Record<string, BadgeVariant>;\n}\n\nexport function StatusCellRenderer(\n props: CustomCellRendererProps & StatusCellRendererParams,\n) {\n const { value, variantMap } = props;\n const variant: BadgeVariant = variantMap?.[value as string] ?? 'neutral';\n return (\n <Badge variant={variant} withDot size=\"sm\">\n {String(value ?? '')}\n </Badge>\n );\n}\n","import type { CustomCellRendererProps } from 'ag-grid-react';\nimport { Avatar } from '../../avatar/avatar';\n\nexport interface UserCellValue {\n name: string;\n src?: string;\n}\n\nexport function UserCellRenderer(\n props: CustomCellRendererProps<unknown, UserCellValue>,\n) {\n const { value } = props;\n if (!value) return null;\n return (\n <span className=\"ds:inline-flex ds:items-center ds:gap-[var(--spacing-sm)]\">\n <Avatar name={value.name} src={value.src} size=\"sm\" />\n <span>{value.name}</span>\n </span>\n );\n}\n","import type { CustomCellRendererProps } from 'ag-grid-react';\nimport { Avatar } from '../../avatar/avatar';\n\nexport interface ImageCellRendererParams {\n /** Field name on the row whose value holds the image URL. */\n srcField: string;\n /** Optional field name holding alt text. Falls back to '' (decorative). */\n altField?: string;\n /**\n * Optional field name holding a name string. When the src URL is missing or\n * rejected, the cell falls back to `<Avatar name={fallback}>` so the cell\n * still has a visual.\n */\n fallbackField?: string;\n /** Visual size of the image. Matches `<Avatar>` sizes (sm=32, md=40, lg=48). */\n size?: 'sm' | 'md' | 'lg';\n /** Corner radius preset. `circle` matches `<Avatar>`. */\n shape?: 'square' | 'rounded' | 'circle';\n}\n\nconst SIZE_CLASS: Record<\n NonNullable<ImageCellRendererParams['size']>,\n string\n> = {\n sm: 'ds:size-8',\n md: 'ds:size-10',\n lg: 'ds:size-12',\n};\n\nconst SHAPE_CLASS: Record<\n NonNullable<ImageCellRendererParams['shape']>,\n string\n> = {\n square: 'ds:rounded-none',\n rounded: 'ds:rounded-[var(--radius-md)]',\n circle: 'ds:rounded-[var(--radius-full)]',\n};\n\nfunction readField(row: unknown, field: string): string | undefined {\n if (!row || typeof row !== 'object') return undefined;\n const value = (row as Record<string, unknown>)[field];\n return typeof value === 'string' ? value : undefined;\n}\n\n// Accept http(s), image data: URLs, and same-origin absolute paths. Reject\n// `javascript:` / `vbscript:` / any other smuggled scheme. Browsers won't\n// execute non-image data: URLs in `<img>`, but rejecting them in JS gives a\n// single audit point.\n//\n// A leading `/` cannot carry a scheme, so a root-relative path is at least as\n// safe as an absolute URL — and it is what an app serving its own images\n// naturally produces. `//host/x` IS excluded: protocol-relative URLs are\n// cross-origin, which is the thing this guard exists to control.\nconst SAFE_SRC_RE = /^(?:https?:|data:image\\/|\\/(?!\\/))/i;\n\nfunction sanitizeSrc(raw: string | undefined): string | undefined {\n if (!raw) return undefined;\n const trimmed = raw.trim();\n if (trimmed.length === 0) return undefined;\n return SAFE_SRC_RE.test(trimmed) ? trimmed : undefined;\n}\n\nexport function ImageCellRenderer<TData = unknown>(\n props: CustomCellRendererProps<TData> & ImageCellRendererParams,\n) {\n const {\n data,\n srcField,\n altField,\n fallbackField,\n size = 'md',\n shape = 'rounded',\n } = props;\n\n if (!data) return null;\n\n const rawSrc = readField(data, srcField);\n const src = sanitizeSrc(rawSrc);\n const alt = altField ? (readField(data, altField) ?? '') : '';\n const fallbackName = fallbackField\n ? readField(data, fallbackField)\n : undefined;\n\n // A circular image with a name is a person: let <Avatar> own it. It paints the\n // initials first, covers them once the photo decodes, and drops the <img> on\n // error so the initials persist — instead of a blank cell that later pops.\n if (shape === 'circle' && fallbackName) {\n return <Avatar src={src} name={fallbackName} size={size} />;\n }\n\n if (!src) {\n if (fallbackName) {\n return <Avatar name={fallbackName} size={size} />;\n }\n return null;\n }\n\n return (\n <img\n src={src}\n alt={alt}\n loading=\"lazy\"\n decoding=\"async\"\n className={['ds:object-cover', SIZE_CLASS[size], SHAPE_CLASS[shape]].join(\n ' ',\n )}\n />\n );\n}\n","import type { CustomCellRendererProps } from 'ag-grid-react';\nimport { useTranslation } from 'react-i18next';\nimport { Tag } from '../../tag/tag';\nimport { Tooltip } from '../../tooltip';\n\nexport interface TagListCellRendererParams {\n maxVisible?: number;\n}\n\nexport function TagListCellRenderer(\n props: CustomCellRendererProps<unknown, string[]> & TagListCellRendererParams,\n) {\n const { value, maxVisible = 3 } = props;\n const { t } = useTranslation();\n\n if (!Array.isArray(value) || value.length === 0) return null;\n\n const visible = value.slice(0, maxVisible);\n const overflow = value.length - maxVisible;\n\n return (\n <span className=\"ds:inline-flex ds:flex-wrap ds:items-center ds:gap-[var(--spacing-xs)]\">\n {visible.map((tag) => (\n <Tag key={tag} label={tag} size=\"sm\" />\n ))}\n {overflow > 0 && (\n <Tooltip label={value.slice(maxVisible).join(', ')}>\n <Tag\n label={t('inputs.multiSelect.overflow', { count: overflow })}\n size=\"sm\"\n variant=\"neutral\"\n fill=\"outline\"\n />\n </Tooltip>\n )}\n </span>\n );\n}\n","import type { CustomCellRendererProps } from 'ag-grid-react';\nimport { Timestamp } from '../../timestamp';\n\nexport type DateCellFormat = 'date' | 'time' | 'datetime' | 'relative';\n\nexport interface DateCellRendererParams {\n /** Which preset to use. Default `'date'`. */\n format?: DateCellFormat;\n /** Escape hatch — overrides the preset entirely. */\n options?: Intl.DateTimeFormatOptions;\n}\n\nconst PRESETS: Record<\n Exclude<DateCellFormat, 'relative'>,\n Intl.DateTimeFormatOptions\n> = {\n date: { year: 'numeric', month: 'short', day: 'numeric' },\n time: { hour: '2-digit', minute: '2-digit' },\n datetime: {\n year: 'numeric',\n month: 'short',\n day: 'numeric',\n hour: '2-digit',\n minute: '2-digit',\n },\n};\n\nexport function DateCellRenderer(\n props: CustomCellRendererProps & DateCellRendererParams,\n) {\n const { value, format = 'date', options } = props;\n if (value == null || value === '') return null;\n\n // `shape=\"bare\"` keeps the cell font inherited from --ag-font-size. Timestamp\n // still emits a proper `<time dateTime>` for assistive tech and feeds.\n if (format === 'relative' && !options) {\n return <Timestamp value={value as string | Date} shape=\"bare\" />;\n }\n\n return (\n <Timestamp\n value={value as string | Date}\n format=\"absolute\"\n shape=\"bare\"\n absoluteFormat={\n options ?? PRESETS[format === 'relative' ? 'date' : format]\n }\n />\n );\n}\n","import type { MouseEvent } from 'react';\nimport type { CustomCellRendererProps } from 'ag-grid-react';\n\ntype Getter<T, TData> = T | ((data: TData) => T | undefined);\n\nexport interface LinkCellRendererParams<TData = unknown> {\n /** Anchor href — or a function that derives it from the row. */\n href?: Getter<string, TData>;\n /** Fallback / additional onClick. Prevents default when provided without href. */\n onClick?: (data: TData) => void;\n /** Small text rendered below the link — supports a row-aware function. */\n secondary?: Getter<string, TData>;\n}\n\nfunction resolve<T, TData>(\n source: Getter<T, TData> | undefined,\n data: TData,\n): T | undefined {\n if (typeof source === 'function') {\n return (source as (data: TData) => T | undefined)(data);\n }\n return source;\n}\n\nfunction safeHref(href: string): string {\n return /^(https?:\\/\\/|\\/|#|mailto:|tel:)/i.test(href) ? href : '#';\n}\n\nexport function LinkCellRenderer<TData = unknown>(\n props: CustomCellRendererProps<TData> & LinkCellRendererParams<TData>,\n) {\n const { value, data, href, onClick, secondary } = props;\n if (!data) return null;\n\n const resolvedHref = resolve(href, data);\n const resolvedSecondary = resolve(secondary, data);\n const content = value == null || value === '' ? '' : String(value);\n\n const linkClasses = [\n 'ds:text-[color:var(--primary)]',\n 'ds:hover:underline',\n 'ds:focus-visible:outline-[length:var(--focus-ring-width)]',\n 'ds:focus-visible:outline-solid',\n 'ds:focus-visible:outline-[color:var(--ring)]',\n 'ds:focus-visible:outline-offset-[length:var(--focus-ring-offset)]',\n 'ds:rounded-[var(--radius-sm)]',\n ].join(' ');\n\n const handleClick = (event: MouseEvent) => {\n if (!onClick) return;\n if (!resolvedHref) event.preventDefault();\n event.stopPropagation();\n onClick(data);\n };\n\n return (\n <span className=\"ds:flex ds:flex-col ds:leading-tight\">\n {resolvedHref ? (\n <a\n href={safeHref(resolvedHref)}\n onClick={handleClick}\n className={linkClasses}\n >\n {content}\n </a>\n ) : (\n <button\n type=\"button\"\n onClick={handleClick}\n className={[\n 'ds:appearance-none ds:bg-transparent ds:p-0 ds:text-start',\n linkClasses,\n ].join(' ')}\n >\n {content}\n </button>\n )}\n {resolvedSecondary ? (\n <span className=\"type-meta ds:text-[color:var(--muted-foreground)]\">\n {resolvedSecondary}\n </span>\n ) : null}\n </span>\n );\n}\n"],"names":["toIsoDate","d","fromIsoDate","s","DateRangeFilter","props","model","onModelChange","getValue","doesRowPassOtherFilter","filterParams","_a","range","setRange","useState","useEffect","useGridFilter","params","from","to","cellValue","cellDate","apply","next","jsx","DateRangePicker","DateRangeFloatingFilter","column","_b","toValueKey","v","LABEL_COLLATOR","collectColumnOptions","api","readValue","seen","node","raw","value","key","b","sameOptions","a","opt","i","useSelectFilterOptions","options","explicit","hasExplicit","derived","setDerived","readValueRef","useRef","recompute","prev","SelectFilter","placeholder","t","useTranslation","useCallback","derivedOptions","initialSelected","useMemo","set","incoming","pending","setPending","active","toggle","optionKey","values","clear","placeholderLabel","jsxs","ScrollArea","option","Checkbox","Button","SelectFloatingFilter","colDef","availableOptions","accessibleLabel","valueByKey","map","selectOptions","currentValue","handleChange","k","original","MultiSelect","normalise","TypeaheadFilter","loadOptions","adapterLoadOptions","query","opts","o","q","draft","setDraft","target","handleSelect","Autocomplete","TypeaheadFloatingFilter","ALL_OPERATORS","operatorTakesValue","op","operatorTakesRange","evaluate","cell","primary","secondary","lo","hi","coerceCell","n","NumberFilter","defaultOperator","min","max","step","initialOperator","operator","setOperator","setPrimary","setSecondary","operatorOptions","showValue","showRange","Select","NumberInput","NumberFloatingFilter","handle","StatusCellRenderer","variantMap","variant","Badge","UserCellRenderer","Avatar","SIZE_CLASS","SHAPE_CLASS","readField","row","field","SAFE_SRC_RE","sanitizeSrc","trimmed","ImageCellRenderer","data","srcField","altField","fallbackField","size","shape","rawSrc","src","alt","fallbackName","TagListCellRenderer","maxVisible","visible","overflow","tag","Tag","Tooltip","PRESETS","DateCellRenderer","format","Timestamp","resolve","source","safeHref","href","LinkCellRenderer","onClick","resolvedHref","resolvedSecondary","content","linkClasses","handleClick","event"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"notes-panel-e9cJWVRg.js","sources":["../../src/components/notes-panel/notes-panel.agent.ts","../../src/components/notes-panel/notes-panel.tsx"],"sourcesContent":["/* -------------------------------------------------------------------- */\n/* Agent adapter — NotesPanel. */\n/* */\n/* The Notes tool: a composer over a note timeline. The agent reads the */\n/* timeline STRUCTURE (note ids + pinned / flagged / clinical flags — */\n/* never the note body or author, both PHI), composes and commits a new */\n/* note, and toggles a note's pinned / flagged state. `add_note` carries */\n/* the `phi` safety class: a note body is patient-identifiable clinical */\n/* content, so the host gates it behind the same confirm-before-act as a */\n/* destructive write and never auto-confirms. A clinical submission also */\n/* requires the host's `practice_add_clinical_note` permission (the */\n/* handle drops it when the gate is closed). */\n/* -------------------------------------------------------------------- */\n\nimport type { AgentAdapter } from '../../agent/types';\nimport type { NotesPanelHandle } from './notes-panel';\n\nexport const notesPanelAgent: AgentAdapter<NotesPanelHandle> = {\n id: 'notes-panel',\n // 'submit' backs add_note (compose + commit a note). pin / flag are toggles\n // with no dedicated capability in the closed vocabulary.\n capabilities: ['submit'],\n state: {\n notes: {\n type: 'Array<{ id?: string; pinned: boolean; flagged: boolean; clinical: boolean }>',\n descriptionKey: 'ui.agent.notesPanel.state.notes',\n description:\n 'Timeline note ids + pinned / flagged / clinical flags. Never the note body or author (both PHI).',\n read: (handle) => handle.getNotes(),\n },\n canAddClinical: {\n type: 'boolean',\n descriptionKey: 'ui.agent.notesPanel.state.canAddClinical',\n description:\n 'Whether the current user may write clinical notes (the practice_add_clinical_note permission gate).',\n read: (handle) => handle.canAddClinical(),\n },\n currentNoteId: {\n type: 'string | null',\n descriptionKey: 'ui.agent.notesPanel.state.currentNoteId',\n description:\n 'Id of the note the host has addressed — the one marked, scrolled to and focused. Null when none is. An id only, never note content.',\n read: (handle) => handle.getCurrentNoteId(),\n },\n },\n actions: {\n add_note: {\n // Writes a clinical/admin note body — patient-identifiable content. PHI.\n safety: 'phi',\n argsType: '{ content: string; clinical: boolean }',\n argsSchema: {\n type: 'object',\n properties: {\n content: {\n type: 'string',\n description:\n 'Note body. Rich HTML for the rich composer, else plain text.',\n },\n clinical: {\n type: 'boolean',\n description:\n 'Whether the note is clinical. Requires the clinical-note permission; the submission is dropped when the gate is closed.',\n },\n },\n required: ['content', 'clinical'],\n },\n openWorld: true,\n descriptionKey: 'ui.agent.notesPanel.actions.addNote',\n description:\n 'Compose and commit a new note. A clinical note requires the clinical-note permission (read canAddClinical first).',\n examples: [\n {\n args: { content: 'Patient reported improvement.', clinical: true },\n note: 'Add a clinical note.',\n },\n ],\n invoke: (handle, args: { content: string; clinical: boolean }) => {\n handle.addNote(args);\n },\n },\n toggle_pin: {\n // Re-orders the timeline (pinned notes lead) — reversible view write.\n safety: 'write',\n argsType: '{ id: string }',\n argsSchema: {\n type: 'object',\n properties: { id: { type: 'string' } },\n required: ['id'],\n },\n idempotent: false,\n descriptionKey: 'ui.agent.notesPanel.actions.togglePin',\n description: 'Toggle the pinned state of the note with the given id.',\n invoke: (handle, args: { id: string }) => {\n handle.togglePin(args.id);\n },\n },\n toggle_flag: {\n safety: 'write',\n argsType: '{ id: string }',\n argsSchema: {\n type: 'object',\n properties: { id: { type: 'string' } },\n required: ['id'],\n },\n idempotent: false,\n descriptionKey: 'ui.agent.notesPanel.actions.toggleFlag',\n description: 'Toggle the flagged state of the note with the given id.',\n invoke: (handle, args: { id: string }) => {\n handle.toggleFlag(args.id);\n },\n },\n },\n domHooks: {\n root: {\n attr: 'data-component',\n value: 'notes-panel',\n description: 'Marks the NotesPanel region.',\n },\n instanceId: {\n attr: 'data-component-id',\n sourceProp: 'id',\n description:\n 'Sourced from the id prop. Required to address a specific notes panel from the agent.',\n },\n },\n};\n","import {\n forwardRef,\n useCallback,\n useEffect,\n useId,\n useImperativeHandle,\n useMemo,\n useRef,\n useState,\n type HTMLAttributes,\n type ReactNode,\n} from 'react';\nimport { cva } from 'class-variance-authority';\nimport { useTranslation } from 'react-i18next';\nimport { useAgentRegistration } from '../../agent/registry';\nimport { notesPanelAgent } from './notes-panel.agent';\nimport { ClinicalNoteCard } from '../clinical-note-card';\nimport type { ClinicalNoteCardProps } from '../clinical-note-card';\nimport { RichTextEditor } from '../rich-text-editor';\nimport type {\n RichTextEditorHandle,\n RichTextEditorProps,\n RichTextEditorToolbar,\n RedactorFactory,\n} from '../rich-text-editor';\nimport { TextArea } from '../text-area';\nimport { Button } from '../button';\nimport { Checkbox } from '../checkbox';\nimport { NotebookPen } from 'lucide-react';\nimport { PanelHeader } from '../panel-header';\n\n/* ------------------------------------------------------------------ */\n/* NotesPanel — the Notes tool for the Control Room rail. */\n/* */\n/* A composer over an elevated ClinicalNoteCard timeline. Drops */\n/* into a ControlRoomRail tool `panel`, the patient profile, or the */\n/* care-plan editor — anywhere notes about the open entity belong. */\n/* The composer is the kit RichTextEditor by default (degrades to a */\n/* textarea where Redactor is absent), carries a permission-gated */\n/* \"clinical note\" toggle, and forwards the editor's AI affordances. */\n/* The note attaches to whatever entity the host is showing — the */\n/* host owns the attachment; the panel only emits content + clinical. */\n/* Pinned notes lead; pin / flag are inline per-note toggles. */\n/* ------------------------------------------------------------------ */\n\n/**\n * One note in the panel — the data subset of `ClinicalNoteCard`.\n *\n * `aria-label` names the card for assistive tech. Set it whenever a row is not\n * a clinical note: the card's fallback name is \"Clinical note\", so an entry\n * that is something else (a dated event, an import) is announced as one.\n */\nexport type NotesPanelNote = Pick<\n ClinicalNoteCardProps,\n | 'id'\n | 'dateIso'\n | 'createdAtIso'\n | 'author'\n | 'clinical'\n | 'attachedTo'\n | 'content'\n | 'contentFormat'\n | 'pinned'\n | 'flagged'\n | 'aria-label'\n>;\n\n/**\n * Curated agent handle for NotesPanel. Reads the note timeline's STRUCTURE\n * (ids + pinned / flagged / clinical flags — never the note body or author,\n * both PHI) and drives the composer + per-note toggles. See\n * `notes-panel.agent.ts`.\n */\nexport interface NotesPanelHandle {\n /** Timeline snapshot — ids + flags only, never note content (PHI). */\n getNotes: () => Array<{\n id?: string;\n pinned: boolean;\n flagged: boolean;\n clinical: boolean;\n }>;\n /** Whether the composer is mounted (onAddNote is wired). */\n canAddNote: () => boolean;\n /** Whether clinical notes may be written (the permission gate). */\n canAddClinical: () => boolean;\n /** Id of the note the host has addressed, or null when none is. */\n getCurrentNoteId: () => string | null;\n /** Commit a new note with explicit content (mirrors the composer's submit). */\n addNote: (submission: NotesPanelSubmission) => void;\n /** Toggle a note's pinned state (mirrors onTogglePin). */\n togglePin: (id: string) => void;\n /** Toggle a note's flagged state (mirrors onToggleFlag). */\n toggleFlag: (id: string) => void;\n}\n\n/** Payload handed to `onAddNote` when the composer commits. */\nexport interface NotesPanelSubmission {\n /** The note body — rich HTML (`composer='rich'`) or plain text. */\n content: string;\n /** Whether the \"clinical note\" toggle was set. */\n clinical: boolean;\n}\n\nexport interface NotesPanelProps extends Omit<\n HTMLAttributes<HTMLElement>,\n 'title' | 'children'\n> {\n /**\n * Stable instance id. Surfaced on the root as `data-component-id` so an\n * agent / MCP UI bridge can address this specific notes panel.\n */\n id?: string;\n /** Notes to render, newest-first by convention. Pinned notes lead. */\n notes: NotesPanelNote[];\n /** Commit a new note. Omit to hide the composer (read-only timeline). */\n onAddNote?: (submission: NotesPanelSubmission) => void;\n /**\n * The note the reader ASKED for, addressed by id from somewhere else in the\n * record — a marker in a chart, a row in a grid, an agent. The panel marks\n * it, scrolls it to the middle of its own scroll region and moves focus to\n * it, and renders it unclamped so the thing that was asked for is readable\n * in full without a second click.\n *\n * Opening a panel is not the same as answering a question: a rail's\n * `setActiveTool` moves no focus and announces nothing, so a control whose\n * name promises \"in the notes panel\" produces no perceivable change for a\n * keyboard or screen-reader reader unless the panel lands them on the note.\n * That is what this prop is for; without it, a host has to reach into the\n * panel's DOM to do the same job.\n *\n * Marked on three channels, never colour alone (WCAG 1.4.1): a solid\n * inline-start bar the other notes do not have, a tinted gutter, and\n * `aria-current` plus a visually-hidden sentence naming the mark.\n *\n * Ids that match no note are ignored. To address the SAME note twice — the\n * reader pressing the same marker again after scrolling away — bump\n * `currentNoteKey` alongside it; this prop alone is a value, not a request.\n */\n currentNoteId?: string;\n /**\n * Bump on every address to re-scroll and re-focus the note in `currentNoteId`\n * even when the id has not changed.\n *\n * Addressing is a request, not a value: a reader who presses the same marker\n * a second time (having scrolled away, or having moved focus off the note) is\n * asking to be taken back, and a control whose name promises the notes panel\n * must do something perceivable every time it is pressed. Without this the\n * second press is inert — the panel is already showing that note, so nothing\n * re-runs. Hosts increment a counter in the same handler that opens the tool.\n */\n currentNoteKey?: string | number;\n\n /* ----- Composer ----- */\n /**\n * `'rich'` (default) mounts the kit `RichTextEditor` (Redactor; degrades to a\n * textarea where Redactor is absent). `'plain'` keeps a simple `TextArea` for\n * lightweight surfaces / deterministic tests.\n */\n composer?: 'rich' | 'plain';\n /** Toolbar preset for the rich composer. Defaults to `'standard'`. */\n editorToolbar?: RichTextEditorToolbar;\n /** Inject the Redactor factory for the rich composer (else `window.$R`). */\n redactor?: RedactorFactory;\n /** Consumer image-upload endpoint for the rich composer. */\n imageUploadUrl?: string;\n\n /* ----- Clinical flag (permission-gated) ----- */\n /** Show the \"clinical note\" toggle in the composer. Default `true`. */\n showClinicalToggle?: boolean;\n /**\n * Permission gate — when `false` the clinical toggle is disabled and shows\n * `clinicalGatedReason` on hover/focus. Mirrors platform's\n * `practice_add_clinical_note` permission. Default `true`.\n */\n canAddClinicalNote?: boolean;\n /** Tooltip shown when the clinical toggle is gated off. */\n clinicalGatedReason?: string;\n /** Initial state of the clinical toggle. Default `false`. */\n defaultClinical?: boolean;\n\n /* ----- AI assistance (forwarded to RichTextEditor) ----- */\n onRequestAiDraft?: RichTextEditorProps['onRequestAiDraft'];\n aiDraftFormat?: RichTextEditorProps['aiDraftFormat'];\n aiPrivacyNotice?: RichTextEditorProps['aiPrivacyNotice'];\n onDictate?: RichTextEditorProps['onDictate'];\n onProofread?: RichTextEditorProps['onProofread'];\n onAutoFormat?: RichTextEditorProps['onAutoFormat'];\n\n /* ----- Per-note actions ----- */\n /** Toggle a note's pinned state — renders an inline pin button per note. */\n onTogglePin?: (id: string) => void;\n /** Toggle a note's flagged state — renders an inline flag button per note. */\n onToggleFlag?: (id: string) => void;\n\n /* ----- Labels / chrome ----- */\n /** Panel heading. Defaults to the localised \"Notes\". */\n title?: string;\n /** Composer placeholder. */\n composerPlaceholder?: string;\n /** Save-button label. */\n addNoteLabel?: string;\n /** Rendered when there are no notes. */\n emptyState?: ReactNode;\n /** Accessible name for the panel region (defaults to `title`). */\n 'aria-label'?: string;\n}\n\n/* The timeline row. The addressed note wears a solid inline-start bar the\n others do not — a shape channel, so the mark is not colour alone. Every row\n reserves the bar's width in `transparent`, so marking one shifts nothing. */\nconst noteItemVariants = cva(\n 'ds:flex ds:flex-col ds:rounded-[var(--radius-md)] ds:border-s-2 ds:ps-[var(--spacing-sm)]',\n {\n variants: {\n current: {\n true: 'ds:border-[color:var(--info)] ds:bg-[color:var(--info)]/10 ds:focus:outline-[length:var(--focus-ring-width)] ds:focus:outline-solid ds:focus:outline-[color:var(--ring)] ds:focus:outline-offset-[length:var(--focus-ring-offset)]',\n false: 'ds:border-transparent',\n },\n },\n defaultVariants: { current: false },\n },\n);\n\n/** Strip tags / entities to test whether a rich draft holds real content. */\nfunction hasRichContent(html: string): boolean {\n if (!html) return false;\n if (/<img\\b/i.test(html)) return true;\n return (\n html\n .replace(/<[^>]*>/g, '')\n .replace(/ /gi, ' ')\n .trim().length > 0\n );\n}\n\nexport const NotesPanel = forwardRef<HTMLElement, NotesPanelProps>(\n function NotesPanel(\n {\n id,\n notes,\n onAddNote,\n currentNoteId,\n currentNoteKey,\n composer = 'rich',\n editorToolbar = 'standard',\n redactor,\n imageUploadUrl,\n showClinicalToggle = true,\n canAddClinicalNote = true,\n clinicalGatedReason,\n defaultClinical = false,\n onRequestAiDraft,\n aiDraftFormat,\n aiPrivacyNotice,\n onDictate,\n onProofread,\n onAutoFormat,\n onTogglePin,\n onToggleFlag,\n title,\n composerPlaceholder,\n addNoteLabel,\n emptyState,\n className,\n 'aria-label': ariaLabel,\n ...rest\n },\n ref,\n ) {\n const { t } = useTranslation();\n const editorRef = useRef<RichTextEditorHandle>(null);\n const [draft, setDraft] = useState('');\n const [clinical, setClinical] = useState(defaultClinical);\n\n const heading = title ?? t('notesPanel.title', { defaultValue: 'Notes' });\n const headingId = useId();\n const placeholder =\n composerPlaceholder ??\n t('notesPanel.placeholder', { defaultValue: 'Add a note…' });\n const composerLabel = t('notesPanel.composerLabel', {\n defaultValue: 'New note',\n });\n\n // Live content check: plain reads the draft directly; rich strips the draft\n // (debounced) for the disabled state but commits from the editor handle.\n const hasContent =\n composer === 'rich' ? hasRichContent(draft) : draft.trim().length > 0;\n\n const commit = useCallback(() => {\n if (!onAddNote) return;\n if (composer === 'rich') {\n const html = editorRef.current?.getHTML() ?? draft;\n if (!hasRichContent(html)) return;\n onAddNote({ content: html, clinical });\n editorRef.current?.clear();\n } else {\n const text = draft.trim();\n if (!text) return;\n onAddNote({ content: text, clinical });\n }\n setDraft('');\n setClinical(defaultClinical);\n }, [composer, draft, clinical, defaultClinical, onAddNote]);\n\n // Pinned notes lead; order within each group is preserved.\n const ordered = [...notes].sort(\n (a, b) => Number(b.pinned ?? false) - Number(a.pinned ?? false),\n );\n\n /* ----- The addressed note (see `currentNoteId`) ----- */\n const currentItemRef = useRef<HTMLLIElement>(null);\n useEffect(() => {\n if (!currentNoteId) return;\n // `center`, not `nearest`: nearest parks the note flush against the edge\n // of the scroll region, which is the least readable place to land.\n currentItemRef.current?.scrollIntoView({ block: 'center' });\n currentItemRef.current?.focus();\n // `currentNoteKey` is in the deps so a REPEAT address of the same note\n // re-runs — see the prop doc. Addressing is a request, not a value.\n }, [currentNoteId, currentNoteKey]);\n\n /* ----- Agent handle (structural reads + composer / toggles) ----- */\n const notesRef = useRef(notes);\n notesRef.current = notes;\n const onAddNoteRef = useRef(onAddNote);\n onAddNoteRef.current = onAddNote;\n const onTogglePinRef = useRef(onTogglePin);\n onTogglePinRef.current = onTogglePin;\n const onToggleFlagRef = useRef(onToggleFlag);\n onToggleFlagRef.current = onToggleFlag;\n const canAddClinicalRef = useRef(canAddClinicalNote);\n canAddClinicalRef.current = canAddClinicalNote;\n const currentNoteIdRef = useRef(currentNoteId);\n currentNoteIdRef.current = currentNoteId;\n\n const rootRef = useRef<HTMLElement>(null);\n useImperativeHandle(ref, () => rootRef.current as HTMLElement, []);\n\n const handle = useMemo<NotesPanelHandle>(\n () => ({\n // Ids + flags only — never the note body or author (both PHI).\n getNotes: () =>\n notesRef.current.map((n) => ({\n id: n.id,\n pinned: !!n.pinned,\n flagged: !!n.flagged,\n clinical: !!n.clinical,\n })),\n canAddNote: () => !!onAddNoteRef.current,\n canAddClinical: () => canAddClinicalRef.current,\n getCurrentNoteId: () => currentNoteIdRef.current ?? null,\n addNote: (submission) => {\n // A clinical submission requires the permission gate to be open.\n if (submission.clinical && !canAddClinicalRef.current) return;\n onAddNoteRef.current?.(submission);\n },\n togglePin: (noteId) => onTogglePinRef.current?.(noteId),\n toggleFlag: (noteId) => onToggleFlagRef.current?.(noteId),\n }),\n [],\n );\n useAgentRegistration(notesPanelAgent, handle, id);\n\n return (\n <section\n ref={rootRef}\n data-component=\"notes-panel\"\n data-component-id={id}\n aria-labelledby={ariaLabel ? undefined : headingId}\n aria-label={ariaLabel}\n className={['ds:flex ds:flex-col', className].filter(Boolean).join(' ')}\n {...rest}\n >\n {/* Shared rail header — pins to the top of the panel's scroll region\n while the composer + note timeline scroll beneath it. The rail\n injects the favourite / close actions via PanelHeaderActionsProvider. */}\n <PanelHeader\n title={heading}\n icon={<NotebookPen aria-hidden=\"true\" />}\n headingId={ariaLabel ? undefined : headingId}\n sticky\n divider\n />\n\n <div className=\"ds:flex ds:flex-col ds:gap-[var(--spacing-md)] ds:p-[var(--spacing-md)]\">\n {onAddNote ? (\n <div className=\"ds:flex ds:flex-col ds:gap-[var(--spacing-sm)]\">\n {composer === 'rich' ? (\n <RichTextEditor\n ref={editorRef}\n ariaLabel={composerLabel}\n placeholder={placeholder}\n toolbar={editorToolbar}\n minHeight=\"sm\"\n redactor={redactor}\n imageUploadUrl={imageUploadUrl}\n onChange={({ html }) => setDraft(html)}\n onRequestAiDraft={onRequestAiDraft}\n aiDraftFormat={aiDraftFormat}\n aiPrivacyNotice={aiPrivacyNotice}\n onDictate={onDictate}\n onProofread={onProofread}\n onAutoFormat={onAutoFormat}\n />\n ) : (\n <TextArea\n value={draft}\n onChange={(event) => setDraft(event.target.value)}\n placeholder={placeholder}\n autoResize\n minRows={2}\n maxRows={6}\n aria-label={composerLabel}\n />\n )}\n <div\n className={[\n 'ds:flex ds:flex-wrap ds:items-center ds:gap-[var(--spacing-sm)]',\n showClinicalToggle ? 'ds:justify-between' : 'ds:justify-end',\n ].join(' ')}\n >\n {showClinicalToggle ? (\n <Checkbox\n size=\"sm\"\n label={t('notesPanel.clinicalToggle', {\n defaultValue: 'Clinical note',\n })}\n checked={clinical}\n onCheckedChange={(next) => setClinical(next === true)}\n disabled={!canAddClinicalNote}\n disabledReason={\n canAddClinicalNote\n ? undefined\n : (clinicalGatedReason ??\n t('notesPanel.clinicalGated', {\n defaultValue:\n 'You do not have permission to write clinical notes.',\n }))\n }\n />\n ) : null}\n <Button\n type=\"button\"\n size=\"sm\"\n onClick={commit}\n disabled={!hasContent}\n >\n {addNoteLabel ??\n t('notesPanel.add', { defaultValue: 'Add note' })}\n </Button>\n </div>\n </div>\n ) : null}\n\n {ordered.length > 0 ? (\n <ul className=\"ds:m-0 ds:flex ds:list-none ds:flex-col ds:gap-[var(--spacing-sm)] ds:p-0\">\n {ordered.map((note, index) => {\n const isCurrent =\n currentNoteId != null && note.id === currentNoteId;\n return (\n <li\n key={note.id ?? index}\n ref={isCurrent ? currentItemRef : undefined}\n // Focus lands on the row, not inside the card, so the\n // sentence below and `aria-current` are read out before the\n // note — and the rail's own Escape still bubbles.\n tabIndex={isCurrent ? -1 : undefined}\n aria-current={isCurrent ? 'true' : undefined}\n className={noteItemVariants({ current: isCurrent })}\n >\n {isCurrent ? (\n <span className=\"ds:sr-only\">\n {t('notesPanel.current', {\n defaultValue: 'Current note',\n })}\n </span>\n ) : null}\n {/* Elevated card per note; the list gap (not a separator)\n spaces them. Bodies clamp to five lines with read-more —\n except the addressed one, which is what the reader came\n for and opens in full. */}\n <ClinicalNoteCard\n surface=\"elevated\"\n truncate={!isCurrent}\n {...note}\n onTogglePin={\n onTogglePin && note.id\n ? () => onTogglePin(note.id as string)\n : undefined\n }\n onToggleFlag={\n onToggleFlag && note.id\n ? () => onToggleFlag(note.id as string)\n : undefined\n }\n />\n </li>\n );\n })}\n </ul>\n ) : (\n (emptyState ?? (\n <p className=\"ds:m-0 type-body-sm ds:text-[color:var(--muted-foreground)]\">\n {t('notesPanel.empty', { defaultValue: 'No notes yet.' })}\n </p>\n ))\n )}\n </div>\n </section>\n );\n },\n);\n"],"names":["notesPanelAgent","handle","args","noteItemVariants","cva","hasRichContent","html","NotesPanel","forwardRef","id","notes","onAddNote","currentNoteId","currentNoteKey","composer","editorToolbar","redactor","imageUploadUrl","showClinicalToggle","canAddClinicalNote","clinicalGatedReason","defaultClinical","onRequestAiDraft","aiDraftFormat","aiPrivacyNotice","onDictate","onProofread","onAutoFormat","onTogglePin","onToggleFlag","title","composerPlaceholder","addNoteLabel","emptyState","className","ariaLabel","rest","ref","t","useTranslation","editorRef","useRef","draft","setDraft","useState","clinical","setClinical","heading","headingId","useId","placeholder","composerLabel","hasContent","commit","useCallback","_a","_b","text","ordered","a","b","currentItemRef","useEffect","notesRef","onAddNoteRef","onTogglePinRef","onToggleFlagRef","canAddClinicalRef","currentNoteIdRef","rootRef","useImperativeHandle","useMemo","n","submission","noteId","useAgentRegistration","jsxs","jsx","PanelHeader","NotebookPen","RichTextEditor","TextArea","event","Checkbox","next","Button","note","index","isCurrent","ClinicalNoteCard"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"tool-rail-CHyUmulN.js","sources":["../../src/components/tool-rail/tool-rail.agent.ts","../../src/components/tool-rail/tool-rail.tsx"],"sourcesContent":["/* -------------------------------------------------------------------- */\n/* Agent adapter — ToolRail. */\n/* */\n/* Rail-level surface: open / close a tool's panel and read which tool */\n/* is active. State is strictly the active tool id — never any tool's */\n/* panel CONTENT (which may carry PHI). Tool ids + badge counts are */\n/* supplied by the host via props, not derived from panel bodies. */\n/* -------------------------------------------------------------------- */\n\nimport type { AgentAdapter } from '../../agent/types';\nimport type { ToolRailHandle } from './tool-rail';\n\nexport const toolRailAgent: AgentAdapter<ToolRailHandle> = {\n id: 'tool-rail',\n capabilities: ['open', 'close', 'select_single'],\n state: {\n activeToolId: {\n type: 'string | null',\n descriptionKey: 'ui.toolRail.agent.state.activeToolId',\n description:\n 'Id of the tool whose panel is currently open, or null when the rail is collapsed to its strip. No panel content is ever exposed.',\n read: (handle) => handle.getActiveToolId(),\n },\n },\n actions: {\n open: {\n // Pure ephemeral panel state (open) — no data loss, no external effect.\n safety: 'read',\n argsType: '{ id: string }',\n argsSchema: {\n type: 'object',\n properties: { id: { type: 'string' } },\n required: ['id'],\n },\n idempotent: true,\n // descriptionKey shape is intentionally pre-existing — already resolves to\n // real multi-locale translations; do not canonicalise.\n descriptionKey: 'ui.toolRail.agent.actions.open',\n description: \"Open the given tool's panel.\",\n examples: [{ args: { id: 'inbox' }, note: 'Open the inbox tool panel.' }],\n invoke: (handle, args: { id: string }) => {\n handle.open(args.id);\n },\n },\n close: {\n safety: 'read',\n idempotent: true,\n descriptionKey: 'ui.toolRail.agent.actions.close',\n description: 'Collapse the rail to its strip (no active tool).',\n invoke: (handle) => {\n handle.close();\n },\n },\n select_tool: {\n safety: 'read',\n argsType: '{ id: string | null }',\n argsSchema: {\n type: 'object',\n properties: { id: { type: ['string', 'null'] } },\n required: ['id'],\n },\n idempotent: true,\n descriptionKey: 'ui.toolRail.agent.actions.selectTool',\n description:\n 'Set the active tool by id (opens its panel), or pass null to collapse.',\n invoke: (handle, args: { id: string | null }) => {\n handle.setActiveTool(args.id);\n },\n },\n },\n domHooks: {\n root: {\n attr: 'data-component',\n value: 'tool-rail',\n description: 'Marks the ToolRail wrapper.',\n },\n instanceId: {\n attr: 'data-component-id',\n sourceProp: 'id',\n description:\n 'Sourced from the id prop. Required to address a specific rail from the agent.',\n },\n item: {\n attr: 'data-tool-id',\n description:\n 'Stable tool id emitted on each strip IconButton. The active icon also carries data-active set to its id.',\n },\n },\n};\n","import {\n forwardRef,\n Fragment,\n useCallback,\n useEffect,\n useId,\n useImperativeHandle,\n useMemo,\n useRef,\n useState,\n type KeyboardEvent as ReactKeyboardEvent,\n type ReactNode,\n} from 'react';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { useTranslation } from 'react-i18next';\nimport { IconButton } from '../button/icon-button';\nimport { ScrollArea } from '../scroll-area/scroll-area';\nimport { TooltipProvider } from '../tooltip/tooltip';\nimport { useControllableState } from '../../hooks/use-controllable-state';\nimport { useEdgeResize } from '../../hooks/use-edge-resize';\nimport { useIsomorphicLayoutEffect } from '../../hooks/use-isomorphic-layout-effect';\nimport { useAgentRegistration } from '../../agent/registry';\nimport type { AgentAdapter } from '../../agent/types';\nimport { toolRailAgent } from './tool-rail.agent';\n\n/* ------------------------------------------------------------------ */\n/* Tokenised widths */\n/* */\n/* Fixed + tokenised so the rail stays constraint-compliant — no */\n/* prop-driven inline styles. Bare `[inline-size:…]` is NOT a */\n/* recognised utility under the `ds:` prefix; it must carry the */\n/* prefix or Tailwind emits no rule. Consumers needing a different */\n/* size wrap the rail and override via CSS. */\n/* ------------------------------------------------------------------ */\nconst RAIL_WIDTH = 'ds:[inline-size:3rem]';\n// Panel width rides a consumer-overridable custom property (set\n// `--tool-rail-panel-inline-size` on the rail or a wrapper) so\n// content-heavy tools (e.g. an inbox feed) can widen their panel\n// without inline styles. Defaults to the 22rem tray-family width.\n// `--ai-tools-rail-panel-inline-size` is the deprecated alias var read\n// here as a fallback so external consumers that set the pre-rename name\n// keep working for one release; the resize hook only ever writes the new\n// `--tool-rail-panel-inline-size`.\nconst PANEL_WIDTH =\n 'ds:[inline-size:var(--tool-rail-panel-inline-size,var(--ai-tools-rail-panel-inline-size,22rem))]';\n// The custom property PANEL_WIDTH reads. The resize hook writes the\n// committed px width here (on the panel element, so it beats consumer\n// class overrides on the root) and removes it on reset.\nconst PANEL_SIZE_PROPERTY = '--tool-rail-panel-inline-size';\n// Resize clamp + nominal default, px (352px = the 22rem token default).\nconst PANEL_MIN_SIZE = 288;\nconst PANEL_MAX_SIZE = 640;\nconst PANEL_DEFAULT_SIZE = 352;\n\n/* ------------------------------------------------------------------ */\n/* Public types */\n/* ------------------------------------------------------------------ */\n\nexport interface ToolRailTool {\n /** Stable tool id — used in `data-active`, the imperative handle, and\n * to label the open panel region. */\n id: string;\n /** Accessible name for the strip icon + tooltip, and the open panel's\n * region label. Never rendered as visible body text. */\n label: string;\n /** lucide-react glyph (rendered `aria-hidden` inside the IconButton). */\n icon: ReactNode;\n /** Panel body for this tool. Rendered inline-start of the strip when\n * `side='end'` (mirrored for `'start'`). The consumer composes it. */\n panel: ReactNode;\n /**\n * Optional non-scrolling header for this tool's panel. Rendered pinned at\n * the top of the open panel — above the scroll region — so a tool's title /\n * filters stay visible as the body scrolls. This is the rail-level home for\n * a sticky tool header; every tool gets it for free.\n */\n header?: ReactNode;\n /**\n * When true the panel body is NOT wrapped in the rail's `ScrollArea` — the\n * tool fills the (height-bounded) panel and owns its own scroll/layout.\n * Use for self-scrolling tools (e.g. `AppointmentTimeline`, whose infinite\n * scroll + today-anchoring + edge fades require it to be the scroll\n * container). Default `false` (the rail scrolls the body).\n */\n fill?: boolean;\n /** Optional unseen / notification count shown on the strip icon. */\n badgeCount?: number;\n /** When true the icon is non-interactive and skipped by roving focus. */\n disabled?: boolean;\n /**\n * Scope tier (drives the two-group divider). The two scope values render in\n * array order with a divider at the boundary between them — order the array\n * so the groups stay contiguous. A divider only appears when BOTH groups are\n * present; omit `scope` on every tool for an ungrouped strip (legacy). The\n * rail attaches no meaning beyond grouping — the host decides which value\n * leads (e.g. the Control Room pins favourites first).\n */\n scope?: 'global' | 'contextual';\n /**\n * When true this icon joins the drag-and-drop / `Alt`+Arrow reorder run.\n * The host supplies `onReorder` and owns the resulting order. Moves are\n * confined to the contiguous run of `reorderable` tools — so if EVERY tool\n * is `reorderable` the run is the whole strip and a tool can be dragged\n * across the scope divider; the host's `onReorder` decides what crossing it\n * means (e.g. the Control Room reads it as pin / unpin). Reordering never\n * changes focus roving — focus follows the moved tool to its new slot.\n */\n reorderable?: boolean;\n}\n\n/**\n * Curated imperative handle for ToolRail. Exposed as the forwardRef\n * target so a future agent / MCP UI bridge can drive the rail without\n * touching the DOM. See `tool-rail.agent.ts`.\n *\n * State surfaced here is strictly identifiers + counts — never any\n * tool's panel content (which may carry PHI).\n */\nexport interface ToolRailHandle {\n /** Currently-active tool id, or null when collapsed to strip-only. */\n getActiveToolId: () => string | null;\n /** Activate a tool by id (opens its panel), or null to collapse. */\n setActiveTool: (id: string | null) => void;\n /** Activate a tool by id (opens its panel). */\n open: (id: string) => void;\n /** Collapse to strip-only (no active tool). */\n close: () => void;\n}\n\n/* ------------------------------------------------------------------ */\n/* CVA — `side` drives the dock edge + which shadow faces main content */\n/* ------------------------------------------------------------------ */\n\nconst railVariants = cva(\n ['ds:flex ds:shrink-0 ds:h-full', 'ds:bg-[var(--background)]'].join(' '),\n {\n variants: {\n side: {\n // Strip docks inline-end; the panel mounts inline-start of it\n // (flex-row, panel rendered first). Shadow faces the main area —\n // no border against main (the rail stays line-free; the only seam\n // is the strip↔panel divider on the open panel, see render).\n // Accessible (AAA) theme swaps the soft chrome shadow for a solid 1px\n // border on the content-facing edge (border-s for end-docked).\n end: 'ds:flex-row ds:shadow-[var(--shadow-chrome-start)] ds:[.theme-accessible_&]:border-s ds:[.theme-accessible_&]:border-[var(--border)] ds:[.theme-accessible_&]:shadow-none',\n // Strip docks inline-start; the panel mounts inline-end of it.\n start:\n 'ds:flex-row-reverse ds:shadow-[var(--shadow-chrome-end)] ds:[.theme-accessible_&]:border-e ds:[.theme-accessible_&]:border-[var(--border)] ds:[.theme-accessible_&]:shadow-none',\n },\n },\n defaultVariants: { side: 'end' },\n },\n);\n\nconst stripVariants = cva(\n [\n 'ds:flex ds:shrink-0 ds:flex-col ds:items-center',\n 'ds:pt-[var(--spacing-md)] ds:pb-[var(--spacing-md)]',\n 'ds:gap-[var(--spacing-xs)]',\n RAIL_WIDTH,\n ].join(' '),\n);\n\nconst panelVariants = cva(\n [\n 'ds:relative ds:flex ds:min-h-0 ds:flex-col ds:h-full',\n 'ds:bg-[var(--background)]',\n PANEL_WIDTH,\n // Defensive: the panel doesn't animate its width today, but if a\n // consumer adds a transition on the var, suppress it mid-drag.\n 'ds:data-[resizing]:transition-none',\n ].join(' '),\n);\n\n// Resize handle on the panel's inner edge — mirrors Resizable's Handle\n// (::before hit area to --min-target-size, hover/focus/drag accent, token\n// focus ring) but absolutely positioned over the panel edge. Like the\n// Sidebar's handle it stays TRANSPARENT at rest: the rail root's\n// `shadow-chrome-*` (which faces the main content) carries the resting seam,\n// exactly as the start-docked Sidebar leans on its own shadow — so the rail\n// never shows the constant coloured bar it used to. The strip only tints\n// accent on hover / focus / drag.\nconst panelResizeHandleVariants = cva(\n [\n 'ds:absolute ds:[inset-block:0] ds:z-[1] ds:select-none ds:touch-none',\n 'ds:[inline-size:var(--resizable-handle-size)] ds:cursor-col-resize',\n 'ds:transition-colors ds:duration-[var(--animation-duration)] ds:motion-reduce:transition-none',\n 'ds:hover:bg-[var(--accent)] ds:focus-visible:bg-[var(--accent)] ds:data-[resizing]:bg-[var(--accent)]',\n 'ds:focus-visible:outline-[length:var(--focus-ring-width)] ds:focus-visible:outline-solid',\n 'ds:focus-visible:outline-[color:var(--ring)] ds:focus-visible:outline-offset-[length:var(--focus-ring-offset)]',\n 'ds:forced-colors:focus-visible:outline-[CanvasText]',\n \"ds:before:absolute ds:before:content-['']\",\n 'ds:before:[block-size:100%]',\n 'ds:before:[inline-size:var(--min-target-size)]',\n 'ds:before:[inset-block:0]',\n 'ds:before:[inset-inline:calc(0px_-_(var(--min-target-size)_-_var(--resizable-handle-size))_/_2)]',\n ],\n {\n variants: {\n edge: {\n 'inline-start': 'ds:start-0',\n 'inline-end': 'ds:end-0',\n },\n },\n defaultVariants: { edge: 'inline-start' },\n },\n);\n\n// Hand-rolled overlay count chip — mirrors the AliaSidebar / MessageTray\n// badge markup. A <Badge> would re-introduce its own landmark/role; the\n// strip icon already carries the count in its accessible name, so this\n// span is purely decorative (`aria-hidden`).\nconst badgeVariants = cva(\n [\n 'ds:absolute ds:-top-[var(--spacing-xs)] ds:-end-[var(--spacing-xs)]',\n 'ds:inline-flex ds:items-center ds:justify-center',\n 'ds:min-w-[calc(var(--spacing-md)+var(--spacing-xs))]',\n 'ds:h-[calc(var(--spacing-md)+var(--spacing-xs))]',\n 'ds:ps-[calc(var(--spacing-xs)/1.5)] ds:pe-[calc(var(--spacing-xs)/1.5)]',\n 'ds:rounded-[var(--radius-full)]',\n 'ds:bg-[color:var(--destructive)] ds:text-[color:var(--destructive-foreground)]',\n 'ds:text-[length:var(--font-size-xs)] ds:font-semibold ds:leading-none',\n 'ds:pointer-events-none ds:select-none',\n 'ds:forced-colors:outline ds:forced-colors:outline-1 ds:forced-colors:outline-[CanvasText]',\n ].join(' '),\n);\n\n// Per-icon slot — the positioned box that hosts the IconButton, its badge and\n// the optional affordance overlay. CVA carries the drag states: the dragged\n// icon dims; a hovered drop target shows a dashed accent ring (logical offset),\n// mirroring the Sidebar's favourite-row affordance.\nconst toolSlotVariants = cva(\n // `group/rail-tool` lets an affordance overlay (e.g. a pin star) reveal on\n // hover / focus-within of the whole slot.\n ['ds:group/rail-tool ds:relative', 'ds:rounded-[var(--radius-sm)]'],\n {\n variants: {\n dragging: { true: 'ds:opacity-60', false: '' },\n dropTarget: {\n true: 'ds:outline-[length:var(--focus-ring-width)] ds:outline-dashed ds:outline-[var(--accent)] ds:outline-offset-[length:var(--focus-ring-offset)]',\n false: '',\n },\n reorderable: {\n true: 'ds:cursor-grab ds:active:cursor-grabbing',\n false: '',\n },\n },\n defaultVariants: {\n dragging: false,\n dropTarget: false,\n reorderable: false,\n },\n },\n);\n\n/* ------------------------------------------------------------------ */\n/* Props */\n/* ------------------------------------------------------------------ */\n\nexport interface ToolRailProps extends VariantProps<typeof railVariants> {\n /** Stable instance id, surfaced on the root as `data-component-id`. */\n id?: string;\n /** The tools shown as icons on the strip. */\n tools: ToolRailTool[];\n /** Controlled active tool id (null = collapsed / strip-only). */\n activeToolId?: string | null;\n /** Uncontrolled initial active tool id (default null). */\n defaultActiveToolId?: string | null;\n /** Fires whenever the active tool changes (incl. → null on collapse). */\n onActiveToolChange?: (id: string | null) => void;\n /** Which edge the rail docks to. Default `'end'` (inline-end). */\n side?: 'start' | 'end';\n /** Accessible label for the toolbar strip. */\n railLabel?: string;\n /**\n * Opt-in resize affordance for the open tool panel. Renders a\n * `role=\"separator\"` drag handle on the panel's inner edge (the edge\n * facing the main content). The committed width is written to\n * `--tool-rail-panel-inline-size` on the panel element (288–640px);\n * Enter / double-click resets to the token-driven default. Default `false`.\n */\n resizablePanel?: boolean;\n /**\n * Fires when the user reorders the `reorderable` run (drag-drop or\n * `Alt`+Arrow). `from` / `to` are indices into `tools`; the host applies the\n * move to the array it passes back. Reordering is enabled when this callback\n * is supplied AND at least one tool is `reorderable`. When the whole strip is\n * reorderable, `to` may land in the other scope group — the host interprets\n * the crossing (and may treat it as a no-op, e.g. reordering within a\n * host-ordered group).\n */\n onReorder?: (from: number, to: number) => void;\n /**\n * Optional override for the polite live-region message announced after a\n * reorder. Lets the host phrase membership-aware moves (e.g. \"Pinned X to\n * favourites\") instead of the generic \"moved to position N\". Return an empty\n * string to stay silent (e.g. for a move the host treats as a no-op). When\n * omitted, the rail announces the generic run-relative position.\n */\n formatReorderAnnouncement?: (args: {\n tool: ToolRailTool;\n from: number;\n to: number;\n }) => string;\n /**\n * Optional per-icon overlay control, rendered as a sibling of each strip\n * IconButton (the same positioned box the notification badge uses). Use for\n * a small affordance such as a pin toggle — the rail attaches no meaning to\n * what it renders. Receives the tool and its index / active state.\n */\n renderToolAffordance?: (\n tool: ToolRailTool,\n context: { index: number; isActive: boolean; isRovingTarget: boolean },\n ) => ReactNode;\n /**\n * Optional non-scrolling header rendered at the top of the OPEN tool panel,\n * above its scroll region. Host-owned content (e.g. a pin / unpin control) —\n * the rail attaches no meaning. Receives the active tool.\n */\n renderPanelHeader?: (\n tool: ToolRailTool,\n context: { isActive: boolean },\n ) => ReactNode;\n /**\n * Identity override for the root's `data-component` attribute. Defaults to\n * `'tool-rail'`. A product wrapper (e.g. ControlRoomRail) passes its own\n * kebab id so the DOM / cascade boundary — and an agent's `domHooks.root` —\n * can tell its surface apart from a bare ToolRail, without the engine ever\n * importing the wrapper.\n */\n dataComponent?: string;\n /**\n * Agent adapter registered for this rail instance. Defaults to\n * `toolRailAgent`. A wrapper passes its own adapter (matching\n * `dataComponent`) so the rail registers under the wrapper's identity. The\n * instance id is sourced from the `id` prop (also stamped as\n * `data-component-id`).\n */\n agentAdapter?: AgentAdapter<ToolRailHandle>;\n className?: string;\n}\n\n/* ------------------------------------------------------------------ */\n/* Helpers */\n/* ------------------------------------------------------------------ */\n\nfunction formatBadgeCount(count: number, overflowLabel: string): string {\n return count > 99 ? overflowLabel : String(count);\n}\n\n/* ------------------------------------------------------------------ */\n/* Root */\n/* ------------------------------------------------------------------ */\n\nexport const ToolRail = forwardRef<ToolRailHandle, ToolRailProps>(\n (\n {\n id,\n tools,\n activeToolId,\n defaultActiveToolId = null,\n onActiveToolChange,\n side = 'end',\n railLabel,\n resizablePanel = false,\n onReorder,\n formatReorderAnnouncement,\n renderToolAffordance,\n renderPanelHeader,\n dataComponent = 'tool-rail',\n agentAdapter = toolRailAgent,\n className,\n },\n ref,\n ) => {\n const { t } = useTranslation();\n\n // Controlled / uncontrolled bridge — the same hook MessageTray uses.\n const [activeRaw, setActiveRaw] = useControllableState<string | null>({\n value: activeToolId,\n defaultValue: defaultActiveToolId,\n onChange: onActiveToolChange,\n });\n const activeId = activeRaw ?? null;\n\n // Roving-tabindex bookkeeping for the vertical toolbar strip.\n const buttonsRef = useRef<Array<HTMLButtonElement | null>>([]);\n const panelRef = useRef<HTMLElement | null>(null);\n const [focusIndex, setFocusIndex] = useState(0);\n // Distinguishes keyboard activation (auto-focus into the panel) from\n // pointer activation (focus stays on the icon — less jarring).\n const activatedByKeyboardRef = useRef(false);\n // Set on Enter/Space keydown, read by the click handler that follows,\n // cleared on keyup — so a subsequent mouse click never inherits it.\n const keyboardActivateRef = useRef(false);\n\n const activeIndex = useMemo(\n () => tools.findIndex((tl) => tl.id === activeId),\n [tools, activeId],\n );\n const activeTool = activeIndex >= 0 ? tools[activeIndex] : null;\n\n // First non-disabled index — the resting roving stop when none active.\n const firstEnabledIndex = useMemo(\n () => tools.findIndex((tl) => !tl.disabled),\n [tools],\n );\n\n // Scope grouping: a divider marks the boundary between the two scope groups,\n // whichever order they appear in. The array is always grouped (all of one\n // scope, then the other); the divider precedes the first tool whose scope\n // differs from the lead tool's. `boundary > 0` guarantees both groups are\n // present. Render-only (not a `tools[]` member) so roving focus is untouched;\n // a legacy strip with no `scope` set stays ungrouped.\n const scopeDividerIndex = useMemo(() => {\n const lead = tools[0]?.scope;\n if (!lead) return -1;\n return tools.findIndex((tl) => tl.scope && tl.scope !== lead);\n }, [tools]);\n const showScopeDivider = scopeDividerIndex > 0;\n\n /* ---- Reorder (drag-and-drop + Alt+Arrow keyboard) ------------- */\n // The contiguous run of `reorderable` tools — a tool only moves within it.\n const reorderRun = useMemo(() => {\n const first = tools.findIndex((tl) => tl.reorderable);\n if (first < 0) return null;\n let last = first;\n while (last + 1 < tools.length && tools[last + 1]?.reorderable) last += 1;\n return { first, last };\n }, [tools]);\n const reorderEnabled = !!onReorder && reorderRun !== null;\n const reorderInstructionsId = useId();\n\n const [dragIndex, setDragIndex] = useState<number | null>(null);\n const [dropIndex, setDropIndex] = useState<number | null>(null);\n\n // sr-only announcement. The zero-width-space toggle forces a re-announce of\n // an identical message (e.g. moving the same item twice) — same technique\n // the Sidebar uses for its reorder live region.\n const [reorderMessage, setReorderMessage] = useState('');\n const announceToggle = useRef(false);\n const commitReorder = useCallback(\n (from: number, to: number) => {\n if (!onReorder || !reorderRun) return;\n const clamped = Math.max(\n reorderRun.first,\n Math.min(reorderRun.last, to),\n );\n if (clamped === from) return;\n onReorder(from, clamped);\n const moved = tools[from];\n if (moved) {\n // The host can phrase membership-aware moves (e.g. pin / unpin) and\n // can return '' to stay silent for a move it treats as a no-op.\n const text = formatReorderAnnouncement\n ? formatReorderAnnouncement({ tool: moved, from, to: clamped })\n : t('toolRail.reorderAnnounce', {\n tool: moved.label,\n to: clamped - reorderRun.first + 1,\n total: reorderRun.last - reorderRun.first + 1,\n defaultValue: '{{tool}} moved to position {{to}} of {{total}}',\n });\n if (text) {\n announceToggle.current = !announceToggle.current;\n setReorderMessage(announceToggle.current ? text : `${text}\\u200b`);\n }\n }\n },\n [onReorder, reorderRun, tools, t, formatReorderAnnouncement],\n );\n\n // The roving tab stop: the active icon when a tool is open, else the\n // index the user last focused, else the first enabled icon.\n const rovingIndex =\n activeIndex >= 0\n ? activeIndex\n : focusIndex >= 0 && !tools[focusIndex]?.disabled\n ? focusIndex\n : firstEnabledIndex;\n\n const selectTool = useCallback(\n (toolId: string, fromKeyboard: boolean) => {\n const tool = tools.find((tl) => tl.id === toolId);\n if (!tool || tool.disabled) return;\n activatedByKeyboardRef.current = fromKeyboard;\n // Clicking / activating the active tool collapses to strip-only.\n setActiveRaw(activeId === toolId ? null : toolId);\n },\n [tools, activeId, setActiveRaw],\n );\n\n /* ---- Roving arrow-key navigation (vertical toolbar) ----------- */\n const handleStripKeyDown = useCallback(\n (event: ReactKeyboardEvent<HTMLDivElement>) => {\n const count = tools.length;\n if (count === 0) return;\n // Alt+Arrow is reserved for reordering (handled on the icon); never\n // let it move focus roving as well.\n if (event.altKey) return;\n\n let next: number | null = null;\n if (event.key === 'ArrowDown') next = (rovingIndex + 1) % count;\n else if (event.key === 'ArrowUp')\n next = (rovingIndex - 1 + count) % count;\n else if (event.key === 'Home') next = 0;\n else if (event.key === 'End') next = count - 1;\n if (next === null) return;\n\n // Skip disabled icons in the travel direction.\n const goingBack = event.key === 'ArrowUp' || event.key === 'End';\n let candidate = next;\n let attempts = 0;\n while (attempts < count && tools[candidate]?.disabled) {\n candidate = goingBack\n ? (candidate - 1 + count) % count\n : (candidate + 1) % count;\n attempts += 1;\n }\n if (tools[candidate]?.disabled) return;\n\n event.preventDefault();\n setFocusIndex(candidate);\n buttonsRef.current[candidate]?.focus();\n },\n [tools, rovingIndex],\n );\n\n /* ---- Esc collapses + returns focus to the active tool's icon -- */\n // Attached imperatively via the root ref (not a JSX onKeyDown on the\n // static container) so the Escape shortcut works from anywhere inside the\n // open panel without an interaction handler on a non-interactive element.\n const rootRef = useRef<HTMLDivElement>(null);\n useEffect(() => {\n const node = rootRef.current;\n if (!node) return undefined;\n const onKeyDown = (event: KeyboardEvent) => {\n if (event.key !== 'Escape') return;\n if (activeIndex < 0) return;\n event.preventDefault();\n const returnIndex = activeIndex;\n setActiveRaw(null);\n setFocusIndex(returnIndex);\n // Defer until after the panel unmounts so focus lands cleanly.\n requestAnimationFrame(() => {\n buttonsRef.current[returnIndex]?.focus();\n });\n };\n node.addEventListener('keydown', onKeyDown);\n return () => node.removeEventListener('keydown', onKeyDown);\n }, [activeIndex, setActiveRaw]);\n\n /* ---- Move focus into the panel when opened by keyboard -------- */\n useIsomorphicLayoutEffect(() => {\n if (activeTool && activatedByKeyboardRef.current) {\n panelRef.current?.focus();\n }\n activatedByKeyboardRef.current = false;\n }, [activeTool]);\n\n /* ---- Opt-in panel resize (inner edge) -------------------------- */\n // The handle sits on the edge facing the main content: the panel's\n // inline-start edge for an end-docked rail, inline-end for start.\n const panelEdge = side === 'end' ? 'inline-start' : 'inline-end';\n const { separatorProps, isResizing, isResized } = useEdgeResize({\n edge: panelEdge,\n targetRef: panelRef,\n cssProperty: PANEL_SIZE_PROPERTY,\n minSize: PANEL_MIN_SIZE,\n maxSize: PANEL_MAX_SIZE,\n defaultSize: PANEL_DEFAULT_SIZE,\n disabled: !resizablePanel,\n });\n\n // The aside unmounts on collapse, dropping the hook's inline override.\n // Re-assert the committed width when a panel (re)mounts so a reopened\n // panel keeps the user's chosen size.\n const committedPanelWidth = separatorProps['aria-valuenow'];\n useIsomorphicLayoutEffect(() => {\n if (!resizablePanel || !isResized || !activeTool) return;\n panelRef.current?.style.setProperty(\n PANEL_SIZE_PROPERTY,\n `${committedPanelWidth}px`,\n );\n }, [activeTool, resizablePanel, isResized, committedPanelWidth]);\n\n /* ---- Curated imperative handle for agent integration ---------- */\n const activeIdRef = useRef<string | null>(activeId);\n activeIdRef.current = activeId;\n\n const handle = useMemo<ToolRailHandle>(\n () => ({\n getActiveToolId: () => activeIdRef.current,\n setActiveTool: (toolId) => {\n activatedByKeyboardRef.current = false;\n setActiveRaw(toolId);\n },\n open: (toolId) => {\n const tool = tools.find((tl) => tl.id === toolId);\n if (!tool || tool.disabled) return;\n activatedByKeyboardRef.current = false;\n setActiveRaw(toolId);\n },\n close: () => {\n const returnId = activeIdRef.current;\n activatedByKeyboardRef.current = false;\n setActiveRaw(null);\n // Return focus to the closed tool's strip icon (mirrors Escape) so a\n // close-button / pointer collapse never drops focus to <body>.\n if (returnId != null) {\n requestAnimationFrame(() => {\n rootRef.current\n ?.querySelector<HTMLButtonElement>(\n `[data-tool-id=\"${CSS.escape(returnId)}\"]`,\n )\n ?.focus();\n });\n }\n },\n }),\n [tools, setActiveRaw],\n );\n\n useImperativeHandle(ref, () => handle, [handle]);\n useAgentRegistration(agentAdapter, handle, id);\n\n const overflowLabel = t('toolRail.badgeCountOverflow', {\n defaultValue: '99+',\n });\n\n return (\n <div\n ref={rootRef}\n data-component={dataComponent}\n data-component-id={id}\n data-side={side}\n className={[railVariants({ side }), className]\n .filter(Boolean)\n .join(' ')}\n >\n {/* Reorder a11y: a polite live region announcing each move. Each\n reorderable icon carries its own keyboard instruction (rendered in\n its slot, referenced via aria-describedby). */}\n {reorderEnabled ? (\n <span role=\"status\" aria-live=\"polite\" className=\"ds:sr-only\">\n {reorderMessage}\n </span>\n ) : null}\n {/* The OPEN panel is the single complementary landmark, named by\n the active tool. Rendered only while a tool is active so two\n unnamed regions never coexist. */}\n {activeTool ? (\n <aside\n ref={panelRef}\n tabIndex={-1}\n aria-label={t('toolRail.panelLabel', {\n tool: activeTool.label,\n defaultValue: '{{tool}} panel',\n })}\n data-tool-rail-state=\"expanded\"\n data-active-tool={activeTool.id}\n data-resizing={isResizing ? '' : undefined}\n className={[\n panelVariants(),\n // Subtle shadow — not a line — separating the panel from the\n // icon strip (the tab row). The panel casts a soft chrome\n // shadow toward the strip on its strip-facing edge. `z-[1]`\n // lifts it above the adjacent strip (the panel is already\n // `relative`) so the shadow paints at the seam instead of being\n // covered by the strip's opaque background. Only renders while\n // the panel is open (the aside is unmounted when collapsed), so\n // it never doubles as a rail↔main seam. In the accessible (AAA)\n // theme the soft shadow becomes a solid 1px border on that same\n // strip-facing edge.\n 'ds:z-[1]',\n side === 'end'\n ? 'ds:shadow-[var(--shadow-chrome-end)] ds:[.theme-accessible_&]:border-e ds:[.theme-accessible_&]:border-[var(--border)] ds:[.theme-accessible_&]:shadow-none'\n : 'ds:shadow-[var(--shadow-chrome-start)] ds:[.theme-accessible_&]:border-s ds:[.theme-accessible_&]:border-[var(--border)] ds:[.theme-accessible_&]:shadow-none',\n 'ds:focus-visible:outline-none',\n ].join(' ')}\n >\n {/* Optional host-owned, non-scrolling panel header (e.g. the\n Control Room's add / remove favourite control). Sits above the\n scroll region so it stays put as the panel body scrolls. */}\n {renderPanelHeader\n ? renderPanelHeader(activeTool, { isActive: true })\n : null}\n {/* Optional per-tool pinned header — the rail-level home for a\n sticky tool header (title / filters). Non-scrolling, above the\n body, so it stays visible for every tool. */}\n {activeTool.header ? (\n <div className=\"ds:shrink-0\">{activeTool.header}</div>\n ) : null}\n {/* Body: a self-scrolling `fill` tool owns its own scroll inside\n the height-bounded panel; otherwise the rail scrolls it. */}\n {activeTool.fill ? (\n // data-tool-fill: a self-scrolling panel owns its own native\n // scroll (it can't use the rail's ScrollArea), so a token rule\n // paints its scrollbar to match the 8px overlay. See tokens/index.css.\n <div data-tool-fill=\"\" className=\"ds:flex-1 ds:min-h-0\">\n {activeTool.panel}\n </div>\n ) : (\n <ScrollArea\n orientation=\"vertical\"\n className=\"ds:flex-1 ds:min-h-0\"\n >\n {activeTool.panel}\n </ScrollArea>\n )}\n {/* Inside the aside (so it belongs to the complementary\n landmark) and outside the toolbar strip — the strip's\n roving handlers never see its key events, and Escape\n still bubbles to the root to collapse, as everywhere\n else in the panel. No stopPropagation needed. */}\n {resizablePanel ? (\n <div\n {...separatorProps}\n aria-label={t('toolRail.resizePanel', {\n defaultValue: 'Resize tool panel',\n })}\n data-resizing={isResizing ? '' : undefined}\n className={panelResizeHandleVariants({ edge: panelEdge })}\n />\n ) : null}\n </aside>\n ) : null}\n\n {/* The 3rem icon strip — a vertical toolbar with roving tabindex.\n Self-contained TooltipProvider so consumers needn't mount one;\n Radix providers compose, so a host-level provider still works. */}\n <TooltipProvider>\n <div\n role=\"toolbar\"\n aria-orientation=\"vertical\"\n aria-label={\n railLabel ?? t('toolRail.railLabel', { defaultValue: 'Tools' })\n }\n className={stripVariants()}\n onKeyDown={handleStripKeyDown}\n >\n {tools.map((tool, index) => {\n const isActive = tool.id === activeId;\n const showBadge =\n typeof tool.badgeCount === 'number' && tool.badgeCount > 0;\n // Fold the count into the accessible name so SR users hear it\n // without relying on the decorative chip.\n const accessibleName = showBadge\n ? t('toolRail.toolWithCount', {\n tool: tool.label,\n count: tool.badgeCount,\n defaultValue: '{{tool}}, {{count}} updates',\n })\n : tool.label;\n const canReorderTool =\n reorderEnabled && !!tool.reorderable && !tool.disabled;\n const reorderDescId = canReorderTool\n ? `${reorderInstructionsId}-${tool.id}`\n : undefined;\n return (\n <Fragment key={tool.id}>\n {showScopeDivider && index === scopeDividerIndex ? (\n <div\n role=\"separator\"\n aria-orientation=\"horizontal\"\n className=\"ds:my-[var(--spacing-2xs)] ds:h-px ds:w-[var(--spacing-lg)] ds:shrink-0 ds:bg-[var(--border)] ds:forced-colors:bg-[CanvasText]\"\n />\n ) : null}\n <div\n className={toolSlotVariants({\n dragging: dragIndex === index,\n dropTarget: dropIndex === index && dragIndex !== index,\n reorderable: canReorderTool,\n })}\n data-dragging={dragIndex === index ? '' : undefined}\n // Drag the whole slot — the icon button is an unreliable\n // HTML5 drag source across browsers. The button keeps its\n // click + Alt+Arrow keyboard reorder; drag is pointer-only.\n draggable={canReorderTool || undefined}\n onDragStart={\n canReorderTool\n ? (event) => {\n setDragIndex(index);\n event.dataTransfer.effectAllowed = 'move';\n // Firefox needs data set to initiate a drag.\n event.dataTransfer.setData(\n 'text/plain',\n String(index),\n );\n }\n : undefined\n }\n onDragOver={\n canReorderTool\n ? (event) => {\n if (dragIndex === null) return;\n event.preventDefault();\n event.dataTransfer.dropEffect = 'move';\n if (dropIndex !== index) setDropIndex(index);\n }\n : undefined\n }\n onDragLeave={\n canReorderTool\n ? () =>\n setDropIndex((cur) => (cur === index ? null : cur))\n : undefined\n }\n onDrop={\n canReorderTool\n ? (event) => {\n event.preventDefault();\n const from = dragIndex;\n setDropIndex(null);\n setDragIndex(null);\n if (from === null || from === index) return;\n commitReorder(from, index);\n }\n : undefined\n }\n onDragEnd={\n canReorderTool\n ? () => {\n setDragIndex(null);\n setDropIndex(null);\n }\n : undefined\n }\n >\n <IconButton\n ref={(el) => {\n buttonsRef.current[index] = el;\n }}\n icon={tool.icon}\n tooltip={tool.label}\n aria-label={accessibleName}\n intent=\"ghost\"\n size=\"sm\"\n disabled={tool.disabled}\n aria-pressed={isActive}\n aria-expanded={isActive}\n data-active={isActive ? tool.id : undefined}\n data-tool-id={tool.id}\n aria-describedby={reorderDescId}\n aria-keyshortcuts={\n canReorderTool ? 'Alt+ArrowUp Alt+ArrowDown' : undefined\n }\n tabIndex={index === rovingIndex ? 0 : -1}\n onFocus={() => {\n if (!tool.disabled) setFocusIndex(index);\n }}\n onClick={() =>\n selectTool(tool.id, keyboardActivateRef.current)\n }\n onKeyDown={(event) => {\n // Enter/Space activate via the native button's click,\n // which fires AFTER this keydown. Flag the upcoming\n // click as keyboard-driven so focus moves into the\n // panel; reset on keyup so a later mouse click doesn't\n // inherit the flag.\n if (event.key === 'Enter' || event.key === ' ') {\n keyboardActivateRef.current = true;\n return;\n }\n // Alt+Arrow / Alt+Home / Alt+End reorder within the run.\n // Focus follows the moved icon to its new index; roving\n // ignores Alt (see handleStripKeyDown).\n if (canReorderTool && event.altKey && reorderRun) {\n let target: number | null = null;\n if (event.key === 'ArrowUp') target = index - 1;\n else if (event.key === 'ArrowDown')\n target = index + 1;\n else if (event.key === 'Home')\n target = reorderRun.first;\n else if (event.key === 'End')\n target = reorderRun.last;\n if (target !== null) {\n event.preventDefault();\n const dest = Math.max(\n reorderRun.first,\n Math.min(reorderRun.last, target),\n );\n // Focus follows the moved TOOL (by id), not the\n // slot index — so it lands correctly however the\n // host reinterprets the move (reorder, cross-group\n // pin / unpin, or a no-op that leaves it in place).\n const movedId = tool.id;\n commitReorder(index, dest);\n requestAnimationFrame(() => {\n rootRef.current\n ?.querySelector<HTMLButtonElement>(\n `[data-tool-id=\"${CSS.escape(movedId)}\"]`,\n )\n ?.focus();\n });\n }\n }\n }}\n onKeyUp={(event) => {\n if (event.key === 'Enter' || event.key === ' ') {\n keyboardActivateRef.current = false;\n }\n }}\n />\n {showBadge ? (\n <span aria-hidden=\"true\" className={badgeVariants()}>\n {formatBadgeCount(\n tool.badgeCount as number,\n overflowLabel,\n )}\n </span>\n ) : null}\n {renderToolAffordance?.(tool, {\n index,\n isActive,\n isRovingTarget: index === rovingIndex,\n })}\n {reorderDescId ? (\n <span id={reorderDescId} className=\"ds:sr-only\">\n {t('toolRail.reorderInstructions', {\n tool: tool.label,\n defaultValue:\n 'Reorder {{tool}}. Press Alt + Up or Alt + Down to move.',\n })}\n </span>\n ) : null}\n </div>\n </Fragment>\n );\n })}\n </div>\n </TooltipProvider>\n </div>\n );\n },\n);\n\nToolRail.displayName = 'ToolRail';\n"],"names":["toolRailAgent","handle","args","RAIL_WIDTH","PANEL_WIDTH","PANEL_SIZE_PROPERTY","PANEL_MIN_SIZE","PANEL_MAX_SIZE","PANEL_DEFAULT_SIZE","railVariants","cva","stripVariants","panelVariants","panelResizeHandleVariants","badgeVariants","toolSlotVariants","formatBadgeCount","count","overflowLabel","ToolRail","forwardRef","id","tools","activeToolId","defaultActiveToolId","onActiveToolChange","side","railLabel","resizablePanel","onReorder","formatReorderAnnouncement","renderToolAffordance","renderPanelHeader","dataComponent","agentAdapter","className","ref","t","useTranslation","activeRaw","setActiveRaw","useControllableState","activeId","buttonsRef","useRef","panelRef","focusIndex","setFocusIndex","useState","activatedByKeyboardRef","keyboardActivateRef","activeIndex","useMemo","tl","activeTool","firstEnabledIndex","scopeDividerIndex","lead","_a","showScopeDivider","reorderRun","first","last","reorderEnabled","reorderInstructionsId","useId","dragIndex","setDragIndex","dropIndex","setDropIndex","reorderMessage","setReorderMessage","announceToggle","commitReorder","useCallback","from","to","clamped","moved","text","rovingIndex","selectTool","toolId","fromKeyboard","tool","handleStripKeyDown","event","next","goingBack","candidate","attempts","_b","_c","rootRef","useEffect","node","onKeyDown","returnIndex","useIsomorphicLayoutEffect","panelEdge","separatorProps","isResizing","isResized","useEdgeResize","committedPanelWidth","activeIdRef","returnId","useImperativeHandle","useAgentRegistration","jsxs","jsx","ScrollArea","TooltipProvider","index","isActive","showBadge","accessibleName","canReorderTool","reorderDescId","Fragment","cur","IconButton","el","target","dest","movedId"],"mappings":"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"}
|