@braedonsaunders/appkit-forms 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +74 -0
  3. package/canvas-editor.d.ts +24 -0
  4. package/canvas-editor.d.ts.map +1 -0
  5. package/canvas-editor.js +115 -0
  6. package/canvas-editor.js.map +1 -0
  7. package/canvas-runtime.d.ts +12 -0
  8. package/canvas-runtime.d.ts.map +1 -0
  9. package/canvas-runtime.js +54 -0
  10. package/canvas-runtime.js.map +1 -0
  11. package/element-preview.d.ts +9 -0
  12. package/element-preview.d.ts.map +1 -0
  13. package/element-preview.js +167 -0
  14. package/element-preview.js.map +1 -0
  15. package/form-designer.d.ts +29 -0
  16. package/form-designer.d.ts.map +1 -0
  17. package/form-designer.js +214 -0
  18. package/form-designer.js.map +1 -0
  19. package/form-flow-validation.d.ts +5 -0
  20. package/form-flow-validation.d.ts.map +1 -0
  21. package/form-flow-validation.js +154 -0
  22. package/form-flow-validation.js.map +1 -0
  23. package/form-renderer.d.ts +37 -0
  24. package/form-renderer.d.ts.map +1 -0
  25. package/form-renderer.js +282 -0
  26. package/form-renderer.js.map +1 -0
  27. package/formula-builder.d.ts +25 -0
  28. package/formula-builder.d.ts.map +1 -0
  29. package/formula-builder.js +310 -0
  30. package/formula-builder.js.map +1 -0
  31. package/generated-copy.d.ts +17 -0
  32. package/generated-copy.d.ts.map +1 -0
  33. package/generated-copy.js +396 -0
  34. package/generated-copy.js.map +1 -0
  35. package/index.d.ts +27 -0
  36. package/index.d.ts.map +1 -0
  37. package/index.js +25 -0
  38. package/index.js.map +1 -0
  39. package/logic-builder.d.ts +27 -0
  40. package/logic-builder.d.ts.map +1 -0
  41. package/logic-builder.js +106 -0
  42. package/logic-builder.js.map +1 -0
  43. package/package.json +54 -0
  44. package/photo-field-state.d.ts +9 -0
  45. package/photo-field-state.d.ts.map +1 -0
  46. package/photo-field-state.js +11 -0
  47. package/photo-field-state.js.map +1 -0
  48. package/production-designer-adapter.d.ts +100 -0
  49. package/production-designer-adapter.d.ts.map +1 -0
  50. package/production-designer-adapter.js +2 -0
  51. package/production-designer-adapter.js.map +1 -0
  52. package/production-file-upload.d.ts +27 -0
  53. package/production-file-upload.d.ts.map +1 -0
  54. package/production-file-upload.js +105 -0
  55. package/production-file-upload.js.map +1 -0
  56. package/production-form-designer.d.ts +3 -0
  57. package/production-form-designer.d.ts.map +1 -0
  58. package/production-form-designer.js +1124 -0
  59. package/production-form-designer.js.map +1 -0
  60. package/production-form-renderer.d.ts +16 -0
  61. package/production-form-renderer.d.ts.map +1 -0
  62. package/production-form-renderer.js +2134 -0
  63. package/production-form-renderer.js.map +1 -0
  64. package/production-runtime-adapter.d.ts +187 -0
  65. package/production-runtime-adapter.d.ts.map +1 -0
  66. package/production-runtime-adapter.js +2 -0
  67. package/production-runtime-adapter.js.map +1 -0
  68. package/production-runtime-context.d.ts +29 -0
  69. package/production-runtime-context.d.ts.map +1 -0
  70. package/production-runtime-context.js +35 -0
  71. package/production-runtime-context.js.map +1 -0
  72. package/production-runtime-layout.d.ts +21 -0
  73. package/production-runtime-layout.d.ts.map +1 -0
  74. package/production-runtime-layout.js +33 -0
  75. package/production-runtime-layout.js.map +1 -0
  76. package/properties.d.ts +36 -0
  77. package/properties.d.ts.map +1 -0
  78. package/properties.js +133 -0
  79. package/properties.js.map +1 -0
  80. package/record-actions-panel.d.ts +28 -0
  81. package/record-actions-panel.d.ts.map +1 -0
  82. package/record-actions-panel.js +136 -0
  83. package/record-actions-panel.js.map +1 -0
  84. package/record-behavior-panel.d.ts +15 -0
  85. package/record-behavior-panel.d.ts.map +1 -0
  86. package/record-behavior-panel.js +59 -0
  87. package/record-behavior-panel.js.map +1 -0
  88. package/record-config.d.ts +44 -0
  89. package/record-config.d.ts.map +1 -0
  90. package/record-config.js +2 -0
  91. package/record-config.js.map +1 -0
  92. package/record-list-panel.d.ts +16 -0
  93. package/record-list-panel.d.ts.map +1 -0
  94. package/record-list-panel.js +86 -0
  95. package/record-list-panel.js.map +1 -0
  96. package/risk-matrix.d.ts +93 -0
  97. package/risk-matrix.d.ts.map +1 -0
  98. package/risk-matrix.js +239 -0
  99. package/risk-matrix.js.map +1 -0
  100. package/runtime-controls.d.ts +19 -0
  101. package/runtime-controls.d.ts.map +1 -0
  102. package/runtime-controls.js +61 -0
  103. package/runtime-controls.js.map +1 -0
  104. package/sketch-pad-client.d.ts +4 -0
  105. package/sketch-pad-client.d.ts.map +1 -0
  106. package/sketch-pad-client.js +54 -0
  107. package/sketch-pad-client.js.map +1 -0
  108. package/sketch-pad.d.ts +13 -0
  109. package/sketch-pad.d.ts.map +1 -0
  110. package/sketch-pad.js +17 -0
  111. package/sketch-pad.js.map +1 -0
  112. package/styles.css +7 -0
  113. package/text.d.ts +5 -0
  114. package/text.d.ts.map +1 -0
  115. package/text.js +10 -0
  116. package/text.js.map +1 -0
  117. package/workflow-properties.d.ts +16 -0
  118. package/workflow-properties.d.ts.map +1 -0
  119. package/workflow-properties.js +31 -0
  120. package/workflow-properties.js.map +1 -0
