@coffer-org/web-ui 4.0.0 → 6.0.0
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/components/ui/copy-button.d.ts +6 -0
- package/dist/components/ui/copy-button.js +28 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/internal-link.d.ts +15 -0
- package/dist/internal-link.js +66 -0
- package/dist/layout.js +3 -3
- package/dist/registry.d.ts +2 -1
- package/dist/registry.js +1 -0
- package/dist/render/activate.d.ts +4 -0
- package/dist/render/activate.js +11 -0
- package/dist/render/blocks/balance.js +3 -2
- package/dist/render/blocks/block-shell.d.ts +1 -0
- package/dist/render/blocks/block-shell.js +4 -0
- package/dist/render/blocks/chart.js +2 -1
- package/dist/render/blocks/compare.js +2 -1
- package/dist/render/blocks/countdown.js +2 -1
- package/dist/render/blocks/idcard.js +2 -1
- package/dist/render/blocks/journal.js +2 -1
- package/dist/render/blocks/ledger.js +2 -2
- package/dist/render/blocks/manifest.js +2 -1
- package/dist/render/blocks/masthead.js +2 -1
- package/dist/render/blocks/meter.js +2 -2
- package/dist/render/blocks/nutrition.js +2 -2
- package/dist/render/blocks/plaque.js +2 -1
- package/dist/render/blocks/receipt.js +2 -2
- package/dist/render/blocks/score.js +2 -2
- package/dist/render/blocks/stats.js +3 -2
- package/dist/render/blocks/table.js +5 -4
- package/dist/render/chrome.d.ts +21 -3
- package/dist/render/chrome.js +46 -6
- package/dist/render/core/dimensions.js +1 -1
- package/dist/render/core/embed.js +8 -1
- package/dist/render/core/money.js +1 -1
- package/dist/render/core/range.js +1 -1
- package/dist/render/core/reminder.js +1 -1
- package/dist/render/core/text.js +57 -3
- package/dist/render/core/weight.js +1 -1
- package/dist/render/datum.d.ts +4 -0
- package/dist/render/datum.js +16 -0
- package/dist/render/dispatch.js +4 -2
- package/dist/render/field-frame.d.ts +1 -1
- package/dist/render/field-frame.js +20 -9
- package/dist/render/markdown-view.js +33 -4
- package/dist/render/popover-field.js +10 -19
- package/dist/render/widgets/display.js +2 -1
- package/dist/render/widgets/edit.js +2 -2
- package/dist/render/widgets/fixed-collection.js +2 -1
- package/dist/render/widgets/group-widgets.js +1 -1
- package/dist/render/widgets/group.js +2 -1
- package/dist/render/widgets/table-group.js +4 -3
- package/dist/types.d.ts +2 -0
- package/package.json +2 -2
|
@@ -7,10 +7,11 @@ import { registerRenderer } from "../../registry.js";
|
|
|
7
7
|
import { Icon } from "../../components/Icon.js";
|
|
8
8
|
import { AddBtn } from "../shared.js";
|
|
9
9
|
import { RemoveBtn } from "../multiple-chips.js";
|
|
10
|
-
import { groupScopeClass, useGroupDepth, GroupDepthProvider } from "../chrome.js";
|
|
10
|
+
import { groupScopeClass, useGroupDepth, GroupDepthProvider, CHROME_LABEL_CLS } from "../chrome.js";
|
|
11
11
|
import { useGroupEdit } from "../widgets/group-edit.js";
|
|
12
12
|
import { useHybridScope } from "../hybrid-scope.js";
|
|
13
13
|
import { FieldSlot, fieldKeys } from "../dispatch.js";
|
|
14
|
+
import { datumClass } from "../datum.js";
|
|
14
15
|
import { useFieldErrors } from "../field-errors.js";
|
|
15
16
|
import { sortByField } from "../sort-rows.js";
|
|
16
17
|
import { useCollectionRows } from "../use-collection-rows.js";
|
|
@@ -123,12 +124,12 @@ function TablePage({ el, value, recordCtx, mode }) {
|
|
|
123
124
|
const totalRow = (bucketRows, testId) => (_jsxs("tr", { "data-testid": testId, className: "text-xs text-muted", children: [numbered && _jsx("td", { className: "border-b border-border py-1.5 pr-3" }), cols.map((f, i) => {
|
|
124
125
|
const kind = totals?.[f.key];
|
|
125
126
|
const n = kind && colOf(f.key) ? aggregate(kind, f, bucketRows) : undefined;
|
|
126
|
-
return (_jsxs("td", { className:
|
|
127
|
+
return (_jsxs("td", { className: `border-b border-border py-1.5 pr-4 ${datumClass(f.type)}`.trim(), children: [i === 0 && !totals?.[f.key] ? t('table.total') : null, n === undefined ? null : (_jsx(FieldSlot, { el: f, field: f.type, value: totalValue(f, bucketRows, n), mode: "view", bare: true, recordCtx: recordCtx }))] }, f.key));
|
|
127
128
|
}), rowTools && _jsx("td", { className: "border-b border-border" })] }));
|
|
128
129
|
const { rowId, updateRow, removeRow, addRow } = useCollectionRows(rows, (v) => g.onChange(v));
|
|
129
130
|
const errorFor = (idx, key) => isStorageGroup(group) ? errorAt([group.key, idx, key]) : undefined;
|
|
130
|
-
const bodyRow = (row, idx, seq) => (_jsxs("tr", { "data-testid": `table-row-${idx}`, children: [numbered && _jsx("td", { className: "border-b border-border py-2 pr-3 text-xs text-muted", children: seq }), cols.map((f) => (_jsx("td", { className:
|
|
131
|
-
const content = (_jsxs("div", { className: "flex flex-col gap-2 min-w-0", children: [_jsx("div", { className: "overflow-auto max-h-[60vh]", children: _jsxs("table", { className: "w-full border-separate border-spacing-0 text-sm", "data-testid": "table-block", children: [_jsx("thead", { className: "eink-solid sticky top-0 z-10 bg-bg/95 backdrop-blur", children: _jsxs("tr", { children: [numbered && _jsx("th", { className: "border-b border-border py-1.5 pr-3" }), cols.map((f) => (_jsx("th", { className:
|
|
131
|
+
const bodyRow = (row, idx, seq) => (_jsxs("tr", { "data-testid": `table-row-${idx}`, children: [numbered && _jsx("td", { className: "border-b border-border py-2 pr-3 text-xs text-muted", children: seq }), cols.map((f) => (_jsx("td", { className: `border-b border-border py-2 pr-4 align-top ${datumClass(f.type)}`.trim(), children: cellNode(f, row, editing, live, (v) => updateRow(idx, f.key, v), recordCtx, errorFor(idx, f.key)) }, f.key))), rowTools && (_jsx("td", { className: "border-b border-border py-2 align-top", children: _jsx(RemoveBtn, { className: "mt-0.5", onClick: () => removeRow(idx) }) }))] }, rowId(row)));
|
|
132
|
+
const content = (_jsxs("div", { className: "flex flex-col gap-2 min-w-0", children: [_jsx("div", { className: "overflow-auto max-h-[60vh]", children: _jsxs("table", { className: "w-full border-separate border-spacing-0 text-sm", "data-testid": "table-block", children: [_jsx("thead", { className: "eink-solid sticky top-0 z-10 bg-bg/95 backdrop-blur", children: _jsxs("tr", { children: [numbered && _jsx("th", { className: "border-b border-border py-1.5 pr-3" }), cols.map((f) => (_jsx("th", { className: `border-b border-border py-1.5 pr-4 text-left ${CHROME_LABEL_CLS}`, children: _jsxs("button", { type: "button", className: "inline-flex items-center gap-1 focus-visible:outline-2 focus-visible:outline-accent", onClick: () => cycle(f.key), children: [t(f.type.label), !editing && sort?.key === f.key && (_jsx(Icon, { name: sort.dir === 'asc' ? 'lucide:arrow-up' : 'lucide:arrow-down', className: "size-3" }))] }) }, f.key))), rowTools && _jsx("th", { className: "border-b border-border" })] }) }), buckets.map((b) => (_jsxs("tbody", { children: [b.key !== null && (_jsx("tr", { "data-testid": `table-group-${b.key}`, children: _jsxs("th", { colSpan: cols.length + (numbered ? 1 : 0) + (rowTools ? 1 : 0), className: `border-b border-border pt-4 pb-1 text-left ${CHROME_LABEL_CLS} ${groupFld ? datumClass(groupFld.type) : ''}`.trim(), children: [b.key === NO_VALUE || !groupFld ? (b.label) : (_jsx(FieldSlot, { el: groupFld, field: groupFld.type, value: b.rows[0]?.[groupKey ?? ''], mode: "view", bare: true, recordCtx: recordCtx })), ' ', _jsx("span", { className: "text-muted/70", children: b.rows.length })] }) })), b.rows.map((row, i) => bodyRow(row, indexOf.get(row) ?? -1, i + 1)), hasTotals && b.key !== null && totalRow(b.rows, `table-subtotal-${b.key}`)] }, b.key ?? 'all'))), hasTotals && _jsx("tfoot", { children: totalRow(rows, 'table-total') })] }) }), rowTools && _jsx(AddBtn, { onClick: addRow, children: t('form.addRow') })] }));
|
|
132
133
|
if (mode !== 'view')
|
|
133
134
|
return content;
|
|
134
135
|
return (_jsxs("section", { className: `${groupScopeClass(depth)} flex flex-col gap-1 min-w-0`, "data-testid": "table", children: [group.label && (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("h3", { className: "text-[13px] font-semibold text-accent", children: t(group.label) }), !live && g.actions && _jsx("span", { className: "ml-auto flex shrink-0 items-center gap-1.5", children: g.actions })] })), _jsx(GroupDepthProvider, { depth: depth + 1, children: content })] }));
|
package/dist/render/chrome.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ReactNode } from 'react';
|
|
1
|
+
import { type HTMLAttributes, type ReactNode } from 'react';
|
|
2
2
|
import type { BoxGeom } from '../types.ts';
|
|
3
3
|
import type { FieldMeta, ContainerNode } from '@coffer-org/sdk/fields';
|
|
4
4
|
export declare const STACK = "gap-3.5";
|
|
@@ -14,6 +14,10 @@ export declare function FieldLabel({ label, required, actions, reserveAction, }:
|
|
|
14
14
|
reserveAction?: boolean;
|
|
15
15
|
htmlFor?: string;
|
|
16
16
|
}): import("react").JSX.Element;
|
|
17
|
+
export declare const CHROME_LABEL_CLS = "min-w-0 text-[11px] font-medium tracking-wide text-muted";
|
|
18
|
+
export declare function ChromeLabel({ children, ...rest }: {
|
|
19
|
+
children: ReactNode;
|
|
20
|
+
} & HTMLAttributes<HTMLSpanElement>): import("react").JSX.Element;
|
|
17
21
|
export declare function FieldLabelText({ label, required }: {
|
|
18
22
|
label: string;
|
|
19
23
|
required?: boolean;
|
|
@@ -41,11 +45,18 @@ export declare function groupScopeClass(depth: number): string;
|
|
|
41
45
|
export declare function groupRevealClass(depth: number): string;
|
|
42
46
|
export declare const ACTION_CHIP = "h-7 w-7 rounded-md border border-border bg-surface/80 text-muted hover:border-accent/60 hover:text-accent";
|
|
43
47
|
export declare const ACTION_CHIP_ACCENT = "h-7 w-7 rounded-md border border-accent/40 bg-accent/10 text-accent hover:border-accent hover:bg-accent/20 hover:text-accent";
|
|
44
|
-
export declare function EditToggle({ onEdit,
|
|
48
|
+
export declare function EditToggle({ onEdit, scope }: {
|
|
45
49
|
onEdit: () => void;
|
|
46
|
-
variant?: 'inline' | 'overlay';
|
|
47
50
|
scope?: 'field' | 'group';
|
|
48
51
|
}): import("react").JSX.Element;
|
|
52
|
+
export declare function ValueActions({ getText, onEdit }: {
|
|
53
|
+
getText: () => string;
|
|
54
|
+
onEdit: () => void;
|
|
55
|
+
}): import("react").JSX.Element;
|
|
56
|
+
export declare function ValueActionsOverlay({ onEdit, children }: {
|
|
57
|
+
onEdit: () => void;
|
|
58
|
+
children: ReactNode;
|
|
59
|
+
}): import("react").JSX.Element;
|
|
49
60
|
export declare function SaveCancel({ size, busy, disabled, onSave, onCancel, }: {
|
|
50
61
|
size: 'xs' | 'sm';
|
|
51
62
|
busy: boolean;
|
|
@@ -59,6 +70,13 @@ export declare function FieldBox({ geom, field, children }: {
|
|
|
59
70
|
field: FieldMeta;
|
|
60
71
|
children: ReactNode;
|
|
61
72
|
}): import("react").JSX.Element;
|
|
73
|
+
export declare function TriggerBox({ expanded, label, onOpen, haspopup, children, }: {
|
|
74
|
+
expanded?: boolean;
|
|
75
|
+
label?: string;
|
|
76
|
+
onOpen: () => void;
|
|
77
|
+
haspopup?: 'dialog';
|
|
78
|
+
children: ReactNode;
|
|
79
|
+
}): import("react").JSX.Element;
|
|
62
80
|
export declare function FieldError({ error }: {
|
|
63
81
|
error?: string;
|
|
64
82
|
}): import("react").JSX.Element | null;
|
package/dist/render/chrome.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { createContext, useContext } from 'react';
|
|
2
|
+
import { createContext, useContext, useLayoutEffect, useRef, useState, } from 'react';
|
|
3
3
|
import { useTranslation } from 'react-i18next';
|
|
4
4
|
import { Check, Pencil, X } from 'lucide-react';
|
|
5
5
|
import { Button } from "../components/ui/button.js";
|
|
6
|
+
import { CopyButton } from "../components/ui/copy-button.js";
|
|
6
7
|
import { Icon } from "../components/Icon.js";
|
|
7
8
|
import { resolveBox, boxClass, FIELD_ROW_CLS } from "./box.js";
|
|
8
9
|
import { isCollectionGroup } from '@coffer-org/sdk/fields';
|
|
10
|
+
import { datumClass } from "./datum.js";
|
|
9
11
|
export const STACK = 'gap-3.5';
|
|
10
12
|
export const LAYOUT_STACK = 'gap-6';
|
|
11
13
|
export const PAGE_STACK = 'gap-6';
|
|
@@ -15,8 +17,12 @@ export function Rail({ children }) {
|
|
|
15
17
|
export function FieldLabel({ label, required, actions, reserveAction, }) {
|
|
16
18
|
return (_jsxs("div", { className: `flex items-center gap-2 mb-0.5 min-h-8 ${reserveAction ? 'pr-24' : ''}`, children: [_jsx(FieldLabelText, { label: label, required: required }), actions && _jsx("div", { className: "ml-auto flex shrink-0 items-center gap-1.5", children: actions })] }));
|
|
17
19
|
}
|
|
20
|
+
export const CHROME_LABEL_CLS = 'min-w-0 text-[11px] font-medium tracking-wide text-muted';
|
|
21
|
+
export function ChromeLabel({ children, ...rest }) {
|
|
22
|
+
return (_jsx("span", { ...rest, className: CHROME_LABEL_CLS, children: children }));
|
|
23
|
+
}
|
|
18
24
|
export function FieldLabelText({ label, required }) {
|
|
19
|
-
return (_jsxs(
|
|
25
|
+
return (_jsxs(ChromeLabel, { children: [label, required && _jsx("span", { className: "text-danger ml-0.5", children: "*" })] }));
|
|
20
26
|
}
|
|
21
27
|
const GROUP_TITLE_BASE = 'flex items-center gap-2 font-semibold';
|
|
22
28
|
export const GROUP_TITLE_CLS = `${GROUP_TITLE_BASE} text-[13px] text-accent`;
|
|
@@ -64,12 +70,19 @@ export function groupRevealClass(depth) {
|
|
|
64
70
|
}
|
|
65
71
|
export const ACTION_CHIP = 'h-7 w-7 rounded-md border border-border bg-surface/80 text-muted hover:border-accent/60 hover:text-accent';
|
|
66
72
|
export const ACTION_CHIP_ACCENT = 'h-7 w-7 rounded-md border border-accent/40 bg-accent/10 text-accent hover:border-accent hover:bg-accent/20 hover:text-accent';
|
|
67
|
-
export function EditToggle({ onEdit,
|
|
73
|
+
export function EditToggle({ onEdit, scope = 'field' }) {
|
|
68
74
|
const { t } = useTranslation();
|
|
69
75
|
const depth = useGroupDepth();
|
|
70
76
|
const reveal = scope === 'group' ? groupRevealClass(depth) : EDIT_REVEAL;
|
|
71
|
-
const
|
|
72
|
-
return (_jsx(Button, { type: "button", variant: "outline", size: "iconXs", className: `${
|
|
77
|
+
const label = t('actions.edit');
|
|
78
|
+
return (_jsx(Button, { type: "button", variant: "outline", size: "iconXs", className: `${ACTION_CHIP} ${reveal}`, onClick: onEdit, title: label, "aria-label": label, children: _jsx(Pencil, { size: 13 }) }));
|
|
79
|
+
}
|
|
80
|
+
export function ValueActions({ getText, onEdit }) {
|
|
81
|
+
return (_jsxs("span", { className: "inline-flex shrink-0 items-center gap-1", children: [_jsx(CopyButton, { getText: getText, className: `${ACTION_CHIP} ${EDIT_REVEAL}` }), _jsx(EditToggle, { onEdit: onEdit })] }));
|
|
82
|
+
}
|
|
83
|
+
export function ValueActionsOverlay({ onEdit, children }) {
|
|
84
|
+
const ref = useRef(null);
|
|
85
|
+
return (_jsxs("div", { className: "relative flex min-w-0 max-w-full items-center pr-16", children: [_jsx("div", { ref: ref, className: "contents", children: children }), _jsx("div", { className: "absolute right-1 top-1/2 z-10 -translate-y-1/2", children: _jsx(ValueActions, { getText: () => ref.current?.textContent ?? '', onEdit: onEdit }) })] }));
|
|
73
86
|
}
|
|
74
87
|
export function SaveCancel({ size, busy, disabled, onSave, onCancel, }) {
|
|
75
88
|
const { t } = useTranslation();
|
|
@@ -83,7 +96,34 @@ export function FieldBox({ geom, field, children }) {
|
|
|
83
96
|
const box = resolveBox(field, geom);
|
|
84
97
|
if (box === 'none')
|
|
85
98
|
return _jsx(_Fragment, { children: children });
|
|
86
|
-
return _jsx("div", { className: boxClass(box), children: children });
|
|
99
|
+
return _jsx("div", { className: `${boxClass(box)} ${datumClass(field)}`.trim(), children: children });
|
|
100
|
+
}
|
|
101
|
+
const TRIGGER_DESCENDANT_SEL = 'a,button,[role="button"],audio,video,input,label,summary,select,textarea,[contenteditable]';
|
|
102
|
+
function ownedByDescendant(target, currentTarget) {
|
|
103
|
+
if (!(target instanceof Element))
|
|
104
|
+
return false;
|
|
105
|
+
const inner = target.closest(TRIGGER_DESCENDANT_SEL);
|
|
106
|
+
return inner !== null && inner !== currentTarget;
|
|
107
|
+
}
|
|
108
|
+
export function TriggerBox({ expanded, label, onOpen, haspopup, children, }) {
|
|
109
|
+
const textRef = useRef(null);
|
|
110
|
+
const [valueText, setValueText] = useState('');
|
|
111
|
+
useLayoutEffect(() => {
|
|
112
|
+
setValueText(textRef.current?.textContent ?? '');
|
|
113
|
+
});
|
|
114
|
+
const accessibleName = label && valueText ? `${label}: ${valueText}` : label;
|
|
115
|
+
return (_jsx("div", { ref: textRef, role: "button", tabIndex: 0, "aria-haspopup": haspopup, "aria-expanded": expanded, "aria-label": accessibleName, className: "relative min-w-0 cursor-pointer rounded-lg outline-none transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", onClick: (e) => {
|
|
116
|
+
if (ownedByDescendant(e.target, e.currentTarget))
|
|
117
|
+
return;
|
|
118
|
+
onOpen();
|
|
119
|
+
}, onKeyDown: (e) => {
|
|
120
|
+
if (e.key !== 'Enter' && e.key !== ' ')
|
|
121
|
+
return;
|
|
122
|
+
if (ownedByDescendant(e.target, e.currentTarget))
|
|
123
|
+
return;
|
|
124
|
+
e.preventDefault();
|
|
125
|
+
onOpen();
|
|
126
|
+
}, children: children }));
|
|
87
127
|
}
|
|
88
128
|
export function FieldError({ error }) {
|
|
89
129
|
if (!error)
|
|
@@ -33,7 +33,7 @@ function DimInput({ value, onChange }) {
|
|
|
33
33
|
}
|
|
34
34
|
function DimView({ value }) {
|
|
35
35
|
const s = fmtDimensions(value);
|
|
36
|
-
return s ? _jsx("span", {
|
|
36
|
+
return s ? _jsx("span", { children: s }) : _jsx(Dash, {});
|
|
37
37
|
}
|
|
38
38
|
function DimPage(p) {
|
|
39
39
|
return (_jsx(FieldFrame, { field: p.field, value: p.value, label: p.label, required: p.required, error: p.error, mode: p.mode, geom: "inline", width: "w-fit", bare: p.bare, view: (v) => _jsx(DimView, { value: v }), edit: (value, onChange) => _jsx(DimInput, { value: value, onChange: onChange }) }));
|
|
@@ -96,4 +96,11 @@ export function EmbedInline(p) {
|
|
|
96
96
|
const first = list[0];
|
|
97
97
|
return (_jsxs("span", { className: "inline-flex min-w-0 items-center gap-1", children: [playable(first) ? _jsx(Play, { size: 12 }) : _jsx(Link2, { size: 12 }), _jsx("span", { className: "truncate", children: first.title ?? getHostname(first.url) }), list.length > 1 && _jsxs("span", { className: "text-muted", children: ["+", list.length - 1] })] }));
|
|
98
98
|
}
|
|
99
|
-
registerRenderer({
|
|
99
|
+
registerRenderer({
|
|
100
|
+
kinds: ['embed'],
|
|
101
|
+
layout: 'block',
|
|
102
|
+
Page: EmbedPage,
|
|
103
|
+
Inline: EmbedInline,
|
|
104
|
+
pickerSize: 'large',
|
|
105
|
+
activate: 'value',
|
|
106
|
+
});
|
|
@@ -24,7 +24,7 @@ export function fmtMoney(value, currency) {
|
|
|
24
24
|
}
|
|
25
25
|
function MoneyView({ value, field }) {
|
|
26
26
|
const s = fmtMoney(value, currencyOf(field));
|
|
27
|
-
return s ? _jsx("span", {
|
|
27
|
+
return s ? _jsx("span", { children: s }) : _jsx(Dash, {});
|
|
28
28
|
}
|
|
29
29
|
function MoneyInput({ field, value, onChange }) {
|
|
30
30
|
const cur = currencyOf(field);
|
|
@@ -44,7 +44,7 @@ function RangeView({ value, field }) {
|
|
|
44
44
|
const r = parseRange(value, field);
|
|
45
45
|
if (r.from == null && r.to == null)
|
|
46
46
|
return _jsx(Dash, {});
|
|
47
|
-
return (_jsxs("span", {
|
|
47
|
+
return (_jsxs("span", { children: [r.from ?? '…', "\u2013", r.to ?? '…'] }));
|
|
48
48
|
}
|
|
49
49
|
function RangePage(p) {
|
|
50
50
|
return (_jsx(FieldFrame, { field: p.field, value: p.value, label: p.label, required: p.required, error: p.error, mode: p.mode, geom: "inline", width: "w-48", bare: p.bare, view: (v) => _jsx(RangeView, { value: v, field: p.field }), edit: (value, onChange) => (_jsx(RangeInput, { field: p.field, value: value, onChange: onChange })) }));
|
|
@@ -45,7 +45,7 @@ function ReminderView({ field, value }) {
|
|
|
45
45
|
: tone === 'soon'
|
|
46
46
|
? t('reminder.inDays', { days: diff })
|
|
47
47
|
: null;
|
|
48
|
-
return (_jsxs("span", { className:
|
|
48
|
+
return (_jsxs("span", { className: TONE_CLS[tone], children: [isoToDisplay(s), note ? _jsx("span", { className: "ml-2 text-xs", children: note }) : null] }));
|
|
49
49
|
}
|
|
50
50
|
function ReminderPage(p) {
|
|
51
51
|
return (_jsx(FieldFrame, { field: p.field, value: p.value, label: p.label, required: p.required, error: p.error, mode: p.mode, geom: "inline", width: "w-40", bare: p.bare, view: (v) => _jsx(ReminderView, { field: p.field, value: v }), edit: (value, onChange) => (_jsx(Input, { type: "date", value: String(value ?? ''), onChange: (e) => onChange(e.target.value) })) }));
|
package/dist/render/core/text.js
CHANGED
|
@@ -72,22 +72,75 @@ function TextInline({ field, value }) {
|
|
|
72
72
|
return _jsx("span", { className: "truncate", children: trunc(fmtOptionLabel(t, field, value)) });
|
|
73
73
|
}
|
|
74
74
|
registerRenderer({
|
|
75
|
-
kinds: ['text', '
|
|
75
|
+
kinds: ['text', 'slug', 'unit'],
|
|
76
76
|
Page: TextPage,
|
|
77
77
|
Inline: TextInline,
|
|
78
78
|
Value: PlainValue,
|
|
79
79
|
keys: optionKeys,
|
|
80
80
|
});
|
|
81
|
+
function telHref(s) {
|
|
82
|
+
const digits = s.replace(/\D/g, '');
|
|
83
|
+
if (!digits)
|
|
84
|
+
return undefined;
|
|
85
|
+
return (s.trim().startsWith('+') ? '+' : '') + digits;
|
|
86
|
+
}
|
|
87
|
+
function TelAnchor({ value }) {
|
|
88
|
+
const s = String(value ?? '');
|
|
89
|
+
if (!hasValue(s))
|
|
90
|
+
return _jsx(Dash, {});
|
|
91
|
+
const href = telHref(s);
|
|
92
|
+
if (!href)
|
|
93
|
+
return _jsx("span", { className: "truncate", children: s });
|
|
94
|
+
return (_jsx("a", { href: `tel:${href}`, className: "inline-block max-w-full truncate align-bottom text-primary underline hover:text-primary/80 transition-colors", title: s, children: s }));
|
|
95
|
+
}
|
|
81
96
|
function TelPage(p) {
|
|
82
|
-
return (_jsx(FieldFrame, { field: p.field, value: p.value, label: p.label, required: p.required, error: p.error, mode: p.mode, geom: "inline", width: "w-48", bare: p.bare, view: (v) =>
|
|
97
|
+
return (_jsx(FieldFrame, { field: p.field, value: p.value, label: p.label, required: p.required, error: p.error, mode: p.mode, geom: "inline", width: "w-48", bare: p.bare, view: (v) => _jsx(TelAnchor, { value: v }), edit: (value, onChange) => (_jsx(Suspense, { fallback: _jsx("div", { className: "text-sm text-muted", children: "\u2026" }), children: _jsx(PhoneInput, { value: String(value), onChange: (v) => onChange(v ?? '') }) })) }));
|
|
83
98
|
}
|
|
84
99
|
function TelInline({ value }) {
|
|
85
|
-
return _jsx(
|
|
100
|
+
return _jsx(TelAnchor, { value: value });
|
|
86
101
|
}
|
|
87
102
|
registerRenderer({
|
|
88
103
|
kinds: ['tel'],
|
|
89
104
|
Page: TelPage,
|
|
90
105
|
Inline: TelInline,
|
|
106
|
+
activate: 'value',
|
|
107
|
+
});
|
|
108
|
+
const EMAIL_HREF_RE = /^[^\s@]+@[^\s@]+$/;
|
|
109
|
+
function mailtoHref(s) {
|
|
110
|
+
const trimmed = s.trim();
|
|
111
|
+
return EMAIL_HREF_RE.test(trimmed) ? trimmed : undefined;
|
|
112
|
+
}
|
|
113
|
+
function EmailAnchor({ value }) {
|
|
114
|
+
const s = String(value ?? '');
|
|
115
|
+
if (!hasValue(s))
|
|
116
|
+
return _jsx(Dash, {});
|
|
117
|
+
const href = mailtoHref(s);
|
|
118
|
+
if (!href)
|
|
119
|
+
return _jsx("span", { className: "truncate", children: s });
|
|
120
|
+
return (_jsx("a", { href: `mailto:${href}`, className: "inline-block max-w-full truncate align-bottom text-primary underline hover:text-primary/80 transition-colors", title: s, children: s }));
|
|
121
|
+
}
|
|
122
|
+
function EmailDisplay({ field, value }) {
|
|
123
|
+
return multiView(field, value, (i) => _jsx(EmailAnchor, { value: i }), undefined, optionChipProps(field));
|
|
124
|
+
}
|
|
125
|
+
function EmailPage(p) {
|
|
126
|
+
return (_jsx(FieldFrame, { field: p.field, value: p.value, label: p.label, required: p.required, error: p.error, mode: p.mode, geom: "inline", bare: p.bare, view: (v) => _jsx(EmailDisplay, { field: p.field, value: v }), edit: (value, onChange) => (_jsx(TextInput, { name: `field-${p.field.label}`, field: p.field, value: value, onChange: onChange, selectOptions: p.selectOptions, excludeValues: p.excludeValues })) }));
|
|
127
|
+
}
|
|
128
|
+
function EmailInline({ field, value }) {
|
|
129
|
+
if (field.hints['multiple'] === true) {
|
|
130
|
+
const items = Array.isArray(value) ? value : parseStrArray(value);
|
|
131
|
+
if (!items.length)
|
|
132
|
+
return _jsx("span", { className: "truncate", children: "\u2014" });
|
|
133
|
+
return (_jsx("span", { className: "truncate", children: items.map((v, i) => (_jsxs(React.Fragment, { children: [i > 0 ? ', ' : '', _jsx(EmailAnchor, { value: v })] }, i))) }));
|
|
134
|
+
}
|
|
135
|
+
return _jsx(EmailAnchor, { value: value });
|
|
136
|
+
}
|
|
137
|
+
registerRenderer({
|
|
138
|
+
kinds: ['email'],
|
|
139
|
+
Page: EmailPage,
|
|
140
|
+
Inline: EmailInline,
|
|
141
|
+
Value: ({ value }) => _jsx(EmailAnchor, { value: value }),
|
|
142
|
+
keys: optionKeys,
|
|
143
|
+
activate: 'value',
|
|
91
144
|
});
|
|
92
145
|
const hrefOf = (s) => (/^[a-z][a-z0-9+.-]*:\/\//i.test(s) ? s : `https://${s}`);
|
|
93
146
|
function LinkAnchor({ value }) {
|
|
@@ -104,6 +157,7 @@ registerRenderer({
|
|
|
104
157
|
Page: LinkPage,
|
|
105
158
|
Inline: ({ value }) => _jsx(LinkAnchor, { value: value }),
|
|
106
159
|
Value: ({ value }) => _jsx(LinkAnchor, { value: value }),
|
|
160
|
+
activate: 'value',
|
|
107
161
|
});
|
|
108
162
|
function PasswordInput({ name, field, value, onChange }) {
|
|
109
163
|
if (field.hints['multiple'] === true) {
|
|
@@ -16,7 +16,7 @@ function WeightView({ value }) {
|
|
|
16
16
|
const p = weightParts(value);
|
|
17
17
|
if (!p)
|
|
18
18
|
return _jsx(Dash, {});
|
|
19
|
-
return (_jsxs("span", {
|
|
19
|
+
return (_jsxs("span", { children: [p.n, " ", t(`core.units.${p.unit}`)] }));
|
|
20
20
|
}
|
|
21
21
|
function ScaleToggle({ unit, onPick, label }) {
|
|
22
22
|
const { t } = useTranslation();
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
const TABULAR_NUMS = 'tabular-nums';
|
|
2
|
+
export const DATUM_KINDS = new Set(['tel', 'slug', 'tag', 'dimensions']);
|
|
3
|
+
export const DATUM_PRIMS = new Set([
|
|
4
|
+
'number',
|
|
5
|
+
'date',
|
|
6
|
+
'time',
|
|
7
|
+
'datetime',
|
|
8
|
+
'measured',
|
|
9
|
+
'geo',
|
|
10
|
+
'period',
|
|
11
|
+
'numberRange',
|
|
12
|
+
'realRange',
|
|
13
|
+
]);
|
|
14
|
+
export function datumClass(field) {
|
|
15
|
+
return DATUM_KINDS.has(field.kind) || DATUM_PRIMS.has(field.prim) ? TABULAR_NUMS : '';
|
|
16
|
+
}
|
package/dist/render/dispatch.js
CHANGED
|
@@ -4,6 +4,7 @@ import { wrapKey } from '@coffer-org/sdk/fields';
|
|
|
4
4
|
import { resolveRenderer } from "../registry.js";
|
|
5
5
|
import { parseTagValues, isMultiValued } from "../lib/multi-value.js";
|
|
6
6
|
import { FieldAddressProvider } from "./field-address.js";
|
|
7
|
+
import { datumClass } from "./datum.js";
|
|
7
8
|
import { SlotProvider } from "./slot.js";
|
|
8
9
|
export function FieldSlot(p) {
|
|
9
10
|
const { onChange, ...rest } = p;
|
|
@@ -25,9 +26,10 @@ export function FieldSlot(p) {
|
|
|
25
26
|
export function FieldCell({ field, value, height }) {
|
|
26
27
|
const r = resolveRenderer(field);
|
|
27
28
|
const inner = r?.Inline ? (_jsx(r.Inline, { field: field, value: value, height: height })) : (_jsx("span", { className: "truncate", children: String(value ?? '') }));
|
|
29
|
+
const datum = datumClass(field);
|
|
28
30
|
if (!height)
|
|
29
|
-
return inner;
|
|
30
|
-
return (_jsx("span", { className:
|
|
31
|
+
return datum ? _jsx("span", { className: datum, children: inner }) : inner;
|
|
32
|
+
return (_jsx("span", { className: `inline-flex min-w-0 items-center font-medium ${datum}`.trim(), style: { fontSize: Math.round(height * 0.7), lineHeight: 1.15 }, children: inner }));
|
|
31
33
|
}
|
|
32
34
|
export function fieldKeys(field, value, ctx) {
|
|
33
35
|
const r = resolveRenderer(field);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
2
|
import type { BoxGeom, FieldMode } from '../types.ts';
|
|
3
3
|
import type { FieldMeta, OptionItem } from '@coffer-org/sdk/fields';
|
|
4
4
|
export declare function focusInPopup(related: EventTarget | null): boolean;
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useRef } from 'react';
|
|
2
3
|
import { useFieldEdit, normalizeValue } from "./use-field-edit.js";
|
|
3
|
-
import { FieldLabel, FieldBox, FieldError,
|
|
4
|
+
import { FieldLabel, FieldBox, FieldError, SaveCancel, TriggerBox, ValueActions, ValueActionsOverlay, } from "./chrome.js";
|
|
4
5
|
import { useInFlow } from "./widgets/flow-row.js";
|
|
5
6
|
import { editorMode } from "./editor-mode.js";
|
|
7
|
+
import { activateMode } from "./activate.js";
|
|
6
8
|
import { useFieldAddress } from "./field-address.js";
|
|
7
9
|
import { useHybridScope } from "./hybrid-scope.js";
|
|
8
10
|
import { useFieldWidth } from "./field-width.js";
|
|
9
11
|
import { PopoverField } from "./popover-field.js";
|
|
10
12
|
import { useSlotOptional, useSlotRender } from "./slot.js";
|
|
13
|
+
import { datumClass } from "./datum.js";
|
|
11
14
|
const POPUP_SEL = '[data-slot="combobox-content"],[data-slot="popover-content"],[role="listbox"],[role="dialog"],[role="menu"],[data-scope="date-picker"][data-part="content"]';
|
|
12
15
|
export function focusInPopup(related) {
|
|
13
16
|
const el = related instanceof Element ? related : typeof document !== 'undefined' ? document.activeElement : null;
|
|
@@ -27,6 +30,7 @@ function FieldFrameBody(props) {
|
|
|
27
30
|
const address = useFieldAddress();
|
|
28
31
|
const onRecordPage = useHybridScope();
|
|
29
32
|
const width = useFieldWidth() ?? ownWidth;
|
|
33
|
+
const valueRef = useRef(null);
|
|
30
34
|
const emphasis = field.hints?.['emphasis'];
|
|
31
35
|
const noLabel = field.hints?.['noLabel'] === true || label === undefined;
|
|
32
36
|
const display = (val, commit) => {
|
|
@@ -59,20 +63,27 @@ function FieldFrameBody(props) {
|
|
|
59
63
|
fe.cancel();
|
|
60
64
|
}
|
|
61
65
|
}, children: content(true, fe.draft, fe.setDraft) }));
|
|
62
|
-
const
|
|
66
|
+
const activatable = Boolean(write) && activateMode(field) === 'edit';
|
|
67
|
+
const valueActivated = Boolean(write) && activateMode(field) === 'value';
|
|
68
|
+
const wrapValueText = (node) => valueActivated ? (_jsx("span", { ref: valueRef, className: "contents", children: node })) : (node);
|
|
69
|
+
const getValueText = () => valueRef.current?.textContent ?? '';
|
|
70
|
+
const closedContent = (_jsx(FieldBox, { geom: geom, field: field, children: content(false, value, () => { }, write) }));
|
|
71
|
+
const viewLayer = activatable ? (_jsx(TriggerBox, { label: label, expanded: fe.editing, onOpen: fe.beginEdit, children: closedContent })) : valueActivated && !noLabel ? (_jsx(ValueActionsOverlay, { onEdit: fe.beginEdit, children: closedContent })) : (wrapValueText(closedContent));
|
|
63
72
|
const zone = inFlow ? (_jsxs("div", { className: "grid min-w-0", children: [_jsx("div", { className: "col-start-1 row-start-1 min-w-0", "aria-hidden": !fe.editing, inert: !fe.editing ? true : undefined, style: { visibility: fe.editing ? 'visible' : 'hidden' }, children: editLayer }), _jsx("div", { className: "col-start-1 row-start-1 min-w-0", style: { visibility: fe.editing ? 'hidden' : 'visible' }, children: viewLayer })] })) : fe.editing ? (editLayer) : (viewLayer);
|
|
64
|
-
const pencilOverlay = write && !fe.editing ? _jsx(EditToggle, { variant: "overlay", onEdit: fe.beginEdit }) : null;
|
|
65
73
|
const editControls = write && fe.editing ? (_jsx("div", { className: "absolute right-1 top-0 z-10 flex gap-1", children: _jsx(SaveCancel, { size: "xs", busy: fe.busy, onSave: () => fe.commit(), onCancel: fe.cancel }) })) : null;
|
|
66
74
|
if (emphasis === 'hero') {
|
|
67
|
-
const heroAction = write
|
|
68
|
-
|
|
75
|
+
const heroAction = write && fe.editing ? (_jsx(SaveCancel, { size: "sm", busy: fe.busy, onSave: () => fe.commit(), onCancel: fe.cancel })) : valueActivated ? (_jsx(ValueActions, { getText: getValueText, onEdit: fe.beginEdit })) : null;
|
|
76
|
+
const heroContent = wrapValueText(content(false, value, () => { }, write));
|
|
77
|
+
return (_jsxs("div", { className: "group/field flex items-center gap-2 min-w-0", children: [_jsx("div", { className: `text-lg font-semibold text-text min-w-0 ${fe.editing ? '' : datumClass(field)}`.trim(), children: fe.editing ? (editLayer) : activatable ? (_jsx(TriggerBox, { label: label, expanded: fe.editing, onOpen: fe.beginEdit, children: heroContent })) : (heroContent) }), heroAction, _jsx(FieldError, { error: slot?.error ?? error })] }));
|
|
69
78
|
}
|
|
70
79
|
if (emphasis === 'muted') {
|
|
71
|
-
const mutedAction = write
|
|
72
|
-
|
|
80
|
+
const mutedAction = write && fe.editing ? (_jsx(SaveCancel, { size: "xs", busy: fe.busy, onSave: () => fe.commit(), onCancel: fe.cancel })) : valueActivated ? (_jsx(ValueActions, { getText: getValueText, onEdit: fe.beginEdit })) : null;
|
|
81
|
+
const mutedContent = wrapValueText(content(false, value, () => { }, write));
|
|
82
|
+
return (_jsxs("div", { className: "group/field grid grid-cols-[110px_1fr] items-center gap-x-3 text-xs text-muted min-w-0", children: [_jsxs("span", { className: "flex items-center gap-1.5", children: [label, mutedAction] }), _jsx("span", { className: `min-w-0 ${fe.editing ? '' : datumClass(field)}`.trim(), children: fe.editing ? (editLayer) : activatable ? (_jsx(TriggerBox, { label: label, expanded: fe.editing, onOpen: fe.beginEdit, children: mutedContent })) : (mutedContent) }), _jsx(FieldError, { error: slot?.error ?? error })] }));
|
|
73
83
|
}
|
|
74
84
|
if (noLabel) {
|
|
75
|
-
|
|
85
|
+
const valueActions = valueActivated && !fe.editing ? (_jsx("div", { className: "absolute right-1 top-1/2 z-10 -translate-y-1/2", children: _jsx(ValueActions, { getText: getValueText, onEdit: fe.beginEdit }) })) : null;
|
|
86
|
+
return (_jsxs("div", { className: "group/field relative min-w-0 pr-16", children: [zone, editControls, valueActions, _jsx(FieldError, { error: slot?.error ?? error })] }));
|
|
76
87
|
}
|
|
77
|
-
return (_jsxs("div", { className: `group/field relative ${width}`, children: [_jsx(FieldLabel, { label: label, required: required, reserveAction: Boolean(write) }), zone,
|
|
88
|
+
return (_jsxs("div", { className: `group/field relative ${width}`, children: [_jsx(FieldLabel, { label: label, required: required, reserveAction: Boolean(write) }), zone, editControls, _jsx(FieldError, { error: slot?.error ?? error })] }));
|
|
78
89
|
}
|
|
@@ -1,17 +1,46 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { marked } from 'marked';
|
|
3
3
|
import DOMPurify from 'dompurify';
|
|
4
|
+
import { useInRouterContext, useNavigate } from 'react-router-dom';
|
|
5
|
+
import { classifySiteHref } from "../internal-link.js";
|
|
6
|
+
const INTERNAL_ATTR = 'data-site-link';
|
|
4
7
|
DOMPurify.addHook('afterSanitizeAttributes', (node) => {
|
|
5
|
-
if (node.tagName
|
|
6
|
-
|
|
7
|
-
|
|
8
|
+
if (node.tagName !== 'A')
|
|
9
|
+
return;
|
|
10
|
+
const kind = classifySiteHref(node.getAttribute('href') ?? '');
|
|
11
|
+
if (kind.kind === 'internal') {
|
|
12
|
+
node.setAttribute('href', kind.to);
|
|
13
|
+
node.setAttribute(INTERNAL_ATTR, '');
|
|
14
|
+
node.removeAttribute('target');
|
|
15
|
+
node.removeAttribute('rel');
|
|
16
|
+
return;
|
|
8
17
|
}
|
|
18
|
+
node.setAttribute('target', '_blank');
|
|
19
|
+
node.setAttribute('rel', 'noopener noreferrer');
|
|
9
20
|
});
|
|
10
21
|
export function markdownToSafeHtml(src) {
|
|
11
22
|
const raw = marked.parse(src, { async: false });
|
|
12
23
|
return DOMPurify.sanitize(raw);
|
|
13
24
|
}
|
|
25
|
+
function Prose({ value, onClick }) {
|
|
26
|
+
return (_jsx("div", { className: "md-static", onClick: onClick, dangerouslySetInnerHTML: { __html: markdownToSafeHtml(value) } }));
|
|
27
|
+
}
|
|
28
|
+
function RoutedProse({ value }) {
|
|
29
|
+
const navigate = useNavigate();
|
|
30
|
+
return (_jsx(Prose, { value: value, onClick: (event) => {
|
|
31
|
+
const anchor = event.target.closest?.(`a[${INTERNAL_ATTR}]`);
|
|
32
|
+
if (!anchor)
|
|
33
|
+
return;
|
|
34
|
+
if (event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey)
|
|
35
|
+
return;
|
|
36
|
+
const to = anchor.getAttribute('href');
|
|
37
|
+
if (!to)
|
|
38
|
+
return;
|
|
39
|
+
event.preventDefault();
|
|
40
|
+
navigate(to);
|
|
41
|
+
} }));
|
|
42
|
+
}
|
|
14
43
|
export function MarkdownView({ value }) {
|
|
15
|
-
return _jsx(
|
|
44
|
+
return useInRouterContext() ? _jsx(RoutedProse, { value: value }) : _jsx(Prose, { value: value });
|
|
16
45
|
}
|
|
17
46
|
export default MarkdownView;
|
|
@@ -9,11 +9,12 @@ import { Dialog, DialogBody, DialogContent, DialogHeader, DialogTitle } from "..
|
|
|
9
9
|
import { Button } from "../components/ui/button.js";
|
|
10
10
|
import { Plus } from 'lucide-react';
|
|
11
11
|
import { useMinWidth } from "../hooks/use-media-query.js";
|
|
12
|
-
import { FieldLabel, FieldError, FieldBox } from "./chrome.js";
|
|
12
|
+
import { FieldLabel, FieldError, FieldBox, TriggerBox, ValueActionsOverlay } from "./chrome.js";
|
|
13
13
|
import { makeIssueText } from "./field-errors.js";
|
|
14
14
|
import { normalizeValue } from "./use-field-edit.js";
|
|
15
15
|
import { focusInPopup } from "./field-frame.js";
|
|
16
16
|
import { editorMode } from "./editor-mode.js";
|
|
17
|
+
import { activateMode } from "./activate.js";
|
|
17
18
|
import { useRegisterPicker } from "./picker-scrim.js";
|
|
18
19
|
import { useFieldAddress } from "./field-address.js";
|
|
19
20
|
import { PickerSurface } from "./picker-surface.js";
|
|
@@ -23,7 +24,7 @@ import { hasValue } from "./shared.js";
|
|
|
23
24
|
import { parseJSON } from "../lib/json.js";
|
|
24
25
|
import { makeWriter } from "./write.js";
|
|
25
26
|
import { SlotProvider } from "./slot.js";
|
|
26
|
-
function Picker({ field, validateWith, trigger, seed, options, listMultiple, pickDecides, showInput, size, label, edit, build, onCommit, onDelete, removeLabel, }) {
|
|
27
|
+
function Picker({ field, validateWith, trigger, valueActivated, seed, options, listMultiple, pickDecides, showInput, size, label, edit, build, onCommit, onDelete, removeLabel, }) {
|
|
27
28
|
const { t } = useTranslation();
|
|
28
29
|
const wide = useMinWidth('sm', 640);
|
|
29
30
|
const [open, setOpen] = useState(false);
|
|
@@ -72,26 +73,15 @@ function Picker({ field, validateWith, trigger, seed, options, listMultiple, pic
|
|
|
72
73
|
edit(normalizeValue(field, draft), setDraft), options && (_jsx(OptionList, { field: field, options: options, value: draft, multiple: listMultiple, onPick: pick }))] })) }), _jsx(FieldError, { error: localError }), !Custom && (showInput || onDelete) && (_jsxs("div", { className: "mt-2 flex items-center justify-end gap-2 border-t border-border pt-2", children: [onDelete && (_jsx(Button, { type: "button", variant: "destructiveOutline", size: "sm", className: "mr-auto", disabled: busy, onClick: () => void write(onDelete()), children: removeLabel })), showInput && (_jsx(Button, { type: "button", size: "sm", disabled: busy, onClick: () => void write(build(draft)), children: t('actions.save') }))] }))] }));
|
|
73
74
|
const asWindow = size === 'modal' || !wide;
|
|
74
75
|
const panelWidth = size === 'large' ? 'w-[min(32rem,calc(100vw-2rem))]' : 'w-72';
|
|
75
|
-
const
|
|
76
|
+
const anchor = (_jsx(PopoverAnchor, { asChild: true, children: _jsx("span", { "aria-hidden": true, className: `pointer-events-none absolute inset-y-0 left-0 max-w-full ${panelWidth}` }) }));
|
|
77
|
+
const valueDisplay = (children) => (_jsxs(ValueActionsOverlay, { onEdit: start, children: [trigger, children] }));
|
|
78
|
+
const anchored = (_jsxs(Popover, { open: open, onOpenChange: (o) => (o ? start() : cancel()), children: [valueActivated ? (valueDisplay(anchor)) : (_jsxs(TriggerBox, { expanded: open, label: label, onOpen: start, haspopup: "dialog", children: [trigger, anchor] })), _jsxs(PopoverContent, { align: "start", className: `${panelWidth} p-2`, onInteractOutside: (e) => {
|
|
76
79
|
if (focusInPopup(e.target))
|
|
77
80
|
e.preventDefault();
|
|
78
81
|
}, onEscapeKeyDown: cancel, children: [_jsx(PopoverArrow, {}), body] })] }));
|
|
79
82
|
if (!asWindow)
|
|
80
83
|
return anchored;
|
|
81
|
-
return (_jsxs(_Fragment, { children: [_jsx(TriggerBox, {
|
|
82
|
-
}
|
|
83
|
-
function TriggerBox({ open, label, onOpen, children, ...rest }) {
|
|
84
|
-
return (_jsx("div", { ...rest, role: "button", tabIndex: 0, "aria-haspopup": "dialog", "aria-expanded": open, "aria-label": label, className: "relative min-w-0 cursor-pointer rounded-lg border border-transparent focus-visible:border-accent focus-visible:outline-none", onClick: (e) => {
|
|
85
|
-
const inner = e.target.closest('a,button,[role="button"],audio,video,input,label');
|
|
86
|
-
if (inner && inner !== e.currentTarget)
|
|
87
|
-
return;
|
|
88
|
-
onOpen();
|
|
89
|
-
}, onKeyDown: (e) => {
|
|
90
|
-
if (e.key === 'Enter' || e.key === ' ') {
|
|
91
|
-
e.preventDefault();
|
|
92
|
-
onOpen();
|
|
93
|
-
}
|
|
94
|
-
}, children: children }));
|
|
84
|
+
return (_jsxs(_Fragment, { children: [valueActivated ? (valueDisplay()) : (_jsx(TriggerBox, { expanded: open, label: label, onOpen: start, haspopup: "dialog", children: trigger })), _jsx(Dialog, { open: open, onOpenChange: (o) => (o ? start() : cancel()), children: _jsxs(DialogContent, { onEscapeKeyDown: cancel, className: "h-[88dvh] md:h-[min(80dvh,52rem)] md:max-w-5xl md:overflow-hidden", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: label ?? '' }) }), _jsx(DialogBody, { children: body })] }) })] }));
|
|
95
85
|
}
|
|
96
86
|
export function PopoverField({ field, value, label, required, error, geom, view, edit, pickerOptions, onCommit, width = 'w-64', }) {
|
|
97
87
|
const { t } = useTranslation();
|
|
@@ -107,6 +97,7 @@ export function PopoverField({ field, value, label, required, error, geom, view,
|
|
|
107
97
|
const showInput = !(options && field.strict);
|
|
108
98
|
const Value = R?.Value;
|
|
109
99
|
const perValue = multi && Value;
|
|
100
|
+
const valueActivated = activateMode(field) === 'value';
|
|
110
101
|
const address = useFieldAddress();
|
|
111
102
|
const scalarField = useMemo(() => ({ ...field, hints: { ...field.hints, multiple: false }, json: undefined }), [field]);
|
|
112
103
|
const freeField = useMemo(() => ({ ...field, options: undefined, hints: { ...field.hints, source: null, suggestions: undefined } }), [field]);
|
|
@@ -146,8 +137,8 @@ export function PopoverField({ field, value, label, required, error, geom, view,
|
|
|
146
137
|
label,
|
|
147
138
|
removeLabel: t('actions.delete'),
|
|
148
139
|
};
|
|
149
|
-
return shell(_jsxs("div", { className: "flex min-w-0 flex-wrap items-center gap-1.5", children: [items.map((item, idx) => (_createElement(Picker, { ...one, key: `${String(item)}-${idx}`, validateWith: field, seed: item, build: (d) => at(idx, d), onDelete: () => without(idx), onCommit: onCommit, trigger: _jsx("span", { className: "inline-flex min-w-0 items-center gap-1.5 rounded-full border border-border px-2 py-0.5 text-sm", children: _jsx(Value, { field: field, value: item }) }) }))), _jsx(Picker, { ...one, validateWith: field, seed: "", build: (d) => [...items, d], onCommit: onCommit, trigger: _jsx("span", { className: "inline-flex items-center gap-1 rounded-full border border-dashed border-border px-2 py-0.5 text-sm text-muted", title: t('actions.add'), children: _jsx(Plus, { size: 14 }) }) })] }));
|
|
140
|
+
return shell(_jsxs("div", { className: "flex min-w-0 flex-wrap items-center gap-1.5", children: [items.map((item, idx) => (_createElement(Picker, { ...one, key: `${String(item)}-${idx}`, validateWith: field, valueActivated: valueActivated, seed: item, build: (d) => at(idx, d), onDelete: () => without(idx), onCommit: onCommit, trigger: _jsx("span", { className: "inline-flex min-w-0 items-center gap-1.5 rounded-full border border-border px-2 py-0.5 text-sm", children: _jsx(Value, { field: field, value: item }) }) }))), _jsx(Picker, { ...one, validateWith: field, valueActivated: false, seed: "", build: (d) => [...items, d], onCommit: onCommit, trigger: _jsx("span", { className: "inline-flex items-center gap-1 rounded-full border border-dashed border-border px-2 py-0.5 text-sm text-muted", title: t('actions.add'), children: _jsx(Plus, { size: 14 }) }) })] }));
|
|
150
141
|
}
|
|
151
142
|
const clearable = !multi && !field.required && !required && hasValue(value);
|
|
152
|
-
return shell(_jsx(Picker, { field: field, label: label, size: size, options: options, listMultiple: multi, pickDecides: !multi, showInput: showInput, seed: value, edit: inputEdit, build: (d) => d, onCommit: onCommit, onDelete: clearable ? () => '' : undefined, removeLabel: t('actions.clear'), trigger: _jsx(FieldBox, { geom: geom, field: field, children: triggerDisplay(value, (v) => void onCommit(v)) }) }));
|
|
143
|
+
return shell(_jsx(Picker, { field: field, valueActivated: valueActivated, label: label, size: size, options: options, listMultiple: multi, pickDecides: !multi, showInput: showInput, seed: value, edit: inputEdit, build: (d) => d, onCommit: onCommit, onDelete: clearable ? () => '' : undefined, removeLabel: t('actions.clear'), trigger: _jsx(FieldBox, { geom: geom, field: field, children: triggerDisplay(value, (v) => void onCommit(v)) }) }));
|
|
153
144
|
}
|
|
@@ -9,6 +9,7 @@ import { getShelf } from "../../schema-registry.js";
|
|
|
9
9
|
import { RecordInline } from "../../components/RecordInline.js";
|
|
10
10
|
import { Dash, hasValue, shortId, CheckLegend, colsToCheckRow } from "../shared.js";
|
|
11
11
|
import { FieldSlot } from "../dispatch.js";
|
|
12
|
+
import { CHROME_LABEL_CLS } from "../chrome.js";
|
|
12
13
|
import { FlowRow } from "./flow-row.js";
|
|
13
14
|
import { ChecklistRow } from "./checklist-row.js";
|
|
14
15
|
import { ChecklistContent, clientChecklistContentFields } from "./checklist-content.js";
|
|
@@ -18,7 +19,7 @@ export function KeyValueList({ value, option, recordCtx = { library: '', shelf:
|
|
|
18
19
|
return _jsx(Dash, {});
|
|
19
20
|
return (_jsx(FlowRow, { items: list.map((p, i) => ({
|
|
20
21
|
key: String(i),
|
|
21
|
-
node: (_jsxs("div", { children: [_jsx("dt", { className:
|
|
22
|
+
node: (_jsxs("div", { children: [_jsx("dt", { className: CHROME_LABEL_CLS, children: p.key }), _jsx("dd", { className: "text-sm text-text break-all", children: _jsx(FieldSlot, { bare: true, field: option, value: p.value, mode: "view", recordCtx: recordCtx }) })] })),
|
|
22
23
|
})) }));
|
|
23
24
|
}
|
|
24
25
|
export function CheckDisplay({ value, slots = [], fields, recordCtx = { library: '', shelf: '', recordId: null }, }) {
|