@@ -0,0 +1,16 @@
1
+ import * as React from 'react';
2
+ import type { ListColumnConfig, ListConfig } from './record-config.js';
3
+ export declare const DEFAULT_RECORD_LIST_COLUMNS: ListColumnConfig[];
4
+ export type RecordListPanelProps = {
5
+ value?: ListConfig;
6
+ fields: {
7
+ id: string;
8
+ label: string;
9
+ }[];
10
+ onChange: (config: ListConfig) => void;
11
+ onSave?: (config: ListConfig) => void | Promise<void>;
12
+ readOnly?: boolean;
13
+ className?: string;
14
+ };
15
+ export declare function RecordListPanel({ value, fields, onChange, onSave, readOnly, className }: RecordListPanelProps): React.JSX.Element;
16
+ //# sourceMappingURL=record-list-panel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"record-list-panel.d.ts","sourceRoot":"","sources":["../src/record-list-panel.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAG9B,OAAO,KAAK,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAanE,eAAO,MAAM,2BAA2B,EAAE,gBAAgB,EAA4D,CAAA;AAsBtH,MAAM,MAAM,oBAAoB,GAAG;IACjC,KAAK,CAAC,EAAE,UAAU,CAAA;IAClB,MAAM,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;IACvC,QAAQ,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,IAAI,CAAA;IACtC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACrD,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,CAAA;AAED,wBAAgB,eAAe,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAgB,EAAE,SAAS,EAAE,EAAE,oBAAoB,qBAwHrH"}
@@ -0,0 +1,86 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import * as React from 'react';
4
+ import { ArrowDown, ArrowUp, Check, Plus, X } from 'lucide-react';
5
+ import { Button, Input, Label, Select, cn } from '@braedonsaunders/appkit-ui';
6
+ const BUILTIN_LABEL = {
7
+ id: 'ID',
8
+ subject: 'Subject',
9
+ site: 'Site',
10
+ status: 'Status',
11
+ created_at: 'Started',
12
+ submitted_at: 'Submitted',
13
+ submittedBy: 'By',
14
+ pdf: 'PDF',
15
+ };
16
+ const BUILTIN_KEYS = Object.keys(BUILTIN_LABEL);
17
+ export const DEFAULT_RECORD_LIST_COLUMNS = BUILTIN_KEYS.map((key) => ({ key, source: 'builtin' }));
18
+ const SORT_OPTIONS = [
19
+ { key: 'submitted_at', label: 'Submitted' },
20
+ { key: 'created_at', label: 'Started' },
21
+ { key: 'status', label: 'Status' },
22
+ ];
23
+ const STATUS_OPTIONS = [
24
+ { value: '', label: 'None' },
25
+ { value: 'draft', label: 'Draft' },
26
+ { value: 'in_progress', label: 'In progress' },
27
+ { value: 'submitted', label: 'Submitted' },
28
+ { value: 'in_review', label: 'In review' },
29
+ { value: 'closed', label: 'Closed' },
30
+ { value: 'rejected', label: 'Rejected' },
31
+ ];
32
+ function columnId(column) {
33
+ return `${column.source}:${column.key}`;
34
+ }
35
+ export function RecordListPanel({ value, fields, onChange, onSave, readOnly = false, className }) {
36
+ const configured = Boolean(value?.columns?.length);
37
+ const columns = value?.columns?.length ? value.columns : DEFAULT_RECORD_LIST_COLUMNS;
38
+ const [addKind, setAddKind] = React.useState('');
39
+ const [pending, startTransition] = React.useTransition();
40
+ const present = React.useMemo(() => new Set(columns.map(columnId)), [columns]);
41
+ const fieldLabels = React.useMemo(() => new Map(fields.map((field) => [field.id, field.label])), [fields]);
42
+ const addableBuiltins = BUILTIN_KEYS.filter((key) => !present.has(`builtin:${key}`));
43
+ const addableFields = fields.filter((field) => !present.has(`field:${field.id}`));
44
+ const canAdd = addableBuiltins.length + addableFields.length > 0;
45
+ const commit = React.useCallback((patch) => {
46
+ onChange({ ...value, ...patch });
47
+ }, [onChange, value]);
48
+ const setColumns = (next) => commit({ columns: next });
49
+ function move(index, delta) {
50
+ const target = index + delta;
51
+ if (target < 0 || target >= columns.length)
52
+ return;
53
+ const next = columns.map((column) => ({ ...column }));
54
+ const current = next[index];
55
+ const replacement = next[target];
56
+ if (!current || !replacement)
57
+ return;
58
+ next[index] = replacement;
59
+ next[target] = current;
60
+ setColumns(next);
61
+ }
62
+ function add() {
63
+ if (!addKind)
64
+ return;
65
+ const separator = addKind.indexOf(':');
66
+ const source = addKind.slice(0, separator);
67
+ const key = addKind.slice(separator + 1);
68
+ if (source === 'builtin' && BUILTIN_KEYS.includes(key))
69
+ setColumns([...columns, { source, key }]);
70
+ if (source === 'field' && fieldLabels.has(key))
71
+ setColumns([...columns, { source, key }]);
72
+ setAddKind('');
73
+ }
74
+ const displayLabel = (column) => column.source === 'builtin'
75
+ ? BUILTIN_LABEL[column.key] ?? column.key
76
+ : fieldLabels.get(column.key) ?? column.key;
77
+ return (_jsxs("div", { className: cn('space-y-4', className), children: [_jsx("p", { className: "text-xs text-fg-muted", children: "Choose the columns, default sort, and initial status filter for the record list." }), _jsxs("section", { className: "space-y-2", children: [_jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsx(Label, { className: "text-xs", children: "Columns" }), _jsx(Button, { type: "button", variant: "ghost", size: "sm", disabled: readOnly || !configured, onClick: () => setColumns(undefined), children: "Reset defaults" })] }), _jsx("ul", { className: "space-y-2", children: columns.map((column, index) => (_jsxs("li", { className: "rounded-md border border-border p-2", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "min-w-0 flex-1 truncate text-sm font-medium text-fg", children: displayLabel(column) }), _jsx("span", { className: "rounded bg-bg-subtle px-1.5 py-0.5 text-[10px] text-fg-muted", children: column.source }), _jsxs("div", { className: "flex items-center gap-0.5", children: [_jsx(IconButton, { label: "Move column up", disabled: readOnly || index === 0, onClick: () => move(index, -1), children: _jsx(ArrowUp, { size: 14 }) }), _jsx(IconButton, { label: "Move column down", disabled: readOnly || index === columns.length - 1, onClick: () => move(index, 1), children: _jsx(ArrowDown, { size: 14 }) }), _jsx(IconButton, { label: "Remove column", danger: true, disabled: readOnly, onClick: () => setColumns(columns.filter((_, candidate) => candidate !== index)), children: _jsx(X, { size: 14 }) })] })] }), _jsx(Input, { value: column.label ?? '', disabled: readOnly, onChange: (event) => {
78
+ const next = columns.map((candidate) => ({ ...candidate }));
79
+ next[index] = { ...column, label: event.target.value.trim() || undefined };
80
+ setColumns(next);
81
+ }, placeholder: `Custom label (default: ${displayLabel(column)})`, className: "mt-1.5 h-7 text-xs" })] }, columnId(column)))) }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsxs(Select, { value: addKind, onChange: (event) => setAddKind(event.target.value), disabled: readOnly || !canAdd, className: "flex-1", children: [_jsx("option", { value: "", children: canAdd ? 'Add a column…' : 'All columns added' }), addableBuiltins.length ? _jsx("optgroup", { label: "Record columns", children: addableBuiltins.map((key) => _jsx("option", { value: `builtin:${key}`, children: BUILTIN_LABEL[key] }, key)) }) : null, addableFields.length ? _jsx("optgroup", { label: "Form fields", children: addableFields.map((field) => _jsx("option", { value: `field:${field.id}`, children: field.label }, field.id)) }) : null] }), _jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: add, disabled: readOnly || !addKind, "aria-label": "Add column", children: _jsx(Plus, { size: 14 }) })] }), _jsx("p", { className: "text-[11px] text-fg-subtle", children: "Drag-free ordering keeps the list editor keyboard accessible; use the arrow controls to set exact order." })] }), _jsxs("section", { className: "space-y-2", children: [_jsx(Label, { className: "text-xs", children: "Default sort" }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Select, { value: value?.defaultSort?.key ?? 'submitted_at', onChange: (event) => commit({ defaultSort: { key: event.target.value, dir: value?.defaultSort?.dir ?? 'desc' } }), disabled: readOnly, className: "flex-1", children: SORT_OPTIONS.map((option) => _jsx("option", { value: option.key, children: option.label }, option.key)) }), _jsxs(Button, { type: "button", variant: "outline", size: "sm", disabled: readOnly, onClick: () => commit({ defaultSort: { key: value?.defaultSort?.key ?? 'submitted_at', dir: value?.defaultSort?.dir === 'asc' ? 'desc' : 'asc' } }), children: [value?.defaultSort?.dir === 'asc' ? _jsx(ArrowUp, { size: 13 }) : _jsx(ArrowDown, { size: 13 }), value?.defaultSort?.dir === 'asc' ? 'Ascending' : 'Descending'] })] })] }), _jsxs("section", { className: "space-y-2", children: [_jsx(Label, { className: "text-xs", children: "Default status" }), _jsx(Select, { value: value?.defaultStatus ?? '', disabled: readOnly, onChange: (event) => commit({ defaultStatus: event.target.value || undefined }), children: STATUS_OPTIONS.map((option) => _jsx("option", { value: option.value, children: option.label }, option.value || 'none')) }), _jsx("p", { className: "text-[11px] text-fg-subtle", children: "People can still change the filter after the list opens." })] }), onSave ? _jsxs(Button, { type: "button", disabled: pending || readOnly, className: "w-full", onClick: () => startTransition(async () => onSave(value ?? {})), children: [_jsx(Check, { size: 14 }), pending ? 'Saving…' : 'Save list settings'] }) : null] }));
82
+ }
83
+ function IconButton({ label, disabled, danger = false, onClick, children }) {
84
+ return _jsx("button", { type: "button", "aria-label": label, title: label, disabled: disabled, onClick: onClick, className: cn('rounded p-1 text-fg-subtle hover:bg-surface-hover hover:text-fg disabled:opacity-30', danger && 'hover:bg-danger-subtle hover:text-danger'), children: children });
85
+ }
86
+ //# sourceMappingURL=record-list-panel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"record-list-panel.js","sourceRoot":"","sources":["../src/record-list-panel.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client'\n\nimport * as React from 'react'\nimport { ArrowDown, ArrowUp, Check, Plus, X } from 'lucide-react'\nimport { Button, Input, Label, Select, cn } from '@braedonsaunders/appkit-ui'\nimport type { ListColumnConfig, ListConfig } from './record-config'\n\nconst BUILTIN_LABEL: Record<string, string> = {\n id: 'ID',\n subject: 'Subject',\n site: 'Site',\n status: 'Status',\n created_at: 'Started',\n submitted_at: 'Submitted',\n submittedBy: 'By',\n pdf: 'PDF',\n}\nconst BUILTIN_KEYS = Object.keys(BUILTIN_LABEL)\nexport const DEFAULT_RECORD_LIST_COLUMNS: ListColumnConfig[] = BUILTIN_KEYS.map((key) => ({ key, source: 'builtin' }))\n\nconst SORT_OPTIONS: { key: NonNullable<ListConfig['defaultSort']>['key']; label: string }[] = [\n { key: 'submitted_at', label: 'Submitted' },\n { key: 'created_at', label: 'Started' },\n { key: 'status', label: 'Status' },\n]\n\nconst STATUS_OPTIONS = [\n { value: '', label: 'None' },\n { value: 'draft', label: 'Draft' },\n { value: 'in_progress', label: 'In progress' },\n { value: 'submitted', label: 'Submitted' },\n { value: 'in_review', label: 'In review' },\n { value: 'closed', label: 'Closed' },\n { value: 'rejected', label: 'Rejected' },\n]\n\nfunction columnId(column: ListColumnConfig) {\n return `${column.source}:${column.key}`\n}\n\nexport type RecordListPanelProps = {\n value?: ListConfig\n fields: { id: string; label: string }[]\n onChange: (config: ListConfig) => void\n onSave?: (config: ListConfig) => void | Promise<void>\n readOnly?: boolean\n className?: string\n}\n\nexport function RecordListPanel({ value, fields, onChange, onSave, readOnly = false, className }: RecordListPanelProps) {\n const configured = Boolean(value?.columns?.length)\n const columns = value?.columns?.length ? value.columns : DEFAULT_RECORD_LIST_COLUMNS\n const [addKind, setAddKind] = React.useState('')\n const [pending, startTransition] = React.useTransition()\n const present = React.useMemo(() => new Set(columns.map(columnId)), [columns])\n const fieldLabels = React.useMemo(() => new Map(fields.map((field) => [field.id, field.label])), [fields])\n const addableBuiltins = BUILTIN_KEYS.filter((key) => !present.has(`builtin:${key}`))\n const addableFields = fields.filter((field) => !present.has(`field:${field.id}`))\n const canAdd = addableBuiltins.length + addableFields.length > 0\n\n const commit = React.useCallback((patch: Partial<ListConfig>) => {\n onChange({ ...value, ...patch })\n }, [onChange, value])\n\n const setColumns = (next: ListColumnConfig[] | undefined) => commit({ columns: next })\n\n function move(index: number, delta: -1 | 1) {\n const target = index + delta\n if (target < 0 || target >= columns.length) return\n const next = columns.map((column) => ({ ...column }))\n const current = next[index]\n const replacement = next[target]\n if (!current || !replacement) return\n next[index] = replacement\n next[target] = current\n setColumns(next)\n }\n\n function add() {\n if (!addKind) return\n const separator = addKind.indexOf(':')\n const source = addKind.slice(0, separator)\n const key = addKind.slice(separator + 1)\n if (source === 'builtin' && BUILTIN_KEYS.includes(key)) setColumns([...columns, { source, key }])\n if (source === 'field' && fieldLabels.has(key)) setColumns([...columns, { source, key }])\n setAddKind('')\n }\n\n const displayLabel = (column: ListColumnConfig) => column.source === 'builtin'\n ? BUILTIN_LABEL[column.key] ?? column.key\n : fieldLabels.get(column.key) ?? column.key\n\n return (\n <div className={cn('space-y-4', className)}>\n <p className=\"text-xs text-fg-muted\">Choose the columns, default sort, and initial status filter for the record list.</p>\n\n <section className=\"space-y-2\">\n <div className=\"flex items-center justify-between gap-2\">\n <Label className=\"text-xs\">Columns</Label>\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" disabled={readOnly || !configured} onClick={() => setColumns(undefined)}>Reset defaults</Button>\n </div>\n <ul className=\"space-y-2\">\n {columns.map((column, index) => (\n <li key={columnId(column)} className=\"rounded-md border border-border p-2\">\n <div className=\"flex items-center gap-2\">\n <span className=\"min-w-0 flex-1 truncate text-sm font-medium text-fg\">{displayLabel(column)}</span>\n <span className=\"rounded bg-bg-subtle px-1.5 py-0.5 text-[10px] text-fg-muted\">{column.source}</span>\n <div className=\"flex items-center gap-0.5\">\n <IconButton label=\"Move column up\" disabled={readOnly || index === 0} onClick={() => move(index, -1)}><ArrowUp size={14} /></IconButton>\n <IconButton label=\"Move column down\" disabled={readOnly || index === columns.length - 1} onClick={() => move(index, 1)}><ArrowDown size={14} /></IconButton>\n <IconButton label=\"Remove column\" danger disabled={readOnly} onClick={() => setColumns(columns.filter((_, candidate) => candidate !== index))}><X size={14} /></IconButton>\n </div>\n </div>\n <Input\n value={column.label ?? ''}\n disabled={readOnly}\n onChange={(event) => {\n const next = columns.map((candidate) => ({ ...candidate }))\n next[index] = { ...column, label: event.target.value.trim() || undefined }\n setColumns(next)\n }}\n placeholder={`Custom label (default: ${displayLabel(column)})`}\n className=\"mt-1.5 h-7 text-xs\"\n />\n </li>\n ))}\n </ul>\n\n <div className=\"flex items-center gap-2\">\n <Select value={addKind} onChange={(event) => setAddKind(event.target.value)} disabled={readOnly || !canAdd} className=\"flex-1\">\n <option value=\"\">{canAdd ? 'Add a column…' : 'All columns added'}</option>\n {addableBuiltins.length ? <optgroup label=\"Record columns\">{addableBuiltins.map((key) => <option key={key} value={`builtin:${key}`}>{BUILTIN_LABEL[key]}</option>)}</optgroup> : null}\n {addableFields.length ? <optgroup label=\"Form fields\">{addableFields.map((field) => <option key={field.id} value={`field:${field.id}`}>{field.label}</option>)}</optgroup> : null}\n </Select>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={add} disabled={readOnly || !addKind} aria-label=\"Add column\"><Plus size={14} /></Button>\n </div>\n <p className=\"text-[11px] text-fg-subtle\">Drag-free ordering keeps the list editor keyboard accessible; use the arrow controls to set exact order.</p>\n </section>\n\n <section className=\"space-y-2\">\n <Label className=\"text-xs\">Default sort</Label>\n <div className=\"flex items-center gap-2\">\n <Select value={value?.defaultSort?.key ?? 'submitted_at'} onChange={(event) => commit({ defaultSort: { key: event.target.value as NonNullable<ListConfig['defaultSort']>['key'], dir: value?.defaultSort?.dir ?? 'desc' } })} disabled={readOnly} className=\"flex-1\">\n {SORT_OPTIONS.map((option) => <option key={option.key} value={option.key}>{option.label}</option>)}\n </Select>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n disabled={readOnly}\n onClick={() => commit({ defaultSort: { key: value?.defaultSort?.key ?? 'submitted_at', dir: value?.defaultSort?.dir === 'asc' ? 'desc' : 'asc' } })}\n >\n {value?.defaultSort?.dir === 'asc' ? <ArrowUp size={13} /> : <ArrowDown size={13} />}\n {value?.defaultSort?.dir === 'asc' ? 'Ascending' : 'Descending'}\n </Button>\n </div>\n </section>\n\n <section className=\"space-y-2\">\n <Label className=\"text-xs\">Default status</Label>\n <Select value={value?.defaultStatus ?? ''} disabled={readOnly} onChange={(event) => commit({ defaultStatus: event.target.value || undefined })}>\n {STATUS_OPTIONS.map((option) => <option key={option.value || 'none'} value={option.value}>{option.label}</option>)}\n </Select>\n <p className=\"text-[11px] text-fg-subtle\">People can still change the filter after the list opens.</p>\n </section>\n\n {onSave ? <Button type=\"button\" disabled={pending || readOnly} className=\"w-full\" onClick={() => startTransition(async () => onSave(value ?? {}))}><Check size={14} />{pending ? 'Saving…' : 'Save list settings'}</Button> : null}\n </div>\n )\n}\n\nfunction IconButton({ label, disabled, danger = false, onClick, children }: { label: string; disabled: boolean; danger?: boolean; onClick: () => void; children: React.ReactNode }) {\n return <button type=\"button\" aria-label={label} title={label} disabled={disabled} onClick={onClick} className={cn('rounded p-1 text-fg-subtle hover:bg-surface-hover hover:text-fg disabled:opacity-30', danger && 'hover:bg-danger-subtle hover:text-danger')}>{children}</button>\n}\n"]}
@@ -0,0 +1,93 @@
1
+ export type RiskMatrixConfig = {
2
+ axes: {
3
+ severity: {
4
+ values: string[];
5
+ };
6
+ likelihood: {
7
+ values: string[];
8
+ };
9
+ };
10
+ cells: Record<string, {
11
+ score: number;
12
+ label: string;
13
+ color: string;
14
+ }>;
15
+ };
16
+ type RiskMatrixSelection = {
17
+ likelihood: number | null;
18
+ severity: number | null;
19
+ score: number | null;
20
+ label: string | null;
21
+ };
22
+ /** A risk band: every cell scoring ≥ `minScore` (and below the next band) wears
23
+ * this label + colour. */
24
+ export type RiskBandDef = {
25
+ label: string;
26
+ color: string;
27
+ minScore: number;
28
+ };
29
+ export declare const MIN_AXIS = 3;
30
+ export declare const MAX_AXIS = 6;
31
+ /** Build a full per-cell matrix from axis labels + bands (score = sev × lik). */
32
+ export declare function buildMatrix(severityLabels: string[], likelihoodLabels: string[], bands: RiskBandDef[]): RiskMatrixConfig;
33
+ /** Recover the editable band list from a stored matrix — one band per distinct
34
+ * (label, colour), keyed to the lowest score that wears it. Round-trips any
35
+ * band-by-score matrix, including legacy seeds. */
36
+ export declare function bandsFromMatrix(matrix: RiskMatrixConfig): RiskBandDef[];
37
+ export declare const DEFAULT_RISK_MATRIX: RiskMatrixConfig;
38
+ export declare function RiskMatrixProvider({ matrix, children, }: {
39
+ matrix: RiskMatrixConfig | null | undefined;
40
+ children: React.ReactNode;
41
+ }): import("react").JSX.Element;
42
+ /** Chip for a precomputed 1-based score (e.g. an aggregated "worst risk"). */
43
+ export declare function RiskScoreChip({ score, prefix }: {
44
+ score: number | null;
45
+ prefix?: string;
46
+ }): import("react").JSX.Element;
47
+ /** Chip showing the score + band for a likelihood × severity pair. */
48
+ export declare function RiskScoreBadge({ likelihood, severity, prefix, }: {
49
+ likelihood?: number | null;
50
+ severity?: number | null;
51
+ prefix?: string;
52
+ }): import("react").JSX.Element;
53
+ /** Pre-control → post-control residual indicator: two score chips + an arrow. */
54
+ export declare function RiskDelta({ preLikelihood, preSeverity, postLikelihood, postSeverity, }: {
55
+ preLikelihood?: number | null;
56
+ preSeverity?: number | null;
57
+ postLikelihood?: number | null;
58
+ postSeverity?: number | null;
59
+ }): import("react").JSX.Element;
60
+ /**
61
+ * The colour-banded matrix grid: severity → across the top (S1…Sn), likelihood ↑
62
+ * down the left (Ln at top, L1 at bottom). Shared by the interactive picker
63
+ * (`RiskMatrixField`) and the editor preview so they can never drift apart.
64
+ */
65
+ export declare function RiskMatrixGrid({ matrix, selected, onSelect, disabled, className, }: {
66
+ matrix: RiskMatrixConfig;
67
+ /** 1-based current selection, when interactive. */
68
+ selected?: {
69
+ likelihood: number | null;
70
+ severity: number | null;
71
+ };
72
+ /** Omit for a read-only preview. */
73
+ onSelect?: (likelihood: number, severity: number) => void;
74
+ disabled?: boolean;
75
+ className?: string;
76
+ }): import("react").JSX.Element;
77
+ /**
78
+ * Interactive risk-matrix picker driven by the tenant's matrix. Exposes the
79
+ * chosen 1-based likelihood/severity pair through hidden inputs so it drops into
80
+ * a plain <form action={…}>.
81
+ */
82
+ export declare function RiskMatrixField({ label, likelihoodName, severityName, defaultLikelihood, defaultSeverity, disabled, onChange, }: {
83
+ label: string;
84
+ likelihoodName: string;
85
+ severityName: string;
86
+ defaultLikelihood?: number | null;
87
+ defaultSeverity?: number | null;
88
+ disabled?: boolean;
89
+ /** Lifts the chosen pair for drawer bodies that build FormData manually. */
90
+ onChange?: (next: RiskMatrixSelection) => void;
91
+ }): import("react").JSX.Element;
92
+ export {};
93
+ //# sourceMappingURL=risk-matrix.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"risk-matrix.d.ts","sourceRoot":"","sources":["../src/risk-matrix.tsx"],"names":[],"mappings":"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"}
package/risk-matrix.js ADDED
@@ -0,0 +1,239 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { GeneratedText, GeneratedValue, useGeneratedValueTranslations } from './generated-copy.js';
4
+ // Tenant-configurable risk model, shared across the Hazard Assessments module
5
+ // (and anywhere else that scores risk). The single source of truth is the
6
+ // tenant's saved `RiskMatrixConfig` — axis labels, per-cell score/label/colour —
7
+ // edited at /hazard-assessments/risk-matrix and injected here through
8
+ // <RiskMatrixProvider> at the app shell. Every component below reads the active
9
+ // matrix from context, so renaming an axis or recolouring a band in the editor
10
+ // flows straight through to the live picker, the score chips and the list.
11
+ //
12
+ // Authoring model: a cell's score is severity × likelihood (1-based), and a cell
13
+ // inherits the label + colour of the highest risk BAND whose threshold its score
14
+ // meets. That keeps the editor ergonomic (tune a handful of bands, not N×M
15
+ // cells) while the stored shape stays a flat per-cell map the renderers can read
16
+ // without recomputing anything.
17
+ import { Fragment, createContext, useContext, useMemo, useState } from 'react';
18
+ import { cn } from '@braedonsaunders/appkit-ui';
19
+ import { hexColor } from '@braedonsaunders/appkit-tokens';
20
+ // Axis sizes the editor allows. The stored 1-based ratings are plain integers,
21
+ // so this only bounds the UI; resizing a populated matrix degrades gracefully
22
+ // (out-of-range historical ratings simply stop matching a cell).
23
+ export const MIN_AXIS = 3;
24
+ export const MAX_AXIS = 6;
25
+ // --- Default matrix --------------------------------------------------------
26
+ // Reproduces the platform's long-standing 5×5 AS/NZS-style matrix. Used whenever
27
+ // a tenant has no saved matrix (or a malformed one), so unconfigured tenants see
28
+ // exactly what they saw before this became editable.
29
+ const SEVERITY_LABELS = ['Negligible', 'Minor', 'Moderate', 'Major', 'Severe'];
30
+ const LIKELIHOOD_LABELS = ['Rare', 'Unlikely', 'Possible', 'Likely', 'Almost certain'];
31
+ const DEFAULT_BANDS = [
32
+ { label: 'Low', color: hexColor('success'), minScore: 1 },
33
+ { label: 'Moderate', color: hexColor('warning'), minScore: 5 },
34
+ { label: 'High', color: hexColor('primary'), minScore: 10 },
35
+ { label: 'Critical', color: hexColor('danger'), minScore: 16 },
36
+ ];
37
+ /** The risk band a score falls into (highest band whose threshold it meets;
38
+ * the lowest band catches anything beneath the first threshold). */
39
+ function resolveBand(bands, score) {
40
+ const sorted = [...bands].sort((a, b) => a.minScore - b.minScore);
41
+ let chosen = sorted[0] ?? { label: '', color: hexColor('fg-subtle'), minScore: 1 };
42
+ for (const b of sorted)
43
+ if (score >= b.minScore)
44
+ chosen = b;
45
+ return chosen;
46
+ }
47
+ /** Build a full per-cell matrix from axis labels + bands (score = sev × lik). */
48
+ export function buildMatrix(severityLabels, likelihoodLabels, bands) {
49
+ const severity = severityLabels.map((s, i) => s.trim() || `S${i + 1}`);
50
+ const likelihood = likelihoodLabels.map((s, i) => s.trim() || `L${i + 1}`);
51
+ const cells = {};
52
+ for (let s = 0; s < severity.length; s++) {
53
+ for (let l = 0; l < likelihood.length; l++) {
54
+ const score = (s + 1) * (l + 1);
55
+ const band = resolveBand(bands, score);
56
+ cells[`${s}:${l}`] = { score, label: band.label, color: band.color };
57
+ }
58
+ }
59
+ return { axes: { severity: { values: severity }, likelihood: { values: likelihood } }, cells };
60
+ }
61
+ /** Recover the editable band list from a stored matrix — one band per distinct
62
+ * (label, colour), keyed to the lowest score that wears it. Round-trips any
63
+ * band-by-score matrix, including legacy seeds. */
64
+ export function bandsFromMatrix(matrix) {
65
+ const byKey = new Map();
66
+ for (const cell of Object.values(matrix.cells)) {
67
+ const key = `${cell.label}|${cell.color}`;
68
+ const existing = byKey.get(key);
69
+ if (!existing)
70
+ byKey.set(key, { label: cell.label, color: cell.color, minScore: cell.score });
71
+ else
72
+ existing.minScore = Math.min(existing.minScore, cell.score);
73
+ }
74
+ const bands = [...byKey.values()].sort((a, b) => a.minScore - b.minScore);
75
+ if (bands.length > 0 && bands[0])
76
+ bands[0].minScore = 1; // lowest band catches everything beneath it
77
+ return bands.length > 0 ? bands : [...DEFAULT_BANDS];
78
+ }
79
+ export const DEFAULT_RISK_MATRIX = buildMatrix(SEVERITY_LABELS, LIKELIHOOD_LABELS, DEFAULT_BANDS);
80
+ // --- Lookups ---------------------------------------------------------------
81
+ /** The cell for a 1-based likelihood × severity pair, or null when unrated /
82
+ * out of range. */
83
+ function cellFor(matrix, likelihood, severity) {
84
+ if (!likelihood || !severity)
85
+ return null;
86
+ return matrix.cells[`${severity - 1}:${likelihood - 1}`] ?? null;
87
+ }
88
+ /** Plain severity × likelihood product — the fallback score with no matrix. */
89
+ function riskScore(likelihood, severity) {
90
+ if (!likelihood || !severity)
91
+ return null;
92
+ return likelihood * severity;
93
+ }
94
+ /** Map a precomputed score (e.g. an aggregated "worst residual") onto a band by
95
+ * matching, then bracketing, the matrix's cell scores. */
96
+ function bandForScore(matrix, score) {
97
+ const cells = Object.values(matrix.cells);
98
+ if (cells.length === 0)
99
+ return null;
100
+ const exact = cells.find((c) => c.score === score);
101
+ if (exact)
102
+ return exact;
103
+ const sorted = [...cells].sort((a, b) => a.score - b.score);
104
+ let below = null;
105
+ for (const c of sorted)
106
+ if (c.score <= score)
107
+ below = c;
108
+ return below ?? sorted[sorted.length - 1] ?? null;
109
+ }
110
+ /** Guard against malformed/empty saved configs — fall back to the default. */
111
+ function normalizeMatrix(matrix) {
112
+ if (!matrix ||
113
+ !matrix.axes?.severity?.values?.length ||
114
+ !matrix.axes?.likelihood?.values?.length ||
115
+ !matrix.cells ||
116
+ Object.keys(matrix.cells).length === 0) {
117
+ return DEFAULT_RISK_MATRIX;
118
+ }
119
+ return matrix;
120
+ }
121
+ // --- Context ---------------------------------------------------------------
122
+ const RiskMatrixContext = createContext(DEFAULT_RISK_MATRIX);
123
+ /** The tenant's active risk matrix (falls back to the default outside a provider). */
124
+ function useRiskMatrix() {
125
+ return useContext(RiskMatrixContext);
126
+ }
127
+ export function RiskMatrixProvider({ matrix, children, }) {
128
+ const value = useMemo(() => normalizeMatrix(matrix), [matrix]);
129
+ return (_jsx(RiskMatrixContext.Provider, { value: value, children: _jsx(GeneratedValue, { value: children }) }));
130
+ }
131
+ // --- Colour helpers --------------------------------------------------------
132
+ function hexToRgb(hex) {
133
+ let h = hex.replace('#', '').trim();
134
+ if (h.length === 3)
135
+ h = h
136
+ .split('')
137
+ .map((c) => c + c)
138
+ .join('');
139
+ const n = Number.parseInt(h, 16);
140
+ if (!Number.isFinite(n) || h.length !== 6)
141
+ return { r: 148, g: 163, b: 184 }; // slate-400
142
+ return { r: (n >> 16) & 255, g: (n >> 8) & 255, b: n & 255 };
143
+ }
144
+ /** Black or white text, whichever reads on the given background. */
145
+ function readableTextColor(hex) {
146
+ const { r, g, b } = hexToRgb(hex);
147
+ const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
148
+ return luminance > 0.62 ? hexColor('fg') : hexColor('primary-fg');
149
+ }
150
+ // --- Chips -----------------------------------------------------------------
151
+ function NotRated({ prefix }) {
152
+ return (_jsxs("span", { className: "inline-flex items-center gap-1 rounded-full bg-surface-muted px-2 py-0.5 text-[11px] font-medium text-fg-muted ring-1 ring-border ring-inset", children: [_jsx(GeneratedValue, { value: prefix ? `${prefix} · ` : '' }), _jsx(GeneratedText, { id: "m_13e61f4185333d" })] }));
153
+ }
154
+ function RiskChip({ color, score, label, prefix, }) {
155
+ return (_jsxs("span", { className: "inline-flex items-center gap-1.5 rounded-full bg-surface-muted px-2 py-0.5 text-[11px] font-medium text-fg-muted ring-1 ring-border ring-inset", children: [_jsx("span", { "aria-hidden": true, className: "size-2 shrink-0 rounded-full ring-1 ring-black/10", style: { background: color } }), _jsx(GeneratedValue, { value: prefix ? (_jsx("span", { className: "opacity-70", children: _jsx(GeneratedValue, { value: prefix }) })) : null }), _jsxs("span", { className: "font-semibold tabular-nums", children: [_jsx(GeneratedValue, { value: score }), _jsx(GeneratedValue, { value: label ? ` · ${label}` : '' })] })] }));
156
+ }
157
+ /** Chip for a precomputed 1-based score (e.g. an aggregated "worst risk"). */
158
+ export function RiskScoreChip({ score, prefix }) {
159
+ const tGeneratedValue = useGeneratedValueTranslations();
160
+ const matrix = useRiskMatrix();
161
+ if (score == null)
162
+ return _jsx(NotRated, { prefix: prefix });
163
+ const cell = bandForScore(matrix, score);
164
+ return (_jsx(RiskChip, { color: cell?.color ?? hexColor('fg-subtle'), score: score, label: tGeneratedValue(cell?.label ?? ''), prefix: prefix }));
165
+ }
166
+ /** Chip showing the score + band for a likelihood × severity pair. */
167
+ export function RiskScoreBadge({ likelihood, severity, prefix, }) {
168
+ const tGeneratedValue = useGeneratedValueTranslations();
169
+ const matrix = useRiskMatrix();
170
+ const cell = cellFor(matrix, likelihood, severity);
171
+ if (!cell)
172
+ return _jsx(NotRated, { prefix: prefix });
173
+ return (_jsx(RiskChip, { color: cell.color, score: cell.score, label: tGeneratedValue(cell.label), prefix: prefix }));
174
+ }
175
+ /** Pre-control → post-control residual indicator: two score chips + an arrow. */
176
+ export function RiskDelta({ preLikelihood, preSeverity, postLikelihood, postSeverity, }) {
177
+ const pre = riskScore(preLikelihood, preSeverity);
178
+ const post = riskScore(postLikelihood, postSeverity);
179
+ if (pre == null && post == null)
180
+ return _jsx(RiskScoreBadge, {});
181
+ return (_jsxs("span", { className: "inline-flex flex-wrap items-center gap-1.5", children: [_jsx(RiskScoreBadge, { likelihood: preLikelihood, severity: preSeverity, prefix: "Initial" }), _jsx("span", { "aria-hidden": true, className: "text-fg-muted", children: "\u2192" }), _jsx(RiskScoreBadge, { likelihood: postLikelihood, severity: postSeverity, prefix: "Residual" })] }));
182
+ }
183
+ // --- Grid ------------------------------------------------------------------
184
+ /**
185
+ * The colour-banded matrix grid: severity → across the top (S1…Sn), likelihood ↑
186
+ * down the left (Ln at top, L1 at bottom). Shared by the interactive picker
187
+ * (`RiskMatrixField`) and the editor preview so they can never drift apart.
188
+ */
189
+ export function RiskMatrixGrid({ matrix, selected, onSelect, disabled, className, }) {
190
+ const tGeneratedValue = useGeneratedValueTranslations();
191
+ const sev = matrix.axes.severity.values;
192
+ const lik = matrix.axes.likelihood.values;
193
+ const interactive = Boolean(onSelect);
194
+ // Likelihood rows render high → low so the most likely sits at the top.
195
+ const rows = lik.map((_, i) => i).reverse();
196
+ return (_jsx("div", { className: cn('overflow-x-auto', className), children: _jsxs("div", { className: "inline-grid w-full gap-0.5 text-center", style: { gridTemplateColumns: `auto repeat(${sev.length}, minmax(2.25rem, 1fr))` }, children: [_jsx("div", {}), _jsx(GeneratedValue, { value: sev.map((label, i) => (_jsxs("div", { title: tGeneratedValue(label), className: "truncate px-1 pb-0.5 text-[10px] font-medium text-fg-muted", children: [_jsx(GeneratedText, { id: "m_16a715c725d406" }), _jsx(GeneratedValue, { value: i + 1 })] }, `sev-${i}`))) }), _jsx(GeneratedValue, { value: rows.map((li) => (_jsxs(Fragment, { children: [_jsxs("div", { title: tGeneratedValue(lik[li]), className: "flex items-center justify-end pr-1.5 text-[10px] font-medium text-fg-muted", children: [_jsx(GeneratedText, { id: "m_182c889ab6dd96" }), _jsx(GeneratedValue, { value: li + 1 })] }), _jsx(GeneratedValue, { value: sev.map((_, si) => {
197
+ const cell = matrix.cells[`${si}:${li}`];
198
+ const color = cell?.color ?? hexColor('fg-subtle');
199
+ const isSelected = selected?.severity === si + 1 && selected?.likelihood === li + 1;
200
+ const shared = 'flex h-11 items-center justify-center rounded-sm text-xs font-semibold transition sm:h-9 sm:text-[11px]';
201
+ const style = { background: color, color: readableTextColor(color) };
202
+ const title = `${lik[li]} × ${sev[si]} = ${cell?.score ?? ''}${cell?.label ? ` · ${cell.label}` : ''}`;
203
+ if (!interactive) {
204
+ return (_jsx("div", { title: tGeneratedValue(title), className: shared, style: style, children: _jsx(GeneratedValue, { value: cell?.score ?? '' }) }, `c-${si}-${li}`));
205
+ }
206
+ return (_jsx("button", { type: "button", disabled: disabled, title: tGeneratedValue(title), "aria-pressed": isSelected, onClick: () => onSelect?.(li + 1, si + 1), className: cn(shared, 'hover:brightness-110 active:brightness-95', isSelected && 'font-bold ring-2 ring-fg ring-inset', disabled && 'cursor-not-allowed opacity-60'), style: style, children: _jsx(GeneratedValue, { value: cell?.score ?? '' }) }, `c-${si}-${li}`));
207
+ }) })] }, `row-${li}`))) })] }) }));
208
+ }
209
+ // --- Interactive field -----------------------------------------------------
210
+ /**
211
+ * Interactive risk-matrix picker driven by the tenant's matrix. Exposes the
212
+ * chosen 1-based likelihood/severity pair through hidden inputs so it drops into
213
+ * a plain <form action={…}>.
214
+ */
215
+ export function RiskMatrixField({ label, likelihoodName, severityName, defaultLikelihood, defaultSeverity, disabled, onChange, }) {
216
+ const matrix = useRiskMatrix();
217
+ const [likelihood, setLikelihood] = useState(defaultLikelihood ?? null);
218
+ const [severity, setSeverity] = useState(defaultSeverity ?? null);
219
+ const sevLabel = severity ? matrix.axes.severity.values[severity - 1] : null;
220
+ const likLabel = likelihood ? matrix.axes.likelihood.values[likelihood - 1] : null;
221
+ function setPair(l, s) {
222
+ setLikelihood(l);
223
+ setSeverity(s);
224
+ const cell = cellFor(matrix, l, s);
225
+ onChange?.({
226
+ likelihood: l,
227
+ severity: s,
228
+ score: cell?.score ?? null,
229
+ label: cell?.label ?? null,
230
+ });
231
+ }
232
+ return (_jsxs("fieldset", { className: "space-y-1.5", disabled: disabled, children: [_jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsx("legend", { className: "text-xs font-medium tracking-wide text-fg-muted uppercase", children: _jsx(GeneratedValue, { value: label }) }), _jsx(RiskScoreBadge, { likelihood: likelihood, severity: severity })] }), _jsx("input", { type: "hidden", name: likelihoodName, value: likelihood ?? '' }), _jsx("input", { type: "hidden", name: severityName, value: severity ?? '' }), _jsx(RiskMatrixGrid, { matrix: matrix, selected: { likelihood, severity }, disabled: disabled, onSelect: (l, s) => {
233
+ if (likelihood === l && severity === s)
234
+ setPair(null, null);
235
+ else
236
+ setPair(l, s);
237
+ } }), _jsx("p", { className: "text-[11px] text-fg-muted", children: _jsx(GeneratedValue, { value: likLabel && sevLabel ? (_jsxs(_Fragment, { children: [_jsx(GeneratedValue, { value: likLabel }), " ", _jsx(GeneratedText, { id: "m_033fc0f555f2d1" }), ' ', _jsx(GeneratedValue, { value: sevLabel }), " ", _jsx(GeneratedText, { id: "m_013e7516f5d2ac" })] })) : (_jsx(GeneratedText, { id: "m_1a4be3846f6837" })) }) })] }));
238
+ }
239
+ //# sourceMappingURL=risk-matrix.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"risk-matrix.js","sourceRoot":"","sources":["../src/risk-matrix.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client'\n\nimport { GeneratedText, GeneratedValue, useGeneratedValueTranslations } from './generated-copy'\n\n// Tenant-configurable risk model, shared across the Hazard Assessments module\n// (and anywhere else that scores risk). The single source of truth is the\n// tenant's saved `RiskMatrixConfig` — axis labels, per-cell score/label/colour —\n// edited at /hazard-assessments/risk-matrix and injected here through\n// <RiskMatrixProvider> at the app shell. Every component below reads the active\n// matrix from context, so renaming an axis or recolouring a band in the editor\n// flows straight through to the live picker, the score chips and the list.\n//\n// Authoring model: a cell's score is severity × likelihood (1-based), and a cell\n// inherits the label + colour of the highest risk BAND whose threshold its score\n// meets. That keeps the editor ergonomic (tune a handful of bands, not N×M\n// cells) while the stored shape stays a flat per-cell map the renderers can read\n// without recomputing anything.\n\nimport { Fragment, createContext, useContext, useMemo, useState } from 'react'\nimport { cn } from '@braedonsaunders/appkit-ui'\nimport { hexColor } from '@braedonsaunders/appkit-tokens'\nexport type RiskMatrixConfig = { axes: { severity: { values: string[] }; likelihood: { values: string[] } }; cells: Record<string, { score: number; label: string; color: string }> }\n\ntype RiskCell = { score: number; label: string; color: string }\ntype RiskMatrixSelection = {\n likelihood: number | null\n severity: number | null\n score: number | null\n label: string | null\n}\n/** A risk band: every cell scoring ≥ `minScore` (and below the next band) wears\n * this label + colour. */\nexport type RiskBandDef = { label: string; color: string; minScore: number }\n\n// Axis sizes the editor allows. The stored 1-based ratings are plain integers,\n// so this only bounds the UI; resizing a populated matrix degrades gracefully\n// (out-of-range historical ratings simply stop matching a cell).\nexport const MIN_AXIS = 3\nexport const MAX_AXIS = 6\n\n// --- Default matrix --------------------------------------------------------\n// Reproduces the platform's long-standing 5×5 AS/NZS-style matrix. Used whenever\n// a tenant has no saved matrix (or a malformed one), so unconfigured tenants see\n// exactly what they saw before this became editable.\n\nconst SEVERITY_LABELS = ['Negligible', 'Minor', 'Moderate', 'Major', 'Severe']\nconst LIKELIHOOD_LABELS = ['Rare', 'Unlikely', 'Possible', 'Likely', 'Almost certain']\n\nconst DEFAULT_BANDS: RiskBandDef[] = [\n { label: 'Low', color: hexColor('success'), minScore: 1 },\n { label: 'Moderate', color: hexColor('warning'), minScore: 5 },\n { label: 'High', color: hexColor('primary'), minScore: 10 },\n { label: 'Critical', color: hexColor('danger'), minScore: 16 },\n]\n\n/** The risk band a score falls into (highest band whose threshold it meets;\n * the lowest band catches anything beneath the first threshold). */\nfunction resolveBand(bands: RiskBandDef[], score: number): RiskBandDef {\n const sorted = [...bands].sort((a, b) => a.minScore - b.minScore)\n let chosen = sorted[0] ?? { label: '', color: hexColor('fg-subtle'), minScore: 1 }\n for (const b of sorted) if (score >= b.minScore) chosen = b\n return chosen\n}\n\n/** Build a full per-cell matrix from axis labels + bands (score = sev × lik). */\nexport function buildMatrix(\n severityLabels: string[],\n likelihoodLabels: string[],\n bands: RiskBandDef[],\n): RiskMatrixConfig {\n const severity = severityLabels.map((s, i) => s.trim() || `S${i + 1}`)\n const likelihood = likelihoodLabels.map((s, i) => s.trim() || `L${i + 1}`)\n const cells: Record<string, RiskCell> = {}\n for (let s = 0; s < severity.length; s++) {\n for (let l = 0; l < likelihood.length; l++) {\n const score = (s + 1) * (l + 1)\n const band = resolveBand(bands, score)\n cells[`${s}:${l}`] = { score, label: band.label, color: band.color }\n }\n }\n return { axes: { severity: { values: severity }, likelihood: { values: likelihood } }, cells }\n}\n\n/** Recover the editable band list from a stored matrix — one band per distinct\n * (label, colour), keyed to the lowest score that wears it. Round-trips any\n * band-by-score matrix, including legacy seeds. */\nexport function bandsFromMatrix(matrix: RiskMatrixConfig): RiskBandDef[] {\n const byKey = new Map<string, RiskBandDef>()\n for (const cell of Object.values(matrix.cells)) {\n const key = `${cell.label}|${cell.color}`\n const existing = byKey.get(key)\n if (!existing) byKey.set(key, { label: cell.label, color: cell.color, minScore: cell.score })\n else existing.minScore = Math.min(existing.minScore, cell.score)\n }\n const bands = [...byKey.values()].sort((a, b) => a.minScore - b.minScore)\n if (bands.length > 0 && bands[0]) bands[0].minScore = 1 // lowest band catches everything beneath it\n return bands.length > 0 ? bands : [...DEFAULT_BANDS]\n}\n\nexport const DEFAULT_RISK_MATRIX: RiskMatrixConfig = buildMatrix(\n SEVERITY_LABELS,\n LIKELIHOOD_LABELS,\n DEFAULT_BANDS,\n)\n\n// --- Lookups ---------------------------------------------------------------\n\n/** The cell for a 1-based likelihood × severity pair, or null when unrated /\n * out of range. */\nfunction cellFor(\n matrix: RiskMatrixConfig,\n likelihood?: number | null,\n severity?: number | null,\n): RiskCell | null {\n if (!likelihood || !severity) return null\n return matrix.cells[`${severity - 1}:${likelihood - 1}`] ?? null\n}\n\n/** Plain severity × likelihood product — the fallback score with no matrix. */\nfunction riskScore(likelihood?: number | null, severity?: number | null): number | null {\n if (!likelihood || !severity) return null\n return likelihood * severity\n}\n\n/** Map a precomputed score (e.g. an aggregated \"worst residual\") onto a band by\n * matching, then bracketing, the matrix's cell scores. */\nfunction bandForScore(matrix: RiskMatrixConfig, score: number): RiskCell | null {\n const cells = Object.values(matrix.cells)\n if (cells.length === 0) return null\n const exact = cells.find((c) => c.score === score)\n if (exact) return exact\n const sorted = [...cells].sort((a, b) => a.score - b.score)\n let below: RiskCell | null = null\n for (const c of sorted) if (c.score <= score) below = c\n return below ?? sorted[sorted.length - 1] ?? null\n}\n\n/** Guard against malformed/empty saved configs — fall back to the default. */\nfunction normalizeMatrix(matrix: RiskMatrixConfig | null | undefined): RiskMatrixConfig {\n if (\n !matrix ||\n !matrix.axes?.severity?.values?.length ||\n !matrix.axes?.likelihood?.values?.length ||\n !matrix.cells ||\n Object.keys(matrix.cells).length === 0\n ) {\n return DEFAULT_RISK_MATRIX\n }\n return matrix\n}\n\n// --- Context ---------------------------------------------------------------\n\nconst RiskMatrixContext = createContext<RiskMatrixConfig>(DEFAULT_RISK_MATRIX)\n\n/** The tenant's active risk matrix (falls back to the default outside a provider). */\nfunction useRiskMatrix(): RiskMatrixConfig {\n return useContext(RiskMatrixContext)\n}\n\nexport function RiskMatrixProvider({\n matrix,\n children,\n}: {\n matrix: RiskMatrixConfig | null | undefined\n children: React.ReactNode\n}) {\n const value = useMemo(() => normalizeMatrix(matrix), [matrix])\n return (\n <RiskMatrixContext.Provider value={value}>\n <GeneratedValue value={children} />\n </RiskMatrixContext.Provider>\n )\n}\n\n// --- Colour helpers --------------------------------------------------------\n\nfunction hexToRgb(hex: string): { r: number; g: number; b: number } {\n let h = hex.replace('#', '').trim()\n if (h.length === 3)\n h = h\n .split('')\n .map((c) => c + c)\n .join('')\n const n = Number.parseInt(h, 16)\n if (!Number.isFinite(n) || h.length !== 6) return { r: 148, g: 163, b: 184 } // slate-400\n return { r: (n >> 16) & 255, g: (n >> 8) & 255, b: n & 255 }\n}\n\n/** Black or white text, whichever reads on the given background. */\nfunction readableTextColor(hex: string): string {\n const { r, g, b } = hexToRgb(hex)\n const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255\n return luminance > 0.62 ? hexColor('fg') : hexColor('primary-fg')\n}\n\n// --- Chips -----------------------------------------------------------------\n\nfunction NotRated({ prefix }: { prefix?: string }) {\n return (\n <span className=\"inline-flex items-center gap-1 rounded-full bg-surface-muted px-2 py-0.5 text-[11px] font-medium text-fg-muted ring-1 ring-border ring-inset\">\n <GeneratedValue value={prefix ? `${prefix} · ` : ''} />\n <GeneratedText id=\"m_13e61f4185333d\" />\n </span>\n )\n}\n\nfunction RiskChip({\n color,\n score,\n label,\n prefix,\n}: {\n color: string\n score: number\n label: string\n prefix?: string\n}) {\n return (\n <span className=\"inline-flex items-center gap-1.5 rounded-full bg-surface-muted px-2 py-0.5 text-[11px] font-medium text-fg-muted ring-1 ring-border ring-inset\">\n <span\n aria-hidden\n className=\"size-2 shrink-0 rounded-full ring-1 ring-black/10\"\n style={{ background: color }}\n />\n <GeneratedValue\n value={\n prefix ? (\n <span className=\"opacity-70\">\n <GeneratedValue value={prefix} />\n </span>\n ) : null\n }\n />\n <span className=\"font-semibold tabular-nums\">\n <GeneratedValue value={score} />\n <GeneratedValue value={label ? ` · ${label}` : ''} />\n </span>\n </span>\n )\n}\n\n/** Chip for a precomputed 1-based score (e.g. an aggregated \"worst risk\"). */\nexport function RiskScoreChip({ score, prefix }: { score: number | null; prefix?: string }) {\n const tGeneratedValue = useGeneratedValueTranslations()\n const matrix = useRiskMatrix()\n if (score == null) return <NotRated prefix={prefix} />\n const cell = bandForScore(matrix, score)\n return (\n <RiskChip\n color={cell?.color ?? hexColor('fg-subtle')}\n score={score}\n label={tGeneratedValue(cell?.label ?? '')}\n prefix={prefix}\n />\n )\n}\n\n/** Chip showing the score + band for a likelihood × severity pair. */\nexport function RiskScoreBadge({\n likelihood,\n severity,\n prefix,\n}: {\n likelihood?: number | null\n severity?: number | null\n prefix?: string\n}) {\n const tGeneratedValue = useGeneratedValueTranslations()\n const matrix = useRiskMatrix()\n const cell = cellFor(matrix, likelihood, severity)\n if (!cell) return <NotRated prefix={prefix} />\n return (\n <RiskChip\n color={cell.color}\n score={cell.score}\n label={tGeneratedValue(cell.label)}\n prefix={prefix}\n />\n )\n}\n\n/** Pre-control → post-control residual indicator: two score chips + an arrow. */\nexport function RiskDelta({\n preLikelihood,\n preSeverity,\n postLikelihood,\n postSeverity,\n}: {\n preLikelihood?: number | null\n preSeverity?: number | null\n postLikelihood?: number | null\n postSeverity?: number | null\n}) {\n const pre = riskScore(preLikelihood, preSeverity)\n const post = riskScore(postLikelihood, postSeverity)\n if (pre == null && post == null) return <RiskScoreBadge />\n return (\n <span className=\"inline-flex flex-wrap items-center gap-1.5\">\n <RiskScoreBadge likelihood={preLikelihood} severity={preSeverity} prefix=\"Initial\" />\n <span aria-hidden className=\"text-fg-muted\">\n →\n </span>\n <RiskScoreBadge likelihood={postLikelihood} severity={postSeverity} prefix=\"Residual\" />\n </span>\n )\n}\n\n// --- Grid ------------------------------------------------------------------\n\n/**\n * The colour-banded matrix grid: severity → across the top (S1…Sn), likelihood ↑\n * down the left (Ln at top, L1 at bottom). Shared by the interactive picker\n * (`RiskMatrixField`) and the editor preview so they can never drift apart.\n */\nexport function RiskMatrixGrid({\n matrix,\n selected,\n onSelect,\n disabled,\n className,\n}: {\n matrix: RiskMatrixConfig\n /** 1-based current selection, when interactive. */\n selected?: { likelihood: number | null; severity: number | null }\n /** Omit for a read-only preview. */\n onSelect?: (likelihood: number, severity: number) => void\n disabled?: boolean\n className?: string\n}) {\n const tGeneratedValue = useGeneratedValueTranslations()\n const sev = matrix.axes.severity.values\n const lik = matrix.axes.likelihood.values\n const interactive = Boolean(onSelect)\n // Likelihood rows render high → low so the most likely sits at the top.\n const rows = lik.map((_, i) => i).reverse()\n\n return (\n <div className={cn('overflow-x-auto', className)}>\n <div\n className=\"inline-grid w-full gap-0.5 text-center\"\n style={{ gridTemplateColumns: `auto repeat(${sev.length}, minmax(2.25rem, 1fr))` }}\n >\n {/* Header row: severity S1…Sn */}\n <div />\n <GeneratedValue\n value={sev.map((label, i) => (\n <div\n key={`sev-${i}`}\n title={tGeneratedValue(label)}\n className=\"truncate px-1 pb-0.5 text-[10px] font-medium text-fg-muted\"\n >\n <GeneratedText id=\"m_16a715c725d406\" />\n <GeneratedValue value={i + 1} />\n </div>\n ))}\n />\n {/* Body rows */}\n <GeneratedValue\n value={rows.map((li) => (\n <Fragment key={`row-${li}`}>\n <div\n title={tGeneratedValue(lik[li])}\n className=\"flex items-center justify-end pr-1.5 text-[10px] font-medium text-fg-muted\"\n >\n <GeneratedText id=\"m_182c889ab6dd96\" />\n <GeneratedValue value={li + 1} />\n </div>\n <GeneratedValue\n value={sev.map((_, si) => {\n const cell = matrix.cells[`${si}:${li}`]\n const color = cell?.color ?? hexColor('fg-subtle')\n const isSelected =\n selected?.severity === si + 1 && selected?.likelihood === li + 1\n const shared =\n 'flex h-11 items-center justify-center rounded-sm text-xs font-semibold transition sm:h-9 sm:text-[11px]'\n const style = { background: color, color: readableTextColor(color) }\n const title = `${lik[li]} × ${sev[si]} = ${cell?.score ?? ''}${cell?.label ? ` · ${cell.label}` : ''}`\n\n if (!interactive) {\n return (\n <div\n key={`c-${si}-${li}`}\n title={tGeneratedValue(title)}\n className={shared}\n style={style}\n >\n <GeneratedValue value={cell?.score ?? ''} />\n </div>\n )\n }\n return (\n <button\n key={`c-${si}-${li}`}\n type=\"button\"\n disabled={disabled}\n title={tGeneratedValue(title)}\n aria-pressed={isSelected}\n onClick={() => onSelect?.(li + 1, si + 1)}\n className={cn(\n shared,\n 'hover:brightness-110 active:brightness-95',\n isSelected && 'font-bold ring-2 ring-fg ring-inset',\n disabled && 'cursor-not-allowed opacity-60',\n )}\n style={style}\n >\n <GeneratedValue value={cell?.score ?? ''} />\n </button>\n )\n })}\n />\n </Fragment>\n ))}\n />\n </div>\n </div>\n )\n}\n\n// --- Interactive field -----------------------------------------------------\n\n/**\n * Interactive risk-matrix picker driven by the tenant's matrix. Exposes the\n * chosen 1-based likelihood/severity pair through hidden inputs so it drops into\n * a plain <form action={…}>.\n */\nexport function RiskMatrixField({\n label,\n likelihoodName,\n severityName,\n defaultLikelihood,\n defaultSeverity,\n disabled,\n onChange,\n}: {\n label: string\n likelihoodName: string\n severityName: string\n defaultLikelihood?: number | null\n defaultSeverity?: number | null\n disabled?: boolean\n /** Lifts the chosen pair for drawer bodies that build FormData manually. */\n onChange?: (next: RiskMatrixSelection) => void\n}) {\n const matrix = useRiskMatrix()\n const [likelihood, setLikelihood] = useState<number | null>(defaultLikelihood ?? null)\n const [severity, setSeverity] = useState<number | null>(defaultSeverity ?? null)\n const sevLabel = severity ? matrix.axes.severity.values[severity - 1] : null\n const likLabel = likelihood ? matrix.axes.likelihood.values[likelihood - 1] : null\n\n function setPair(l: number | null, s: number | null) {\n setLikelihood(l)\n setSeverity(s)\n const cell = cellFor(matrix, l, s)\n onChange?.({\n likelihood: l,\n severity: s,\n score: cell?.score ?? null,\n label: cell?.label ?? null,\n })\n }\n\n return (\n <fieldset className=\"space-y-1.5\" disabled={disabled}>\n <div className=\"flex items-center justify-between gap-2\">\n <legend className=\"text-xs font-medium tracking-wide text-fg-muted uppercase\">\n <GeneratedValue value={label} />\n </legend>\n <RiskScoreBadge likelihood={likelihood} severity={severity} />\n </div>\n <input type=\"hidden\" name={likelihoodName} value={likelihood ?? ''} />\n <input type=\"hidden\" name={severityName} value={severity ?? ''} />\n <RiskMatrixGrid\n matrix={matrix}\n selected={{ likelihood, severity }}\n disabled={disabled}\n onSelect={(l, s) => {\n if (likelihood === l && severity === s) setPair(null, null)\n else setPair(l, s)\n }}\n />\n <p className=\"text-[11px] text-fg-muted\">\n <GeneratedValue\n value={\n likLabel && sevLabel ? (\n <>\n <GeneratedValue value={likLabel} /> <GeneratedText id=\"m_033fc0f555f2d1\" />{' '}\n <GeneratedValue value={sevLabel} /> <GeneratedText id=\"m_013e7516f5d2ac\" />\n </>\n ) : (\n <GeneratedText id=\"m_1a4be3846f6837\" />\n )\n }\n />\n </p>\n </fieldset>\n )\n}\n"]}
@@ -0,0 +1,19 @@
1
+ import type { FormField } from '@braedonsaunders/appkit-forms-core';
2
+ import { type RiskMatrixConfig } from './risk-matrix.js';
3
+ export declare function GpsField({ value, onChange, disabled }: ControlProps): import("react").JSX.Element;
4
+ export declare function MatrixField({ field, value, onChange, disabled }: ControlProps): import("react").JSX.Element;
5
+ export declare function TableField({ field, value, onChange, disabled }: ControlProps): import("react").JSX.Element;
6
+ export declare function AddressField({ value, onChange, disabled }: ControlProps): import("react").JSX.Element;
7
+ export declare function SignatureField({ value, onChange, disabled }: ControlProps): import("react").JSX.Element;
8
+ export declare function SketchField({ value, onChange, disabled }: ControlProps): import("react").JSX.Element;
9
+ export declare function RiskField({ field, value, onChange, disabled, matrix }: ControlProps & {
10
+ matrix?: RiskMatrixConfig;
11
+ }): import("react").JSX.Element;
12
+ type ControlProps = {
13
+ field: FormField;
14
+ value: unknown;
15
+ onChange: (value: unknown) => void;
16
+ disabled?: boolean;
17
+ };
18
+ export {};
19
+ //# sourceMappingURL=runtime-controls.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime-controls.d.ts","sourceRoot":"","sources":["../src/runtime-controls.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAA4B,MAAM,oCAAoC,CAAA;AAG7F,OAAO,EAAuC,KAAK,gBAAgB,EAAE,MAAM,eAAe,CAAA;AAG1F,wBAAgB,QAAQ,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,YAAY,+BAKnE;AAED,wBAAgB,WAAW,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,YAAY,+BAM7E;AAED,wBAAgB,UAAU,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,YAAY,+BAW5E;AAQD,wBAAgB,YAAY,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,YAAY,+BAIvE;AAED,wBAAgB,cAAc,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,YAAY,+BAGzE;AAED,wBAAgB,WAAW,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,YAAY,+BAGtE;AAED,wBAAgB,SAAS,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,YAAY,GAAG;IAAE,MAAM,CAAC,EAAE,gBAAgB,CAAA;CAAE,+BAGnH;AAED,KAAK,YAAY,GAAG;IAAE,KAAK,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAE,CAAA"}