@declarion/react 0.4.7 → 0.6.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-lib/{DraggableChildrenTable-DjSEK6XF.js → DraggableChildrenTable-DhcSXQfV.js} +55 -55
- package/dist-lib/{DraggableChildrenTable-DjSEK6XF.js.map → DraggableChildrenTable-DhcSXQfV.js.map} +1 -1
- package/dist-lib/{Grid-D8gOvkf3.js → Grid-Cwxca9aO.js} +2 -2
- package/dist-lib/{Grid-D8gOvkf3.js.map → Grid-Cwxca9aO.js.map} +1 -1
- package/dist-lib/{MarkdownRenderer-Dvo_0Zcx.js → MarkdownRenderer-Dbl3AFDQ.js} +36 -11
- package/dist-lib/MarkdownRenderer-Dbl3AFDQ.js.map +1 -0
- package/dist-lib/app.d.ts +1 -1
- package/dist-lib/components/agents/AgentChatPanel.d.ts +1 -1
- package/dist-lib/components/agents/AgentConversationDrawer.d.ts +1 -1
- package/dist-lib/components/agents/AgentElicitForm.d.ts +1 -1
- package/dist-lib/components/columns/ColumnPicker.d.ts +1 -1
- package/dist-lib/components/detail-layout/AssociatedBlock.d.ts +1 -1
- package/dist-lib/components/detail-layout/AssociatedToolbar.d.ts +1 -1
- package/dist-lib/components/detail-layout/ChildrenBlock.d.ts +1 -1
- package/dist-lib/components/detail-layout/ChildrenCards.d.ts +1 -1
- package/dist-lib/components/detail-layout/ChildrenList.d.ts +1 -1
- package/dist-lib/components/detail-layout/ChildrenTable.d.ts +1 -1
- package/dist-lib/components/detail-layout/ChildrenTimeline.d.ts +1 -1
- package/dist-lib/components/detail-layout/DetailAuditStrip.d.ts +1 -1
- package/dist-lib/components/detail-layout/DetailHighlights.d.ts +1 -1
- package/dist-lib/components/detail-layout/DraggableChildrenTable.d.ts +1 -1
- package/dist-lib/components/detail-layout/EditableChildrenTable.d.ts +1 -1
- package/dist-lib/components/detail-layout/ErrorSummary.d.ts +1 -1
- package/dist-lib/components/detail-layout/FieldRow.d.ts +2 -2
- package/dist-lib/components/detail-layout/FieldsBlock.d.ts +1 -1
- package/dist-lib/components/detail-layout/LayoutColumn.d.ts +1 -1
- package/dist-lib/components/detail-layout/LayoutRenderer.d.ts +2 -2
- package/dist-lib/components/detail-layout/LayoutRow.d.ts +1 -1
- package/dist-lib/components/detail-layout/LayoutTabs.d.ts +1 -1
- package/dist-lib/components/detail-layout/PropertiesBlock.d.ts +1 -1
- package/dist-lib/components/detail-layout/QuickAddForm.d.ts +1 -1
- package/dist-lib/components/detail-layout/SectionBlock.d.ts +1 -1
- package/dist-lib/components/detail-layout/StatusesBlock.d.ts +1 -1
- package/dist-lib/components/detail-layout/TimelineComposer.d.ts +1 -1
- package/dist-lib/components/fields/BoolField.d.ts +1 -1
- package/dist-lib/components/fields/EmailField.d.ts +1 -1
- package/dist-lib/components/fields/EnumField.d.ts +1 -1
- package/dist-lib/components/fields/IntArrayField.d.ts +1 -1
- package/dist-lib/components/fields/JsonEditor.d.ts +1 -1
- package/dist-lib/components/fields/JsonField.d.ts +1 -1
- package/dist-lib/components/fields/MultilangField.d.ts +1 -1
- package/dist-lib/components/fields/MultilangTextField.d.ts +1 -1
- package/dist-lib/components/fields/NumberField.d.ts +1 -1
- package/dist-lib/components/fields/PasswordField.d.ts +1 -1
- package/dist-lib/components/fields/PhoneField.d.ts +1 -1
- package/dist-lib/components/fields/RefField.d.ts +1 -1
- package/dist-lib/components/fields/RichTextField.d.ts +1 -1
- package/dist-lib/components/fields/SecretField.d.ts +1 -1
- package/dist-lib/components/fields/StringArrayField.d.ts +1 -1
- package/dist-lib/components/fields/StringField.d.ts +1 -1
- package/dist-lib/components/fields/StructureField.d.ts +1 -1
- package/dist-lib/components/fields/TextField.d.ts +1 -1
- package/dist-lib/components/fields/TimestampField.d.ts +1 -1
- package/dist-lib/components/fields/UrlField.d.ts +1 -1
- package/dist-lib/components/fields/cells/registry.d.ts +0 -1
- package/dist-lib/components/fields/index.d.ts +0 -1
- package/dist-lib/components/file-widgets/CircleImage.d.ts +1 -1
- package/dist-lib/components/file-widgets/DocumentCard.d.ts +1 -1
- package/dist-lib/components/file-widgets/FileList.d.ts +1 -1
- package/dist-lib/components/file-widgets/Grid.d.ts +1 -1
- package/dist-lib/components/file-widgets/GridLazy.d.ts +1 -1
- package/dist-lib/components/file-widgets/ImageWidget.d.ts +1 -1
- package/dist-lib/components/file-widgets/LabeledSlots.d.ts +1 -1
- package/dist-lib/components/file-widgets/SquareImage.d.ts +1 -1
- package/dist-lib/components/file-widgets/WideBanner.d.ts +1 -1
- package/dist-lib/components/file-widgets/draft-scope.d.ts +1 -1
- package/dist-lib/components/file-widgets/host.d.ts +0 -1
- package/dist-lib/components/filters/FilterChips.d.ts +1 -1
- package/dist-lib/components/filters/FilterDrawer.d.ts +1 -1
- package/dist-lib/components/layout/DirtyTabsCloseDialog.d.ts +1 -1
- package/dist-lib/components/layout/ImpersonationBanner.d.ts +1 -1
- package/dist-lib/components/layout/ImpersonationFrame.d.ts +1 -1
- package/dist-lib/components/layout/ImpersonationStartModal.d.ts +1 -1
- package/dist-lib/components/layout/Layout.d.ts +1 -1
- package/dist-lib/components/layout/Rail.d.ts +1 -1
- package/dist-lib/components/layout/Sidebar.d.ts +1 -1
- package/dist-lib/components/layout/TabStrip.d.ts +1 -1
- package/dist-lib/components/layout/TenantChip.d.ts +1 -1
- package/dist-lib/components/layout/TopBar.d.ts +1 -1
- package/dist-lib/components/list/BulkBar.d.ts +1 -1
- package/dist-lib/components/list/InlineEditCell.d.ts +1 -1
- package/dist-lib/components/list/ListPage.d.ts +1 -1
- package/dist-lib/components/list/PeekFieldCell.d.ts +1 -1
- package/dist-lib/components/list/QuickPeek.d.ts +2 -2
- package/dist-lib/components/list/RowMenu.d.ts +1 -1
- package/dist-lib/components/mass-edit/PropertyGridEditor.d.ts +1 -1
- package/dist-lib/components/pages/AdminCatalogPage.d.ts +1 -1
- package/dist-lib/components/pages/ExportMenu.d.ts +1 -1
- package/dist-lib/components/pages/NotFoundPage.d.ts +1 -1
- package/dist-lib/components/pages/SSOCallbackPage.d.ts +1 -1
- package/dist-lib/components/pages/SmartDetailPage.d.ts +1 -1
- package/dist-lib/components/pages/SmartListPage.d.ts +1 -1
- package/dist-lib/components/pages/SmartRecordListPage.d.ts +1 -1
- package/dist-lib/components/pages/auth/AuthShell.d.ts +1 -1
- package/dist-lib/components/pages/auth/Forgot.d.ts +1 -1
- package/dist-lib/components/pages/auth/Reset.d.ts +1 -1
- package/dist-lib/components/pages/auth/SignIn.d.ts +1 -1
- package/dist-lib/components/pages/auth/SignUp.d.ts +1 -1
- package/dist-lib/components/pages/auth/TwoFA.d.ts +1 -1
- package/dist-lib/components/pages/auth/icons.d.ts +2 -2
- package/dist-lib/components/pages/home/HomeStub.d.ts +1 -1
- package/dist-lib/components/pages/jobs/Jobs.d.ts +1 -1
- package/dist-lib/components/pages/list/DataCell.d.ts +1 -1
- package/dist-lib/components/pages/list/ListBulkBar.d.ts +1 -1
- package/dist-lib/components/pages/list/ListHeader.d.ts +1 -1
- package/dist-lib/components/pages/list/RowActionsCell.d.ts +1 -1
- package/dist-lib/components/pages/list/SaveViewDialog.d.ts +1 -1
- package/dist-lib/components/pages/pipeline/Pipeline.d.ts +1 -1
- package/dist-lib/components/pages/profile/Preferences.d.ts +1 -1
- package/dist-lib/components/pages/profile/Profile.d.ts +1 -1
- package/dist-lib/components/primitives/Avatar.d.ts +1 -1
- package/dist-lib/components/primitives/BoolToggle.d.ts +1 -1
- package/dist-lib/components/primitives/BrandGlyph.d.ts +1 -1
- package/dist-lib/components/primitives/BrandLogo.d.ts +1 -1
- package/dist-lib/components/primitives/Check.d.ts +1 -1
- package/dist-lib/components/primitives/Chip.d.ts +1 -1
- package/dist-lib/components/primitives/ClassifiedFieldLocked.d.ts +1 -1
- package/dist-lib/components/primitives/Fields.d.ts +11 -11
- package/dist-lib/components/primitives/HoverCard.d.ts +1 -1
- package/dist-lib/components/primitives/Kbd.d.ts +1 -1
- package/dist-lib/components/primitives/MenuItem.d.ts +1 -1
- package/dist-lib/components/primitives/Pill.d.ts +1 -1
- package/dist-lib/components/primitives/RecordIdBadge.d.ts +1 -1
- package/dist-lib/components/primitives/RowField.d.ts +1 -1
- package/dist-lib/components/primitives/SectionCard.d.ts +1 -1
- package/dist-lib/components/primitives/Spinner.d.ts +1 -1
- package/dist-lib/components/shared/ActionButton.d.ts +1 -1
- package/dist-lib/components/shared/ActionConfirmDialog.d.ts +1 -1
- package/dist-lib/components/shared/ActionDialog.d.ts +1 -1
- package/dist-lib/components/shared/ActionResultDialog.d.ts +1 -1
- package/dist-lib/components/shared/ActionRunner.d.ts +1 -1
- package/dist-lib/components/shared/AssociationTabs.d.ts +1 -1
- package/dist-lib/components/shared/AuditMetaStrip.d.ts +1 -1
- package/dist-lib/components/shared/Breadcrumbs.d.ts +1 -1
- package/dist-lib/components/shared/ChangePasswordDialog.d.ts +1 -1
- package/dist-lib/components/shared/ConfirmDialog.d.ts +1 -1
- package/dist-lib/components/shared/CopyButton.d.ts +1 -1
- package/dist-lib/components/shared/DynamicIcon.d.ts +1 -1
- package/dist-lib/components/shared/EmptyState.d.ts +1 -1
- package/dist-lib/components/shared/ErrorBoundary.d.ts +1 -1
- package/dist-lib/components/shared/HighlightsStrip.d.ts +1 -1
- package/dist-lib/components/shared/LoadingSkeleton.d.ts +2 -2
- package/dist-lib/components/shared/Markdown.d.ts +1 -1
- package/dist-lib/components/shared/MarkdownRenderer.d.ts +1 -1
- package/dist-lib/components/shared/PageHeader.d.ts +1 -1
- package/dist-lib/components/shared/RecordHeader.d.ts +1 -1
- package/dist-lib/components/shared/ResponsiveActionBar.d.ts +1 -1
- package/dist-lib/components/shared/RowActionsKebab.d.ts +1 -1
- package/dist-lib/components/shared/StatusGroupsDisplay.d.ts +1 -1
- package/dist-lib/components/shared/StatusStepper.d.ts +1 -1
- package/dist-lib/components/shared/toast/DeclarionToast.d.ts +1 -1
- package/dist-lib/components/shell/CommandPalette.d.ts +1 -1
- package/dist-lib/components/views/ViewSwitcher.d.ts +1 -1
- package/dist-lib/{dataTableStyles-eleWR-pg.js → dataTableStyles-BwnbXviH.js} +1559 -1589
- package/dist-lib/dataTableStyles-BwnbXviH.js.map +1 -0
- package/dist-lib/embed/EmbedAuthBootstrap.d.ts +1 -1
- package/dist-lib/embed/EmbedContext.d.ts +1 -1
- package/dist-lib/embed/EmbedUnsavedGuard.d.ts +1 -1
- package/dist-lib/embed/index.d.ts +5 -3
- package/dist-lib/embed/protocol.d.ts +30 -3
- package/dist-lib/embed/screen-location.d.ts +38 -8
- package/dist-lib/embed/useEmbedNavigate.d.ts +9 -0
- package/dist-lib/embed/useGlobalLinkInterceptor.d.ts +66 -0
- package/dist-lib/index.js +5143 -5025
- package/dist-lib/index.js.map +1 -1
- package/dist-lib/{value-CiwnEAde.js → value-CrpaObP_.js} +2 -1
- package/dist-lib/value-CrpaObP_.js.map +1 -0
- package/package.json +14 -14
- package/dist-lib/MarkdownRenderer-Dvo_0Zcx.js.map +0 -1
- package/dist-lib/dataTableStyles-eleWR-pg.js.map +0 -1
- package/dist-lib/value-CiwnEAde.js.map +0 -1
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Xt as e, Yt as t, Zt as n, a as r, i, n as a, r as o, t as s, ut as c } from "./dataTableStyles-BwnbXviH.js";
|
|
2
2
|
import { useCallback as l, useMemo as u, useRef as d } from "react";
|
|
3
3
|
import { jsx as f, jsxs as p } from "react/jsx-runtime";
|
|
4
4
|
import { DndContext as m, PointerSensor as h, closestCenter as g, useSensor as _, useSensors as v } from "@dnd-kit/core";
|
|
5
5
|
import { SortableContext as y, useSortable as b, verticalListSortingStrategy as x } from "@dnd-kit/sortable";
|
|
6
6
|
import { CSS as S } from "@dnd-kit/utilities";
|
|
7
7
|
//#region src/components/detail-layout/DraggableChildrenTable.tsx
|
|
8
|
-
function C({ config:
|
|
9
|
-
let T = d(null), E =
|
|
8
|
+
function C({ config: t, childEntity: r, schema: i, rows: s, onChange: b, validationErrors: S, refs: C }) {
|
|
9
|
+
let T = d(null), E = t.position_field, D = t.columns, O = v(_(h, { activationConstraint: { distance: 5 } })), k = new Set(Array.isArray(t.foreign_key) ? t.foreign_key : [t.foreign_key]), A = D.filter((e) => {
|
|
10
10
|
if (k.has(e) || e === E) return !1;
|
|
11
11
|
let t = r.fields.get(e);
|
|
12
12
|
return !(!t || t.auto || t.primary);
|
|
@@ -14,56 +14,56 @@ function C({ config: n, childEntity: r, schema: a, rows: c, onChange: b, validat
|
|
|
14
14
|
if (e === E) return !0;
|
|
15
15
|
let t = r.fields.get(e);
|
|
16
16
|
return t && (t.auto || t.primary);
|
|
17
|
-
})), M = D.filter((e) => e !== E), N = u(() =>
|
|
18
|
-
b(
|
|
17
|
+
})), M = D.filter((e) => e !== E), N = u(() => s.filter((e) => !e._deleted).map((e) => e._key), [s]), P = l((e, t, n) => {
|
|
18
|
+
b(s.map((r) => r._key === e ? {
|
|
19
19
|
...r,
|
|
20
20
|
[t]: n,
|
|
21
21
|
_dirty: !0
|
|
22
22
|
} : r));
|
|
23
|
-
}, [
|
|
24
|
-
let e =
|
|
23
|
+
}, [s, b]), F = l(() => {
|
|
24
|
+
let e = s.filter((e) => !e._deleted).reduce((e, t) => {
|
|
25
25
|
let n = typeof t[E] == "number" ? t[E] : -1;
|
|
26
26
|
return Math.max(e, n);
|
|
27
|
-
}, -1),
|
|
27
|
+
}, -1), n = {
|
|
28
28
|
_key: crypto.randomUUID(),
|
|
29
29
|
_dirty: !0,
|
|
30
30
|
[E]: e + 1
|
|
31
|
-
}, i =
|
|
31
|
+
}, i = t.quick_add?.defaults ?? {};
|
|
32
32
|
for (let e of A) {
|
|
33
|
-
let
|
|
34
|
-
|
|
33
|
+
let t = r.fields.get(e);
|
|
34
|
+
t && (i[e] === void 0 ? t.default === void 0 ? t.type === "bool" && (n[e] = !1) : n[e] = t.default : n[e] = i[e]);
|
|
35
35
|
}
|
|
36
|
-
b([...
|
|
36
|
+
b([...s, n]);
|
|
37
37
|
}, [
|
|
38
|
-
|
|
38
|
+
s,
|
|
39
39
|
b,
|
|
40
40
|
A,
|
|
41
41
|
r,
|
|
42
|
-
|
|
42
|
+
t.quick_add?.defaults,
|
|
43
43
|
E
|
|
44
44
|
]), I = l((e) => {
|
|
45
|
-
b(
|
|
45
|
+
b(s.map((t) => t._key === e ? {
|
|
46
46
|
...t,
|
|
47
47
|
_deleted: !0
|
|
48
48
|
} : t));
|
|
49
|
-
}, [
|
|
50
|
-
b(
|
|
49
|
+
}, [s, b]), L = l((e) => {
|
|
50
|
+
b(s.map((t) => t._key === e ? {
|
|
51
51
|
...t,
|
|
52
52
|
_deleted: !1
|
|
53
53
|
} : t));
|
|
54
|
-
}, [
|
|
54
|
+
}, [s, b]), R = l((e) => {
|
|
55
55
|
let { active: t, over: n } = e;
|
|
56
56
|
if (!n || t.id === n.id) return;
|
|
57
|
-
let r =
|
|
57
|
+
let r = s.filter((e) => !e._deleted), i = s.filter((e) => e._deleted), a = r.findIndex((e) => e._key === String(t.id)), o = r.findIndex((e) => e._key === String(n.id));
|
|
58
58
|
if (a === -1 || o === -1) return;
|
|
59
|
-
let
|
|
60
|
-
|
|
59
|
+
let c = [...r], [l] = c.splice(a, 1);
|
|
60
|
+
c.splice(o, 0, l), b([...c.map((e, t) => ({
|
|
61
61
|
...e,
|
|
62
62
|
[E]: t,
|
|
63
63
|
_dirty: e[E] === t ? e._dirty : !0
|
|
64
64
|
})), ...i]);
|
|
65
65
|
}, [
|
|
66
|
-
|
|
66
|
+
s,
|
|
67
67
|
b,
|
|
68
68
|
E
|
|
69
69
|
]), z = l((e) => {
|
|
@@ -98,7 +98,7 @@ function C({ config: n, childEntity: r, schema: a, rows: c, onChange: b, validat
|
|
|
98
98
|
}
|
|
99
99
|
});
|
|
100
100
|
} else e.key === "Escape" && t.blur?.();
|
|
101
|
-
}, [F]), B =
|
|
101
|
+
}, [F]), B = a;
|
|
102
102
|
return /* @__PURE__ */ p("div", { children: [/* @__PURE__ */ f("div", {
|
|
103
103
|
style: { overflowX: "auto" },
|
|
104
104
|
children: /* @__PURE__ */ f(m, {
|
|
@@ -107,7 +107,7 @@ function C({ config: n, childEntity: r, schema: a, rows: c, onChange: b, validat
|
|
|
107
107
|
onDragEnd: R,
|
|
108
108
|
children: /* @__PURE__ */ p("table", {
|
|
109
109
|
ref: T,
|
|
110
|
-
style:
|
|
110
|
+
style: o,
|
|
111
111
|
onKeyDown: z,
|
|
112
112
|
children: [/* @__PURE__ */ f("thead", { children: /* @__PURE__ */ p("tr", { children: [
|
|
113
113
|
/* @__PURE__ */ f("th", { style: {
|
|
@@ -119,7 +119,7 @@ function C({ config: n, childEntity: r, schema: a, rows: c, onChange: b, validat
|
|
|
119
119
|
let t = r.fields.get(e);
|
|
120
120
|
return /* @__PURE__ */ f("th", {
|
|
121
121
|
style: B,
|
|
122
|
-
children: t ?
|
|
122
|
+
children: t ? c(t, e, i.entities) : e
|
|
123
123
|
}, e);
|
|
124
124
|
}),
|
|
125
125
|
/* @__PURE__ */ f("th", { style: {
|
|
@@ -129,7 +129,7 @@ function C({ config: n, childEntity: r, schema: a, rows: c, onChange: b, validat
|
|
|
129
129
|
] }) }), /* @__PURE__ */ f(y, {
|
|
130
130
|
items: N,
|
|
131
131
|
strategy: x,
|
|
132
|
-
children: /* @__PURE__ */ f("tbody", { children:
|
|
132
|
+
children: /* @__PURE__ */ f("tbody", { children: s.map((e) => /* @__PURE__ */ f(w, {
|
|
133
133
|
row: e,
|
|
134
134
|
columns: M,
|
|
135
135
|
editableColumns: A,
|
|
@@ -146,25 +146,25 @@ function C({ config: n, childEntity: r, schema: a, rows: c, onChange: b, validat
|
|
|
146
146
|
})
|
|
147
147
|
}), /* @__PURE__ */ f("div", {
|
|
148
148
|
style: { marginTop: 8 },
|
|
149
|
-
children: /* @__PURE__ */ f(
|
|
149
|
+
children: /* @__PURE__ */ f(e, {
|
|
150
150
|
kind: "ghost",
|
|
151
151
|
size: "xs",
|
|
152
|
-
icon: /* @__PURE__ */ f(
|
|
152
|
+
icon: /* @__PURE__ */ f(n.plus, { size: 12 }),
|
|
153
153
|
onClick: F,
|
|
154
154
|
children: "Add row"
|
|
155
155
|
})
|
|
156
156
|
})] });
|
|
157
157
|
}
|
|
158
|
-
function w({ row:
|
|
159
|
-
let _ =
|
|
160
|
-
id:
|
|
158
|
+
function w({ row: e, columns: a, editableColumns: o, displayOnlyColumns: c, childEntity: l, validationErrors: u, refs: d, onCellChange: m, onDelete: h, onUndoDelete: g }) {
|
|
159
|
+
let _ = i(), v = !!e._deleted, y = u?.[e._key], { attributes: x, listeners: C, setNodeRef: w, transform: T, transition: E, isDragging: D } = b({
|
|
160
|
+
id: e._key,
|
|
161
161
|
disabled: v
|
|
162
162
|
}), O = {
|
|
163
163
|
transform: S.Translate.toString(T),
|
|
164
164
|
transition: E,
|
|
165
165
|
zIndex: D ? 10 : void 0,
|
|
166
166
|
opacity: D ? .5 : void 0
|
|
167
|
-
}, k =
|
|
167
|
+
}, k = s, A = {
|
|
168
168
|
padding: "4px 4px",
|
|
169
169
|
borderBottom: "1px solid var(--divider)",
|
|
170
170
|
verticalAlign: "middle"
|
|
@@ -195,44 +195,44 @@ function w({ row: t, columns: i, editableColumns: o, displayOnlyColumns: s, chil
|
|
|
195
195
|
},
|
|
196
196
|
...x,
|
|
197
197
|
...C,
|
|
198
|
-
children: /* @__PURE__ */ f(
|
|
198
|
+
children: /* @__PURE__ */ f(n.grid, { size: 14 })
|
|
199
199
|
})
|
|
200
200
|
}),
|
|
201
|
-
|
|
202
|
-
let n = l.fields.get(
|
|
203
|
-
return v || a || !
|
|
201
|
+
a.map((t) => {
|
|
202
|
+
let n = l.fields.get(t), i = y?.includes(t), a = c.has(t), s = o.includes(t);
|
|
203
|
+
return v || a || !s ? /* @__PURE__ */ f("td", {
|
|
204
204
|
style: {
|
|
205
205
|
...k,
|
|
206
206
|
textDecoration: v ? "line-through" : "none"
|
|
207
207
|
},
|
|
208
208
|
children: n ? r({
|
|
209
209
|
field: n,
|
|
210
|
-
fieldName:
|
|
211
|
-
value: t
|
|
210
|
+
fieldName: t,
|
|
211
|
+
value: e[t],
|
|
212
212
|
mode: "display",
|
|
213
|
-
record:
|
|
213
|
+
record: e,
|
|
214
214
|
refs: d,
|
|
215
215
|
locale: _
|
|
216
|
-
}) : String(t
|
|
217
|
-
},
|
|
216
|
+
}) : String(e[t] ?? "")
|
|
217
|
+
}, t) : /* @__PURE__ */ f("td", {
|
|
218
218
|
style: {
|
|
219
219
|
...A,
|
|
220
220
|
outline: i ? "1px solid var(--danger)" : "none",
|
|
221
221
|
outlineOffset: -1,
|
|
222
222
|
borderRadius: i ? 4 : 0
|
|
223
223
|
},
|
|
224
|
-
title: i ? `${
|
|
224
|
+
title: i ? `${t} is required` : void 0,
|
|
225
225
|
children: n ? r({
|
|
226
226
|
field: n,
|
|
227
|
-
fieldName:
|
|
228
|
-
value: t
|
|
227
|
+
fieldName: t,
|
|
228
|
+
value: e[t],
|
|
229
229
|
mode: "edit",
|
|
230
|
-
onChange: (n) => m(
|
|
231
|
-
record:
|
|
230
|
+
onChange: (n) => m(e._key, t, n),
|
|
231
|
+
record: e,
|
|
232
232
|
refs: d,
|
|
233
233
|
locale: _
|
|
234
|
-
}) : String(t
|
|
235
|
-
},
|
|
234
|
+
}) : String(e[t] ?? "")
|
|
235
|
+
}, t);
|
|
236
236
|
}),
|
|
237
237
|
/* @__PURE__ */ f("td", {
|
|
238
238
|
style: {
|
|
@@ -240,15 +240,15 @@ function w({ row: t, columns: i, editableColumns: o, displayOnlyColumns: s, chil
|
|
|
240
240
|
borderBottom: "1px solid var(--divider)",
|
|
241
241
|
verticalAlign: "middle"
|
|
242
242
|
},
|
|
243
|
-
children: v ? /* @__PURE__ */ f(
|
|
243
|
+
children: v ? /* @__PURE__ */ f(t, {
|
|
244
244
|
size: 24,
|
|
245
|
-
icon: /* @__PURE__ */ f(
|
|
246
|
-
onClick: () => g(
|
|
245
|
+
icon: /* @__PURE__ */ f(n.refresh, { size: 12 }),
|
|
246
|
+
onClick: () => g(e._key),
|
|
247
247
|
title: "Undo delete"
|
|
248
|
-
}) : /* @__PURE__ */ f(
|
|
248
|
+
}) : /* @__PURE__ */ f(t, {
|
|
249
249
|
size: 24,
|
|
250
|
-
icon: /* @__PURE__ */ f(
|
|
251
|
-
onClick: () => h(
|
|
250
|
+
icon: /* @__PURE__ */ f(n.trash, { size: 12 }),
|
|
251
|
+
onClick: () => h(e._key),
|
|
252
252
|
title: "Delete row"
|
|
253
253
|
})
|
|
254
254
|
})
|
|
@@ -258,4 +258,4 @@ function w({ row: t, columns: i, editableColumns: o, displayOnlyColumns: s, chil
|
|
|
258
258
|
//#endregion
|
|
259
259
|
export { C as DraggableChildrenTable };
|
|
260
260
|
|
|
261
|
-
//# sourceMappingURL=DraggableChildrenTable-
|
|
261
|
+
//# sourceMappingURL=DraggableChildrenTable-DhcSXQfV.js.map
|
package/dist-lib/{DraggableChildrenTable-DjSEK6XF.js.map → DraggableChildrenTable-DhcSXQfV.js.map}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DraggableChildrenTable-DjSEK6XF.js","names":[],"sources":["../src/components/detail-layout/DraggableChildrenTable.tsx"],"sourcesContent":["import { useCallback, useMemo, useRef } from \"react\";\nimport {\n DndContext,\n closestCenter,\n PointerSensor,\n useSensor,\n useSensors,\n type DragEndEvent,\n} from \"@dnd-kit/core\";\nimport {\n SortableContext,\n verticalListSortingStrategy,\n useSortable,\n} from \"@dnd-kit/sortable\";\nimport { CSS } from \"@dnd-kit/utilities\";\nimport { Btn, Icon, IconBtn } from \"@/components/primitives\";\nimport {\n dataTableStyle,\n dataTableHeadCellStyle,\n dataTableCellStyle,\n} from \"@/components/primitives/dataTableStyles\";\nimport { fieldDisplayName } from \"@/types/schema\";\nimport type { Entity, Schema } from \"@/types/schema\";\nimport { renderField } from \"@/components/fields\";\nimport type { RefsMap } from \"@/components/fields\";\nimport { useUserLocale } from \"@/hooks/useUserLocale\";\nimport type { PendingChildRow } from \"./LayoutRenderer\";\nimport type { ResolvedChildConfig } from \"@/lib/child-config\";\n\ninterface DraggableChildrenTableProps {\n config: ResolvedChildConfig;\n childEntity: Entity;\n schema: Schema;\n rows: PendingChildRow[];\n onChange: (rows: PendingChildRow[]) => void;\n validationErrors?: Record<string, string[]>;\n refs?: RefsMap;\n}\n\n// DraggableChildrenTable renders child rows with drag handles for reordering.\n// Used when position_field is configured on a child relation.\nexport function DraggableChildrenTable({\n config,\n childEntity,\n schema,\n rows,\n onChange,\n validationErrors,\n refs,\n}: DraggableChildrenTableProps) {\n const tableRef = useRef<HTMLTableElement>(null);\n const positionField = config.position_field!;\n const columns = config.columns;\n\n // distance: 5 separates click from drag.\n const sensors = useSensors(\n useSensor(PointerSensor, { activationConstraint: { distance: 5 } }),\n );\n\n // Editable columns: exclude FK, auto, primary, and position_field.\n const fkSet = new Set(Array.isArray(config.foreign_key) ? config.foreign_key : [config.foreign_key]);\n const editableColumns = columns.filter((col) => {\n if (fkSet.has(col)) return false;\n if (col === positionField) return false;\n const field = childEntity.fields.get(col);\n if (!field) return false;\n if (field.auto || field.primary) return false;\n return true;\n });\n\n // Display-only columns (auto/primary but visible in column list, or position_field).\n const displayOnlyColumns = new Set(\n columns.filter((col) => {\n if (col === positionField) return true;\n const field = childEntity.fields.get(col);\n return field && (field.auto || field.primary);\n }),\n );\n\n // Visible columns: exclude position_field from display since it's managed by drag.\n const visibleColumns = columns.filter((col) => col !== positionField);\n\n // Row IDs for SortableContext - only non-deleted rows participate in drag.\n const rowIds = useMemo(\n () => rows.filter((r) => !r._deleted).map((r) => r._key),\n [rows],\n );\n\n const handleCellChange = useCallback(\n (rowKey: string, fieldName: string, value: unknown) => {\n const updated = rows.map((row) =>\n row._key === rowKey\n ? { ...row, [fieldName]: value, _dirty: true }\n : row,\n );\n onChange(updated);\n },\n [rows, onChange],\n );\n\n const handleAddRow = useCallback(() => {\n const maxPosition = rows\n .filter((r) => !r._deleted)\n .reduce((max, r) => {\n const pos = typeof r[positionField] === \"number\" ? (r[positionField] as number) : -1;\n return Math.max(max, pos);\n }, -1);\n\n const newRow: PendingChildRow = {\n _key: crypto.randomUUID(),\n _dirty: true,\n [positionField]: maxPosition + 1,\n };\n // Pre-fill from quick_add.defaults first, then field-level defaults.\n const quickDefaults = config.quick_add?.defaults ?? {};\n for (const col of editableColumns) {\n const field = childEntity.fields.get(col);\n if (!field) continue;\n if (quickDefaults[col] !== undefined) {\n newRow[col] = quickDefaults[col];\n } else if (field.default !== undefined) {\n newRow[col] = field.default;\n } else if (field.type === \"bool\") {\n newRow[col] = false;\n }\n }\n onChange([...rows, newRow]);\n }, [rows, onChange, editableColumns, childEntity, config.quick_add?.defaults, positionField]);\n\n const handleDeleteRow = useCallback(\n (rowKey: string) => {\n const updated = rows.map((row) =>\n row._key === rowKey ? { ...row, _deleted: true } : row,\n );\n onChange(updated);\n },\n [rows, onChange],\n );\n\n const handleUndoDelete = useCallback(\n (rowKey: string) => {\n const updated = rows.map((row) =>\n row._key === rowKey ? { ...row, _deleted: false } : row,\n );\n onChange(updated);\n },\n [rows, onChange],\n );\n\n const handleDragEnd = useCallback(\n (event: DragEndEvent) => {\n const { active, over } = event;\n if (!over || active.id === over.id) return;\n\n // Reorder non-deleted rows.\n const nonDeleted = rows.filter((r) => !r._deleted);\n const deleted = rows.filter((r) => r._deleted);\n\n const oldIndex = nonDeleted.findIndex((r) => r._key === String(active.id));\n const newIndex = nonDeleted.findIndex((r) => r._key === String(over.id));\n if (oldIndex === -1 || newIndex === -1) return;\n\n // Move the row.\n const reordered = [...nonDeleted];\n const [moved] = reordered.splice(oldIndex, 1);\n reordered.splice(newIndex, 0, moved);\n\n // Re-assign sequential position values and mark dirty only if position changed.\n const withPositions = reordered.map((row, index) => ({\n ...row,\n [positionField]: index,\n _dirty: row[positionField] !== index ? true : row._dirty,\n }));\n\n // Append deleted rows at the end (they'll be excluded from payload).\n onChange([...withPositions, ...deleted]);\n },\n [rows, onChange, positionField],\n );\n\n const handleKeyDown = useCallback(\n (e: React.KeyboardEvent<HTMLTableElement>) => {\n const target = e.target as HTMLElement;\n const cell = target.closest(\"td\");\n if (!cell) return;\n const row = cell.closest(\"tr\");\n if (!row) return;\n const tbody = row.closest(\"tbody\");\n if (!tbody) return;\n\n const cellIndex = Array.from(row.cells).indexOf(cell as HTMLTableCellElement);\n const rowIndex = Array.from(tbody.rows).indexOf(row as HTMLTableRowElement);\n\n if (e.key === \"Tab\") {\n const direction = e.shiftKey ? -1 : 1;\n const allRows = Array.from(tbody.rows);\n let nextRow = rowIndex;\n let nextCell = cellIndex + direction;\n\n while (nextRow >= 0 && nextRow < allRows.length) {\n if (nextCell >= 0 && nextCell < allRows[nextRow].cells.length) {\n const nextTd = allRows[nextRow].cells[nextCell];\n const input = nextTd.querySelector(\"input, select, textarea, [tabindex]\") as HTMLElement | null;\n if (input) {\n e.preventDefault();\n input.focus();\n return;\n }\n nextCell += direction;\n } else {\n nextRow += direction;\n nextCell = direction > 0 ? 0 : (allRows[nextRow]?.cells.length ?? 1) - 1;\n }\n }\n } else if (e.key === \"Enter\") {\n e.preventDefault();\n const allRows = Array.from(tbody.rows);\n if (rowIndex < allRows.length - 1) {\n const nextTd = allRows[rowIndex + 1].cells[cellIndex];\n const input = nextTd?.querySelector(\"input, select, textarea, [tabindex]\") as HTMLElement | null;\n if (input) input.focus();\n } else {\n handleAddRow();\n requestAnimationFrame(() => {\n const newRows = tbody.querySelectorAll(\"tr\");\n const lastRow = newRows[newRows.length - 1];\n if (lastRow) {\n const firstInput = lastRow.querySelector(\"input, select, textarea, [tabindex]\") as HTMLElement | null;\n if (firstInput) firstInput.focus();\n }\n });\n }\n } else if (e.key === \"Escape\") {\n (target as HTMLElement).blur?.();\n }\n },\n [handleAddRow],\n );\n\n // Shared table chrome - see primitives/dataTableStyles.ts.\n const thStyle = dataTableHeadCellStyle;\n\n return (\n <div>\n <div style={{ overflowX: \"auto\" }}>\n <DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={handleDragEnd}>\n <table\n ref={tableRef}\n style={dataTableStyle}\n onKeyDown={handleKeyDown}\n >\n <thead>\n <tr>\n <th style={{ ...thStyle, width: 32, padding: \"0 4px\" }} />\n {visibleColumns.map((col) => {\n const field = childEntity.fields.get(col);\n return (\n <th key={col} style={thStyle}>\n {field ? fieldDisplayName(field, col, schema.entities) : col}\n </th>\n );\n })}\n <th style={{ ...thStyle, width: 40 }} />\n </tr>\n </thead>\n <SortableContext items={rowIds} strategy={verticalListSortingStrategy}>\n <tbody>\n {rows.map((row) => (\n <SortableRow\n key={row._key}\n row={row}\n columns={visibleColumns}\n editableColumns={editableColumns}\n displayOnlyColumns={displayOnlyColumns}\n childEntity={childEntity}\n validationErrors={validationErrors}\n refs={refs}\n onCellChange={handleCellChange}\n onDelete={handleDeleteRow}\n onUndoDelete={handleUndoDelete}\n />\n ))}\n </tbody>\n </SortableContext>\n </table>\n </DndContext>\n </div>\n <div style={{ marginTop: 8 }}>\n <Btn kind=\"ghost\" size=\"xs\" icon={<Icon.plus size={12} />} onClick={handleAddRow}>\n Add row\n </Btn>\n </div>\n </div>\n );\n}\n\n// SortableRow wraps a table row with drag-and-drop support.\ninterface SortableRowProps {\n row: PendingChildRow;\n columns: string[];\n editableColumns: string[];\n displayOnlyColumns: Set<string>;\n childEntity: Entity;\n validationErrors?: Record<string, string[]>;\n refs?: RefsMap;\n onCellChange: (rowKey: string, fieldName: string, value: unknown) => void;\n onDelete: (rowKey: string) => void;\n onUndoDelete: (rowKey: string) => void;\n}\n\nfunction SortableRow({\n row,\n columns,\n editableColumns,\n displayOnlyColumns,\n childEntity,\n validationErrors,\n refs,\n onCellChange,\n onDelete,\n onUndoDelete,\n}: SortableRowProps) {\n const userLocale = useUserLocale();\n const isDeleted = !!row._deleted;\n const rowErrors = validationErrors?.[row._key];\n\n const {\n attributes,\n listeners,\n setNodeRef,\n transform,\n transition,\n isDragging,\n } = useSortable({ id: row._key, disabled: isDeleted });\n\n const style = {\n transform: CSS.Translate.toString(transform),\n transition,\n zIndex: isDragging ? 10 : undefined,\n opacity: isDragging ? 0.5 : undefined,\n };\n\n const cellStyle = dataTableCellStyle;\n const editCellStyle: React.CSSProperties = {\n padding: \"4px 4px\",\n borderBottom: \"1px solid var(--divider)\",\n verticalAlign: \"middle\",\n };\n\n return (\n <tr ref={setNodeRef} style={{ ...style, opacity: isDeleted ? 0.5 : style?.opacity ?? 1 }}>\n <td style={{ padding: \"4px 4px\", borderBottom: \"1px solid var(--divider)\", width: 32, verticalAlign: \"middle\" }}>\n {!isDeleted && (\n <button\n type=\"button\"\n style={{\n cursor: \"grab\",\n padding: 4,\n color: \"var(--text-3)\",\n background: \"transparent\",\n border: 0,\n display: \"inline-flex\",\n }}\n {...attributes}\n {...listeners}\n >\n <Icon.grid size={14} />\n </button>\n )}\n </td>\n {columns.map((col) => {\n const field = childEntity.fields.get(col);\n const hasError = rowErrors?.includes(col);\n const isDisplayOnly = displayOnlyColumns.has(col);\n const isEditable = editableColumns.includes(col);\n\n if (isDeleted || isDisplayOnly || !isEditable) {\n return (\n <td\n key={col}\n style={{\n ...cellStyle,\n textDecoration: isDeleted ? \"line-through\" : \"none\",\n }}\n >\n {field\n ? renderField({\n field,\n fieldName: col,\n value: row[col],\n mode: \"display\",\n record: row as Record<string, unknown>,\n refs,\n locale: userLocale,\n })\n : String(row[col] ?? \"\")}\n </td>\n );\n }\n\n return (\n <td\n key={col}\n style={{\n ...editCellStyle,\n outline: hasError ? \"1px solid var(--danger)\" : \"none\",\n outlineOffset: -1,\n borderRadius: hasError ? 4 : 0,\n }}\n title={hasError ? `${col} is required` : undefined}\n >\n {field\n ? renderField({\n field,\n fieldName: col,\n value: row[col],\n mode: \"edit\",\n onChange: (val) => onCellChange(row._key, col, val),\n record: row as Record<string, unknown>,\n refs,\n locale: userLocale,\n })\n : String(row[col] ?? \"\")}\n </td>\n );\n })}\n <td style={{ padding: \"4px 8px\", borderBottom: \"1px solid var(--divider)\", verticalAlign: \"middle\" }}>\n {isDeleted ? (\n <IconBtn\n size={24}\n icon={<Icon.refresh size={12} />}\n onClick={() => onUndoDelete(row._key)}\n title=\"Undo delete\"\n />\n ) : (\n <IconBtn\n size={24}\n icon={<Icon.trash size={12} />}\n onClick={() => onDelete(row._key)}\n title=\"Delete row\"\n />\n )}\n </td>\n </tr>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"DraggableChildrenTable-DhcSXQfV.js","names":[],"sources":["../src/components/detail-layout/DraggableChildrenTable.tsx"],"sourcesContent":["import { useCallback, useMemo, useRef } from \"react\";\nimport {\n DndContext,\n closestCenter,\n PointerSensor,\n useSensor,\n useSensors,\n type DragEndEvent,\n} from \"@dnd-kit/core\";\nimport {\n SortableContext,\n verticalListSortingStrategy,\n useSortable,\n} from \"@dnd-kit/sortable\";\nimport { CSS } from \"@dnd-kit/utilities\";\nimport { Btn, Icon, IconBtn } from \"@/components/primitives\";\nimport {\n dataTableStyle,\n dataTableHeadCellStyle,\n dataTableCellStyle,\n} from \"@/components/primitives/dataTableStyles\";\nimport { fieldDisplayName } from \"@/types/schema\";\nimport type { Entity, Schema } from \"@/types/schema\";\nimport { renderField } from \"@/components/fields\";\nimport type { RefsMap } from \"@/components/fields\";\nimport { useUserLocale } from \"@/hooks/useUserLocale\";\nimport type { PendingChildRow } from \"./LayoutRenderer\";\nimport type { ResolvedChildConfig } from \"@/lib/child-config\";\n\ninterface DraggableChildrenTableProps {\n config: ResolvedChildConfig;\n childEntity: Entity;\n schema: Schema;\n rows: PendingChildRow[];\n onChange: (rows: PendingChildRow[]) => void;\n validationErrors?: Record<string, string[]>;\n refs?: RefsMap;\n}\n\n// DraggableChildrenTable renders child rows with drag handles for reordering.\n// Used when position_field is configured on a child relation.\nexport function DraggableChildrenTable({\n config,\n childEntity,\n schema,\n rows,\n onChange,\n validationErrors,\n refs,\n}: DraggableChildrenTableProps) {\n const tableRef = useRef<HTMLTableElement>(null);\n const positionField = config.position_field!;\n const columns = config.columns;\n\n // distance: 5 separates click from drag.\n const sensors = useSensors(\n useSensor(PointerSensor, { activationConstraint: { distance: 5 } }),\n );\n\n // Editable columns: exclude FK, auto, primary, and position_field.\n const fkSet = new Set(Array.isArray(config.foreign_key) ? config.foreign_key : [config.foreign_key]);\n const editableColumns = columns.filter((col) => {\n if (fkSet.has(col)) return false;\n if (col === positionField) return false;\n const field = childEntity.fields.get(col);\n if (!field) return false;\n if (field.auto || field.primary) return false;\n return true;\n });\n\n // Display-only columns (auto/primary but visible in column list, or position_field).\n const displayOnlyColumns = new Set(\n columns.filter((col) => {\n if (col === positionField) return true;\n const field = childEntity.fields.get(col);\n return field && (field.auto || field.primary);\n }),\n );\n\n // Visible columns: exclude position_field from display since it's managed by drag.\n const visibleColumns = columns.filter((col) => col !== positionField);\n\n // Row IDs for SortableContext - only non-deleted rows participate in drag.\n const rowIds = useMemo(\n () => rows.filter((r) => !r._deleted).map((r) => r._key),\n [rows],\n );\n\n const handleCellChange = useCallback(\n (rowKey: string, fieldName: string, value: unknown) => {\n const updated = rows.map((row) =>\n row._key === rowKey\n ? { ...row, [fieldName]: value, _dirty: true }\n : row,\n );\n onChange(updated);\n },\n [rows, onChange],\n );\n\n const handleAddRow = useCallback(() => {\n const maxPosition = rows\n .filter((r) => !r._deleted)\n .reduce((max, r) => {\n const pos = typeof r[positionField] === \"number\" ? (r[positionField] as number) : -1;\n return Math.max(max, pos);\n }, -1);\n\n const newRow: PendingChildRow = {\n _key: crypto.randomUUID(),\n _dirty: true,\n [positionField]: maxPosition + 1,\n };\n // Pre-fill from quick_add.defaults first, then field-level defaults.\n const quickDefaults = config.quick_add?.defaults ?? {};\n for (const col of editableColumns) {\n const field = childEntity.fields.get(col);\n if (!field) continue;\n if (quickDefaults[col] !== undefined) {\n newRow[col] = quickDefaults[col];\n } else if (field.default !== undefined) {\n newRow[col] = field.default;\n } else if (field.type === \"bool\") {\n newRow[col] = false;\n }\n }\n onChange([...rows, newRow]);\n }, [rows, onChange, editableColumns, childEntity, config.quick_add?.defaults, positionField]);\n\n const handleDeleteRow = useCallback(\n (rowKey: string) => {\n const updated = rows.map((row) =>\n row._key === rowKey ? { ...row, _deleted: true } : row,\n );\n onChange(updated);\n },\n [rows, onChange],\n );\n\n const handleUndoDelete = useCallback(\n (rowKey: string) => {\n const updated = rows.map((row) =>\n row._key === rowKey ? { ...row, _deleted: false } : row,\n );\n onChange(updated);\n },\n [rows, onChange],\n );\n\n const handleDragEnd = useCallback(\n (event: DragEndEvent) => {\n const { active, over } = event;\n if (!over || active.id === over.id) return;\n\n // Reorder non-deleted rows.\n const nonDeleted = rows.filter((r) => !r._deleted);\n const deleted = rows.filter((r) => r._deleted);\n\n const oldIndex = nonDeleted.findIndex((r) => r._key === String(active.id));\n const newIndex = nonDeleted.findIndex((r) => r._key === String(over.id));\n if (oldIndex === -1 || newIndex === -1) return;\n\n // Move the row.\n const reordered = [...nonDeleted];\n const [moved] = reordered.splice(oldIndex, 1);\n reordered.splice(newIndex, 0, moved);\n\n // Re-assign sequential position values and mark dirty only if position changed.\n const withPositions = reordered.map((row, index) => ({\n ...row,\n [positionField]: index,\n _dirty: row[positionField] !== index ? true : row._dirty,\n }));\n\n // Append deleted rows at the end (they'll be excluded from payload).\n onChange([...withPositions, ...deleted]);\n },\n [rows, onChange, positionField],\n );\n\n const handleKeyDown = useCallback(\n (e: React.KeyboardEvent<HTMLTableElement>) => {\n const target = e.target as HTMLElement;\n const cell = target.closest(\"td\");\n if (!cell) return;\n const row = cell.closest(\"tr\");\n if (!row) return;\n const tbody = row.closest(\"tbody\");\n if (!tbody) return;\n\n const cellIndex = Array.from(row.cells).indexOf(cell as HTMLTableCellElement);\n const rowIndex = Array.from(tbody.rows).indexOf(row as HTMLTableRowElement);\n\n if (e.key === \"Tab\") {\n const direction = e.shiftKey ? -1 : 1;\n const allRows = Array.from(tbody.rows);\n let nextRow = rowIndex;\n let nextCell = cellIndex + direction;\n\n while (nextRow >= 0 && nextRow < allRows.length) {\n if (nextCell >= 0 && nextCell < allRows[nextRow].cells.length) {\n const nextTd = allRows[nextRow].cells[nextCell];\n const input = nextTd.querySelector(\"input, select, textarea, [tabindex]\") as HTMLElement | null;\n if (input) {\n e.preventDefault();\n input.focus();\n return;\n }\n nextCell += direction;\n } else {\n nextRow += direction;\n nextCell = direction > 0 ? 0 : (allRows[nextRow]?.cells.length ?? 1) - 1;\n }\n }\n } else if (e.key === \"Enter\") {\n e.preventDefault();\n const allRows = Array.from(tbody.rows);\n if (rowIndex < allRows.length - 1) {\n const nextTd = allRows[rowIndex + 1].cells[cellIndex];\n const input = nextTd?.querySelector(\"input, select, textarea, [tabindex]\") as HTMLElement | null;\n if (input) input.focus();\n } else {\n handleAddRow();\n requestAnimationFrame(() => {\n const newRows = tbody.querySelectorAll(\"tr\");\n const lastRow = newRows[newRows.length - 1];\n if (lastRow) {\n const firstInput = lastRow.querySelector(\"input, select, textarea, [tabindex]\") as HTMLElement | null;\n if (firstInput) firstInput.focus();\n }\n });\n }\n } else if (e.key === \"Escape\") {\n (target as HTMLElement).blur?.();\n }\n },\n [handleAddRow],\n );\n\n // Shared table chrome - see primitives/dataTableStyles.ts.\n const thStyle = dataTableHeadCellStyle;\n\n return (\n <div>\n <div style={{ overflowX: \"auto\" }}>\n <DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={handleDragEnd}>\n <table\n ref={tableRef}\n style={dataTableStyle}\n onKeyDown={handleKeyDown}\n >\n <thead>\n <tr>\n <th style={{ ...thStyle, width: 32, padding: \"0 4px\" }} />\n {visibleColumns.map((col) => {\n const field = childEntity.fields.get(col);\n return (\n <th key={col} style={thStyle}>\n {field ? fieldDisplayName(field, col, schema.entities) : col}\n </th>\n );\n })}\n <th style={{ ...thStyle, width: 40 }} />\n </tr>\n </thead>\n <SortableContext items={rowIds} strategy={verticalListSortingStrategy}>\n <tbody>\n {rows.map((row) => (\n <SortableRow\n key={row._key}\n row={row}\n columns={visibleColumns}\n editableColumns={editableColumns}\n displayOnlyColumns={displayOnlyColumns}\n childEntity={childEntity}\n validationErrors={validationErrors}\n refs={refs}\n onCellChange={handleCellChange}\n onDelete={handleDeleteRow}\n onUndoDelete={handleUndoDelete}\n />\n ))}\n </tbody>\n </SortableContext>\n </table>\n </DndContext>\n </div>\n <div style={{ marginTop: 8 }}>\n <Btn kind=\"ghost\" size=\"xs\" icon={<Icon.plus size={12} />} onClick={handleAddRow}>\n Add row\n </Btn>\n </div>\n </div>\n );\n}\n\n// SortableRow wraps a table row with drag-and-drop support.\ninterface SortableRowProps {\n row: PendingChildRow;\n columns: string[];\n editableColumns: string[];\n displayOnlyColumns: Set<string>;\n childEntity: Entity;\n validationErrors?: Record<string, string[]>;\n refs?: RefsMap;\n onCellChange: (rowKey: string, fieldName: string, value: unknown) => void;\n onDelete: (rowKey: string) => void;\n onUndoDelete: (rowKey: string) => void;\n}\n\nfunction SortableRow({\n row,\n columns,\n editableColumns,\n displayOnlyColumns,\n childEntity,\n validationErrors,\n refs,\n onCellChange,\n onDelete,\n onUndoDelete,\n}: SortableRowProps) {\n const userLocale = useUserLocale();\n const isDeleted = !!row._deleted;\n const rowErrors = validationErrors?.[row._key];\n\n const {\n attributes,\n listeners,\n setNodeRef,\n transform,\n transition,\n isDragging,\n } = useSortable({ id: row._key, disabled: isDeleted });\n\n const style = {\n transform: CSS.Translate.toString(transform),\n transition,\n zIndex: isDragging ? 10 : undefined,\n opacity: isDragging ? 0.5 : undefined,\n };\n\n const cellStyle = dataTableCellStyle;\n const editCellStyle: React.CSSProperties = {\n padding: \"4px 4px\",\n borderBottom: \"1px solid var(--divider)\",\n verticalAlign: \"middle\",\n };\n\n return (\n <tr ref={setNodeRef} style={{ ...style, opacity: isDeleted ? 0.5 : style?.opacity ?? 1 }}>\n <td style={{ padding: \"4px 4px\", borderBottom: \"1px solid var(--divider)\", width: 32, verticalAlign: \"middle\" }}>\n {!isDeleted && (\n <button\n type=\"button\"\n style={{\n cursor: \"grab\",\n padding: 4,\n color: \"var(--text-3)\",\n background: \"transparent\",\n border: 0,\n display: \"inline-flex\",\n }}\n {...attributes}\n {...listeners}\n >\n <Icon.grid size={14} />\n </button>\n )}\n </td>\n {columns.map((col) => {\n const field = childEntity.fields.get(col);\n const hasError = rowErrors?.includes(col);\n const isDisplayOnly = displayOnlyColumns.has(col);\n const isEditable = editableColumns.includes(col);\n\n if (isDeleted || isDisplayOnly || !isEditable) {\n return (\n <td\n key={col}\n style={{\n ...cellStyle,\n textDecoration: isDeleted ? \"line-through\" : \"none\",\n }}\n >\n {field\n ? renderField({\n field,\n fieldName: col,\n value: row[col],\n mode: \"display\",\n record: row as Record<string, unknown>,\n refs,\n locale: userLocale,\n })\n : String(row[col] ?? \"\")}\n </td>\n );\n }\n\n return (\n <td\n key={col}\n style={{\n ...editCellStyle,\n outline: hasError ? \"1px solid var(--danger)\" : \"none\",\n outlineOffset: -1,\n borderRadius: hasError ? 4 : 0,\n }}\n title={hasError ? `${col} is required` : undefined}\n >\n {field\n ? renderField({\n field,\n fieldName: col,\n value: row[col],\n mode: \"edit\",\n onChange: (val) => onCellChange(row._key, col, val),\n record: row as Record<string, unknown>,\n refs,\n locale: userLocale,\n })\n : String(row[col] ?? \"\")}\n </td>\n );\n })}\n <td style={{ padding: \"4px 8px\", borderBottom: \"1px solid var(--divider)\", verticalAlign: \"middle\" }}>\n {isDeleted ? (\n <IconBtn\n size={24}\n icon={<Icon.refresh size={12} />}\n onClick={() => onUndoDelete(row._key)}\n title=\"Undo delete\"\n />\n ) : (\n <IconBtn\n size={24}\n icon={<Icon.trash size={12} />}\n onClick={() => onDelete(row._key)}\n title=\"Delete row\"\n />\n )}\n </td>\n </tr>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { _ as e, b as t, d as n, f as r, i, o as a, t as o, v as s, x as c, y as l } from "./value-
|
|
1
|
+
import { _ as e, b as t, d as n, f as r, i, o as a, t as o, v as s, x as c, y as l } from "./value-CrpaObP_.js";
|
|
2
2
|
import { useCallback as u, useEffect as d, useMemo as f, useRef as p, useState as m } from "react";
|
|
3
3
|
import { Fragment as h, jsx as g, jsxs as _ } from "react/jsx-runtime";
|
|
4
4
|
import { DndContext as v, PointerSensor as y, closestCenter as b, useSensor as x, useSensors as S } from "@dnd-kit/core";
|
|
@@ -402,4 +402,4 @@ function re({ file: e, hasPrev: t, hasNext: n, onPrev: r, onNext: a, onClose: o
|
|
|
402
402
|
//#endregion
|
|
403
403
|
export { D as Grid };
|
|
404
404
|
|
|
405
|
-
//# sourceMappingURL=Grid-
|
|
405
|
+
//# sourceMappingURL=Grid-Cwxca9aO.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Grid-D8gOvkf3.js","names":[],"sources":["../src/components/file-widgets/Grid.tsx"],"sourcesContent":["import {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport type { CSSProperties, DragEvent, KeyboardEvent } from \"react\";\nimport {\n DndContext,\n closestCenter,\n PointerSensor,\n useSensor,\n useSensors,\n type DragEndEvent,\n} from \"@dnd-kit/core\";\nimport {\n SortableContext,\n rectSortingStrategy,\n useSortable,\n arrayMove,\n} from \"@dnd-kit/sortable\";\nimport { CSS } from \"@dnd-kit/utilities\";\nimport {\n uploadFile,\n discardFile,\n validateClientSide,\n formatBytes,\n UploadError,\n} from \"./upload\";\nimport { useDraftRegistration } from \"./draft-scope\";\nimport { preferredListThumbnailURL, preferredThumbnailVariant } from \"./helpers\";\nimport { useEmbedFileSrc } from \"./useEmbedFileSrc\";\nimport { fileArrayFromValue } from \"./value\";\nimport type {\n FileWidgetProps,\n HydratedFile,\n UploadProgress,\n} from \"./types\";\n\n// Grid widget: thumbnail grid for `mode: array` files. Per einstein §6.4.\n//\n// Behaviors:\n// - drag-drop multi-upload (each file gets its own progress tile)\n// - per-item hover overlay (View / Metadata / Delete)\n// - drag-handle reorder via @dnd-kit\n// - multi-select toolbar with Bulk Delete\n// - click → lightbox with prev/next + keyboard nav\n// - IntersectionObserver lazy thumbnail loading\n// - per-tile thumbnail prefers the schema-selected list derivation, then\n// falls back to built-in thumbs or the primary file URL\n//\n// Value contract: array of HydratedFile. onChange emits the full new array\n// (callers persist it via the entity's $files payload on save).\n\ninterface InFlightTile {\n // Local-only tiles representing uploads in progress. Once finished they\n // are appended to the value array and removed from this list.\n localId: string;\n filename: string;\n progress: UploadProgress | null;\n error: { code: string; message: string } | null;\n abort: () => void;\n}\n\nfunction thumbUrl(f: HydratedFile, preferredDerivationKey?: string): string | undefined {\n return preferredListThumbnailURL(f, preferredDerivationKey);\n}\n\nexport function Grid(props: FileWidgetProps) {\n const { field, fieldName, value, onChange, mode, disabled } = props;\n const items = fileArrayFromValue(value);\n const [tiles, setTiles] = useState<InFlightTile[]>([]);\n const [selected, setSelected] = useState<Set<string>>(new Set());\n const [lightbox, setLightbox] = useState<number | null>(null);\n const { register, unregister, isDraft } = useDraftRegistration();\n const cancelledUploadsRef = useRef<Set<string>>(new Set());\n const tilesRef = useRef<InFlightTile[]>([]);\n tilesRef.current = tiles;\n // valueRef snapshots the current value array so parallel uploads append\n // against the most recent state, not the closure they were created with.\n const valueRef = useRef<HydratedFile[]>(items);\n valueRef.current = items;\n\n // Cleanup all in-flight uploads on unmount.\n useEffect(() => {\n return () => {\n tilesRef.current.forEach((t) => t.abort());\n };\n }, []);\n\n const sensors = useSensors(\n useSensor(PointerSensor, { activationConstraint: { distance: 5 } }),\n );\n\n const ariaLabel = field.display?.placeholder || fieldName;\n const accept = (field.accepts ?? [\"image/*\"]).join(\",\");\n const reorderable = field.display?.reorderable !== false;\n const preferredThumbnail = field.display?.listable_thumbnail;\n\n const startUpload = useCallback(\n async (raw: File) => {\n const v = validateClientSide(raw, field.accepts, field.max_size_bytes);\n const localId = `tile-${Math.random().toString(36).slice(2)}-${Date.now()}`;\n const ctrl = new AbortController();\n\n if (v) {\n setTiles((prev) => [\n ...prev,\n {\n localId,\n filename: raw.name,\n progress: null,\n error: v,\n abort: () => {},\n },\n ]);\n return;\n }\n\n setTiles((prev) => [\n ...prev,\n {\n localId,\n filename: raw.name,\n progress: { loaded: 0, total: raw.size, pct: 0 },\n error: null,\n abort: () => ctrl.abort(),\n },\n ]);\n\n try {\n const result = await uploadFile({\n file: raw,\n entityCode: props.entityCode,\n fieldCode: fieldName,\n signal: ctrl.signal,\n onProgress: (p) =>\n setTiles((prev) =>\n prev.map((t) =>\n t.localId === localId ? { ...t, progress: p } : t,\n ),\n ),\n });\n if (cancelledUploadsRef.current.delete(localId)) {\n void discardFile(result.id).catch(() => {});\n unregister(result.id);\n return;\n }\n register(result.id, () => discardFile(result.id));\n const next: HydratedFile = {\n id: result.id,\n filename: result.filename,\n content_type: result.content_type,\n size_bytes: result.size_bytes,\n sha256: result.sha256,\n width: result.width,\n height: result.height,\n url: result.url,\n metadata: result.metadata,\n };\n const merged = [...valueRef.current, next];\n valueRef.current = merged;\n onChange?.(merged);\n setTiles((prev) => prev.filter((t) => t.localId !== localId));\n } catch (err) {\n if (err instanceof UploadError && err.code === \"ABORTED\") {\n setTiles((prev) => prev.filter((t) => t.localId !== localId));\n return;\n }\n const e =\n err instanceof UploadError\n ? { code: err.code, message: err.message }\n : {\n code: \"UNKNOWN\",\n message: err instanceof Error ? err.message : \"Upload failed\",\n };\n setTiles((prev) =>\n prev.map((t) =>\n t.localId === localId\n ? { ...t, error: e, progress: null, abort: () => {} }\n : t,\n ),\n );\n }\n },\n [field.accepts, field.max_size_bytes, fieldName, onChange, props.entityCode, register, unregister],\n );\n\n const handleFiles = useCallback(\n (files: FileList | File[]) => {\n const arr = Array.from(files);\n arr.forEach((f) => void startUpload(f));\n },\n [startUpload],\n );\n\n const fileInputRef = useRef<HTMLInputElement | null>(null);\n const onPick = useCallback(() => {\n if (disabled) return;\n fileInputRef.current?.click();\n }, [disabled]);\n\n const onDragOver = useCallback(\n (e: DragEvent<HTMLDivElement>) => {\n if (disabled) return;\n e.preventDefault();\n e.dataTransfer.dropEffect = \"copy\";\n },\n [disabled],\n );\n\n const onDrop = useCallback(\n (e: DragEvent<HTMLDivElement>) => {\n if (disabled) return;\n e.preventDefault();\n const files = e.dataTransfer.files;\n if (files && files.length > 0) handleFiles(files);\n },\n [disabled, handleFiles],\n );\n\n const onChangeFile = useCallback(\n (e: React.ChangeEvent<HTMLInputElement>) => {\n const files = e.target.files;\n e.target.value = \"\";\n if (files && files.length > 0) handleFiles(files);\n },\n [handleFiles],\n );\n\n const handleRemove = useCallback(\n (id: string) => {\n if (isDraft(id)) {\n void discardFile(id).catch(() => {});\n unregister(id);\n }\n const next = items.filter((f) => f.id !== id);\n onChange?.(next);\n setSelected((prev) => {\n const n = new Set(prev);\n n.delete(id);\n return n;\n });\n },\n [isDraft, items, onChange, unregister],\n );\n\n const handleBulkDelete = useCallback(() => {\n if (selected.size === 0) return;\n const ids = Array.from(selected);\n ids.forEach((id) => {\n if (isDraft(id)) {\n void discardFile(id).catch(() => {});\n unregister(id);\n }\n });\n onChange?.(items.filter((f) => !selected.has(f.id)));\n setSelected(new Set());\n }, [isDraft, items, selected, onChange, unregister]);\n\n const handleDragEnd = useCallback(\n (event: DragEndEvent) => {\n const { active, over } = event;\n if (!over || active.id === over.id) return;\n const oldIdx = items.findIndex((f) => f.id === active.id);\n const newIdx = items.findIndex((f) => f.id === over.id);\n if (oldIdx < 0 || newIdx < 0) return;\n onChange?.(arrayMove(items, oldIdx, newIdx));\n },\n [items, onChange],\n );\n\n const toggleSelect = useCallback((id: string) => {\n setSelected((prev) => {\n const n = new Set(prev);\n if (n.has(id)) n.delete(id);\n else n.add(id);\n return n;\n });\n }, []);\n\n const lightboxFile = lightbox != null ? items[lightbox] : null;\n const lightboxNext = useCallback(() => {\n setLightbox((cur) =>\n cur == null ? cur : Math.min(items.length - 1, cur + 1),\n );\n }, [items.length]);\n const lightboxPrev = useCallback(() => {\n setLightbox((cur) => (cur == null ? cur : Math.max(0, cur - 1)));\n }, []);\n const lightboxClose = useCallback(() => setLightbox(null), []);\n\n useEffect(() => {\n if (lightbox == null) return;\n const handler = (e: globalThis.KeyboardEvent) => {\n if (e.key === \"Escape\") lightboxClose();\n else if (e.key === \"ArrowRight\") lightboxNext();\n else if (e.key === \"ArrowLeft\") lightboxPrev();\n };\n window.addEventListener(\"keydown\", handler);\n return () => window.removeEventListener(\"keydown\", handler);\n }, [lightbox, lightboxClose, lightboxNext, lightboxPrev]);\n\n const onGridKeyDown = useCallback(\n (e: KeyboardEvent<HTMLDivElement>) => {\n if (disabled) return;\n if (e.key === \"Enter\" || e.key === \" \") {\n if (e.target === e.currentTarget) {\n e.preventDefault();\n onPick();\n }\n }\n },\n [disabled, onPick],\n );\n\n const isDisplay = mode === \"display\";\n\n const sortableIds = useMemo(() => items.map((f) => f.id), [items]);\n\n return (\n <div data-testid={`file-widget-${fieldName}`} className=\"flex flex-col gap-2\">\n {!isDisplay && selected.size > 0 && (\n <div\n className=\"flex items-center gap-2 rounded-md border border-border bg-surface-2 p-2 text-sm\"\n role=\"toolbar\"\n aria-label=\"Bulk actions\"\n >\n <span className=\"text-foreground\">{selected.size} selected</span>\n <button\n type=\"button\"\n className=\"rounded-sm border border-border bg-card px-2 py-1 text-xs text-destructive hover:bg-surface-2\"\n onClick={handleBulkDelete}\n data-testid=\"bulk-delete\"\n >\n Delete\n </button>\n <button\n type=\"button\"\n className=\"rounded-sm border border-border bg-card px-2 py-1 text-xs text-foreground hover:bg-surface-2\"\n onClick={() => setSelected(new Set())}\n >\n Clear\n </button>\n </div>\n )}\n\n <DndContext\n sensors={sensors}\n collisionDetection={closestCenter}\n onDragEnd={handleDragEnd}\n >\n <SortableContext items={sortableIds} strategy={rectSortingStrategy}>\n <div\n className=\"grid grid-cols-[repeat(auto-fill,minmax(140px,1fr))] gap-3 rounded-md border border-dashed border-border bg-surface p-3 outline-none focus-visible:outline-2 focus-visible:outline-accent\"\n role=\"grid\"\n aria-label={ariaLabel}\n tabIndex={isDisplay || disabled ? -1 : 0}\n onDragOver={onDragOver}\n onDrop={onDrop}\n onKeyDown={onGridKeyDown}\n data-testid=\"grid-container\"\n >\n {items.map((f, idx) => (\n <SortableTile\n key={f.id}\n file={f}\n selected={selected.has(f.id)}\n disabled={disabled || isDisplay}\n reorderable={reorderable && !isDisplay}\n preferredThumbnail={preferredThumbnail}\n onSelect={() => toggleSelect(f.id)}\n onRemove={() => handleRemove(f.id)}\n onPreview={() => setLightbox(idx)}\n />\n ))}\n {tiles.map((t) => (\n <PendingTile\n key={t.localId}\n tile={t}\n onCancel={() => {\n t.abort();\n cancelledUploadsRef.current.add(t.localId);\n setTiles((prev) =>\n prev.filter((x) => x.localId !== t.localId),\n );\n }}\n onDismiss={() =>\n setTiles((prev) =>\n prev.filter((x) => x.localId !== t.localId),\n )\n }\n />\n ))}\n {items.length === 0 && tiles.length === 0 && (\n <div\n className=\"col-span-full flex h-24 cursor-pointer items-center justify-center text-sm text-muted-foreground\"\n onClick={onPick}\n data-testid=\"grid-empty\"\n >\n {field.display?.placeholder || \"Drop files here or click to browse\"}\n </div>\n )}\n </div>\n </SortableContext>\n </DndContext>\n\n {!isDisplay && (\n <input\n ref={fileInputRef}\n type=\"file\"\n accept={accept}\n multiple\n onChange={onChangeFile}\n style={{ display: \"none\" }}\n data-testid={`file-input-${fieldName}`}\n />\n )}\n\n {lightboxFile && (\n <Lightbox\n file={lightboxFile}\n hasPrev={lightbox! > 0}\n hasNext={lightbox! < items.length - 1}\n onPrev={lightboxPrev}\n onNext={lightboxNext}\n onClose={lightboxClose}\n />\n )}\n </div>\n );\n}\n\nfunction SortableTile({\n file,\n selected,\n disabled,\n reorderable,\n preferredThumbnail,\n onSelect,\n onRemove,\n onPreview,\n}: {\n file: HydratedFile;\n selected: boolean;\n disabled?: boolean;\n reorderable: boolean;\n preferredThumbnail?: string;\n onSelect: () => void;\n onRemove: () => void;\n onPreview: () => void;\n}) {\n const {\n attributes,\n listeners,\n setNodeRef,\n transform,\n transition,\n isDragging,\n } = useSortable({ id: file.id, disabled: !reorderable });\n const style: CSSProperties = {\n transform: CSS.Transform.toString(transform),\n transition,\n opacity: isDragging ? 0.5 : 1,\n };\n // Cookie mode: `thumbUrl` is the static thumbnail URL, returned unchanged.\n // Embed mode: resolved through the Bearer-authenticated file URL endpoint.\n const url = useEmbedFileSrc(\n file.id,\n preferredThumbnailVariant(file, preferredThumbnail),\n thumbUrl(file, preferredThumbnail),\n );\n return (\n <div\n ref={setNodeRef}\n style={style}\n className={`group relative aspect-square overflow-hidden rounded-md border ${\n selected ? \"border-accent\" : \"border-border\"\n } bg-surface-2`}\n data-testid={`grid-tile-${file.id}`}\n data-selected={selected || undefined}\n >\n {/* Lazy thumb: native loading=\"lazy\" approximates IntersectionObserver\n and is what the rest of the SDK uses (see ImageWidget). */}\n {url ? (\n <img\n src={url}\n alt={file.filename}\n className=\"h-full w-full cursor-pointer object-cover\"\n loading=\"lazy\"\n onClick={onPreview}\n />\n ) : (\n <div\n className=\"flex h-full w-full cursor-pointer items-center justify-center bg-surface-3 text-xs text-muted-foreground\"\n onClick={onPreview}\n >\n {file.filename}\n </div>\n )}\n {!disabled && (\n <>\n <input\n type=\"checkbox\"\n className=\"absolute left-1 top-1 h-4 w-4 cursor-pointer\"\n checked={selected}\n onChange={onSelect}\n aria-label={`Select ${file.filename}`}\n data-testid={`select-${file.id}`}\n onClick={(e) => e.stopPropagation()}\n />\n {reorderable && (\n <button\n type=\"button\"\n className=\"absolute right-1 top-1 cursor-grab rounded-sm border border-border bg-card px-1 text-xs\"\n aria-label={`Drag to reorder ${file.filename}`}\n data-testid={`drag-${file.id}`}\n {...attributes}\n {...listeners}\n >\n ⋮⋮\n </button>\n )}\n <div className=\"absolute inset-x-0 bottom-0 flex items-center justify-end gap-1 bg-black/50 p-1 opacity-0 transition-opacity group-hover:opacity-100 group-focus-within:opacity-100\">\n <button\n type=\"button\"\n className=\"rounded-sm border border-border bg-card px-2 py-0.5 text-xs\"\n onClick={onPreview}\n >\n View\n </button>\n <button\n type=\"button\"\n className=\"rounded-sm border border-border bg-card px-2 py-0.5 text-xs text-destructive\"\n onClick={onRemove}\n data-testid={`remove-${file.id}`}\n >\n Delete\n </button>\n </div>\n </>\n )}\n </div>\n );\n}\n\nfunction PendingTile({\n tile,\n onCancel,\n onDismiss,\n}: {\n tile: InFlightTile;\n onCancel: () => void;\n onDismiss: () => void;\n}) {\n return (\n <div\n className={`relative flex aspect-square flex-col items-center justify-center rounded-md border ${\n tile.error ? \"border-destructive\" : \"border-border\"\n } bg-surface-2 p-2 text-center text-xs`}\n data-testid={`pending-tile-${tile.localId}`}\n >\n <span className=\"truncate\" title={tile.filename}>\n {tile.filename}\n </span>\n {tile.progress && !tile.error && (\n <span\n className=\"mt-1 text-muted-foreground\"\n role=\"progressbar\"\n aria-valuenow={tile.progress.pct}\n aria-valuemin={0}\n aria-valuemax={100}\n >\n {tile.progress.pct}%\n </span>\n )}\n {tile.error && (\n <div role=\"alert\" className=\"mt-1 text-destructive\">\n {tile.error.message}\n </div>\n )}\n <button\n type=\"button\"\n className=\"mt-1 rounded-sm border border-border bg-card px-2 py-0.5\"\n onClick={tile.error ? onDismiss : onCancel}\n >\n {tile.error ? \"Dismiss\" : \"Cancel\"}\n </button>\n </div>\n );\n}\n\nfunction Lightbox({\n file,\n hasPrev,\n hasNext,\n onPrev,\n onNext,\n onClose,\n}: {\n file: HydratedFile;\n hasPrev: boolean;\n hasNext: boolean;\n onPrev: () => void;\n onNext: () => void;\n onClose: () => void;\n}) {\n // The lightbox shows the full image. Cookie mode: the static primary URL.\n // Embed mode: the primary file resolved through the embed file URL endpoint.\n const url = useEmbedFileSrc(\n file.id,\n \"preview\",\n file.presigned_url ?? file.url ?? thumbUrl(file),\n );\n return (\n <div\n className=\"fixed inset-0 z-50 flex items-center justify-center bg-black/80\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label={file.filename}\n data-testid=\"lightbox\"\n onClick={onClose}\n >\n <button\n type=\"button\"\n className=\"absolute right-4 top-4 rounded-sm border border-border bg-card px-3 py-1 text-foreground\"\n onClick={(e) => {\n e.stopPropagation();\n onClose();\n }}\n data-testid=\"lightbox-close\"\n >\n Close\n </button>\n {hasPrev && (\n <button\n type=\"button\"\n className=\"absolute left-4 rounded-sm border border-border bg-card px-3 py-2 text-foreground\"\n onClick={(e) => {\n e.stopPropagation();\n onPrev();\n }}\n data-testid=\"lightbox-prev\"\n >\n ‹\n </button>\n )}\n {hasNext && (\n <button\n type=\"button\"\n className=\"absolute right-4 bottom-4 rounded-sm border border-border bg-card px-3 py-2 text-foreground\"\n onClick={(e) => {\n e.stopPropagation();\n onNext();\n }}\n data-testid=\"lightbox-next\"\n >\n ›\n </button>\n )}\n {url ? (\n <img\n src={url}\n alt={file.filename}\n className=\"max-h-[90vh] max-w-[90vw] object-contain\"\n onClick={(e) => e.stopPropagation()}\n />\n ) : (\n <div className=\"rounded-md bg-card p-4 text-foreground\">\n {file.filename} ({formatBytes(file.size_bytes)})\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Grid-Cwxca9aO.js","names":[],"sources":["../src/components/file-widgets/Grid.tsx"],"sourcesContent":["import {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport type { CSSProperties, DragEvent, KeyboardEvent } from \"react\";\nimport {\n DndContext,\n closestCenter,\n PointerSensor,\n useSensor,\n useSensors,\n type DragEndEvent,\n} from \"@dnd-kit/core\";\nimport {\n SortableContext,\n rectSortingStrategy,\n useSortable,\n arrayMove,\n} from \"@dnd-kit/sortable\";\nimport { CSS } from \"@dnd-kit/utilities\";\nimport {\n uploadFile,\n discardFile,\n validateClientSide,\n formatBytes,\n UploadError,\n} from \"./upload\";\nimport { useDraftRegistration } from \"./draft-scope\";\nimport { preferredListThumbnailURL, preferredThumbnailVariant } from \"./helpers\";\nimport { useEmbedFileSrc } from \"./useEmbedFileSrc\";\nimport { fileArrayFromValue } from \"./value\";\nimport type {\n FileWidgetProps,\n HydratedFile,\n UploadProgress,\n} from \"./types\";\n\n// Grid widget: thumbnail grid for `mode: array` files. Per einstein §6.4.\n//\n// Behaviors:\n// - drag-drop multi-upload (each file gets its own progress tile)\n// - per-item hover overlay (View / Metadata / Delete)\n// - drag-handle reorder via @dnd-kit\n// - multi-select toolbar with Bulk Delete\n// - click → lightbox with prev/next + keyboard nav\n// - IntersectionObserver lazy thumbnail loading\n// - per-tile thumbnail prefers the schema-selected list derivation, then\n// falls back to built-in thumbs or the primary file URL\n//\n// Value contract: array of HydratedFile. onChange emits the full new array\n// (callers persist it via the entity's $files payload on save).\n\ninterface InFlightTile {\n // Local-only tiles representing uploads in progress. Once finished they\n // are appended to the value array and removed from this list.\n localId: string;\n filename: string;\n progress: UploadProgress | null;\n error: { code: string; message: string } | null;\n abort: () => void;\n}\n\nfunction thumbUrl(f: HydratedFile, preferredDerivationKey?: string): string | undefined {\n return preferredListThumbnailURL(f, preferredDerivationKey);\n}\n\nexport function Grid(props: FileWidgetProps) {\n const { field, fieldName, value, onChange, mode, disabled } = props;\n const items = fileArrayFromValue(value);\n const [tiles, setTiles] = useState<InFlightTile[]>([]);\n const [selected, setSelected] = useState<Set<string>>(new Set());\n const [lightbox, setLightbox] = useState<number | null>(null);\n const { register, unregister, isDraft } = useDraftRegistration();\n const cancelledUploadsRef = useRef<Set<string>>(new Set());\n const tilesRef = useRef<InFlightTile[]>([]);\n tilesRef.current = tiles;\n // valueRef snapshots the current value array so parallel uploads append\n // against the most recent state, not the closure they were created with.\n const valueRef = useRef<HydratedFile[]>(items);\n valueRef.current = items;\n\n // Cleanup all in-flight uploads on unmount.\n useEffect(() => {\n return () => {\n tilesRef.current.forEach((t) => t.abort());\n };\n }, []);\n\n const sensors = useSensors(\n useSensor(PointerSensor, { activationConstraint: { distance: 5 } }),\n );\n\n const ariaLabel = field.display?.placeholder || fieldName;\n const accept = (field.accepts ?? [\"image/*\"]).join(\",\");\n const reorderable = field.display?.reorderable !== false;\n const preferredThumbnail = field.display?.listable_thumbnail;\n\n const startUpload = useCallback(\n async (raw: File) => {\n const v = validateClientSide(raw, field.accepts, field.max_size_bytes);\n const localId = `tile-${Math.random().toString(36).slice(2)}-${Date.now()}`;\n const ctrl = new AbortController();\n\n if (v) {\n setTiles((prev) => [\n ...prev,\n {\n localId,\n filename: raw.name,\n progress: null,\n error: v,\n abort: () => {},\n },\n ]);\n return;\n }\n\n setTiles((prev) => [\n ...prev,\n {\n localId,\n filename: raw.name,\n progress: { loaded: 0, total: raw.size, pct: 0 },\n error: null,\n abort: () => ctrl.abort(),\n },\n ]);\n\n try {\n const result = await uploadFile({\n file: raw,\n entityCode: props.entityCode,\n fieldCode: fieldName,\n signal: ctrl.signal,\n onProgress: (p) =>\n setTiles((prev) =>\n prev.map((t) =>\n t.localId === localId ? { ...t, progress: p } : t,\n ),\n ),\n });\n if (cancelledUploadsRef.current.delete(localId)) {\n void discardFile(result.id).catch(() => {});\n unregister(result.id);\n return;\n }\n register(result.id, () => discardFile(result.id));\n const next: HydratedFile = {\n id: result.id,\n filename: result.filename,\n content_type: result.content_type,\n size_bytes: result.size_bytes,\n sha256: result.sha256,\n width: result.width,\n height: result.height,\n url: result.url,\n metadata: result.metadata,\n };\n const merged = [...valueRef.current, next];\n valueRef.current = merged;\n onChange?.(merged);\n setTiles((prev) => prev.filter((t) => t.localId !== localId));\n } catch (err) {\n if (err instanceof UploadError && err.code === \"ABORTED\") {\n setTiles((prev) => prev.filter((t) => t.localId !== localId));\n return;\n }\n const e =\n err instanceof UploadError\n ? { code: err.code, message: err.message }\n : {\n code: \"UNKNOWN\",\n message: err instanceof Error ? err.message : \"Upload failed\",\n };\n setTiles((prev) =>\n prev.map((t) =>\n t.localId === localId\n ? { ...t, error: e, progress: null, abort: () => {} }\n : t,\n ),\n );\n }\n },\n [field.accepts, field.max_size_bytes, fieldName, onChange, props.entityCode, register, unregister],\n );\n\n const handleFiles = useCallback(\n (files: FileList | File[]) => {\n const arr = Array.from(files);\n arr.forEach((f) => void startUpload(f));\n },\n [startUpload],\n );\n\n const fileInputRef = useRef<HTMLInputElement | null>(null);\n const onPick = useCallback(() => {\n if (disabled) return;\n fileInputRef.current?.click();\n }, [disabled]);\n\n const onDragOver = useCallback(\n (e: DragEvent<HTMLDivElement>) => {\n if (disabled) return;\n e.preventDefault();\n e.dataTransfer.dropEffect = \"copy\";\n },\n [disabled],\n );\n\n const onDrop = useCallback(\n (e: DragEvent<HTMLDivElement>) => {\n if (disabled) return;\n e.preventDefault();\n const files = e.dataTransfer.files;\n if (files && files.length > 0) handleFiles(files);\n },\n [disabled, handleFiles],\n );\n\n const onChangeFile = useCallback(\n (e: React.ChangeEvent<HTMLInputElement>) => {\n const files = e.target.files;\n e.target.value = \"\";\n if (files && files.length > 0) handleFiles(files);\n },\n [handleFiles],\n );\n\n const handleRemove = useCallback(\n (id: string) => {\n if (isDraft(id)) {\n void discardFile(id).catch(() => {});\n unregister(id);\n }\n const next = items.filter((f) => f.id !== id);\n onChange?.(next);\n setSelected((prev) => {\n const n = new Set(prev);\n n.delete(id);\n return n;\n });\n },\n [isDraft, items, onChange, unregister],\n );\n\n const handleBulkDelete = useCallback(() => {\n if (selected.size === 0) return;\n const ids = Array.from(selected);\n ids.forEach((id) => {\n if (isDraft(id)) {\n void discardFile(id).catch(() => {});\n unregister(id);\n }\n });\n onChange?.(items.filter((f) => !selected.has(f.id)));\n setSelected(new Set());\n }, [isDraft, items, selected, onChange, unregister]);\n\n const handleDragEnd = useCallback(\n (event: DragEndEvent) => {\n const { active, over } = event;\n if (!over || active.id === over.id) return;\n const oldIdx = items.findIndex((f) => f.id === active.id);\n const newIdx = items.findIndex((f) => f.id === over.id);\n if (oldIdx < 0 || newIdx < 0) return;\n onChange?.(arrayMove(items, oldIdx, newIdx));\n },\n [items, onChange],\n );\n\n const toggleSelect = useCallback((id: string) => {\n setSelected((prev) => {\n const n = new Set(prev);\n if (n.has(id)) n.delete(id);\n else n.add(id);\n return n;\n });\n }, []);\n\n const lightboxFile = lightbox != null ? items[lightbox] : null;\n const lightboxNext = useCallback(() => {\n setLightbox((cur) =>\n cur == null ? cur : Math.min(items.length - 1, cur + 1),\n );\n }, [items.length]);\n const lightboxPrev = useCallback(() => {\n setLightbox((cur) => (cur == null ? cur : Math.max(0, cur - 1)));\n }, []);\n const lightboxClose = useCallback(() => setLightbox(null), []);\n\n useEffect(() => {\n if (lightbox == null) return;\n const handler = (e: globalThis.KeyboardEvent) => {\n if (e.key === \"Escape\") lightboxClose();\n else if (e.key === \"ArrowRight\") lightboxNext();\n else if (e.key === \"ArrowLeft\") lightboxPrev();\n };\n window.addEventListener(\"keydown\", handler);\n return () => window.removeEventListener(\"keydown\", handler);\n }, [lightbox, lightboxClose, lightboxNext, lightboxPrev]);\n\n const onGridKeyDown = useCallback(\n (e: KeyboardEvent<HTMLDivElement>) => {\n if (disabled) return;\n if (e.key === \"Enter\" || e.key === \" \") {\n if (e.target === e.currentTarget) {\n e.preventDefault();\n onPick();\n }\n }\n },\n [disabled, onPick],\n );\n\n const isDisplay = mode === \"display\";\n\n const sortableIds = useMemo(() => items.map((f) => f.id), [items]);\n\n return (\n <div data-testid={`file-widget-${fieldName}`} className=\"flex flex-col gap-2\">\n {!isDisplay && selected.size > 0 && (\n <div\n className=\"flex items-center gap-2 rounded-md border border-border bg-surface-2 p-2 text-sm\"\n role=\"toolbar\"\n aria-label=\"Bulk actions\"\n >\n <span className=\"text-foreground\">{selected.size} selected</span>\n <button\n type=\"button\"\n className=\"rounded-sm border border-border bg-card px-2 py-1 text-xs text-destructive hover:bg-surface-2\"\n onClick={handleBulkDelete}\n data-testid=\"bulk-delete\"\n >\n Delete\n </button>\n <button\n type=\"button\"\n className=\"rounded-sm border border-border bg-card px-2 py-1 text-xs text-foreground hover:bg-surface-2\"\n onClick={() => setSelected(new Set())}\n >\n Clear\n </button>\n </div>\n )}\n\n <DndContext\n sensors={sensors}\n collisionDetection={closestCenter}\n onDragEnd={handleDragEnd}\n >\n <SortableContext items={sortableIds} strategy={rectSortingStrategy}>\n <div\n className=\"grid grid-cols-[repeat(auto-fill,minmax(140px,1fr))] gap-3 rounded-md border border-dashed border-border bg-surface p-3 outline-none focus-visible:outline-2 focus-visible:outline-accent\"\n role=\"grid\"\n aria-label={ariaLabel}\n tabIndex={isDisplay || disabled ? -1 : 0}\n onDragOver={onDragOver}\n onDrop={onDrop}\n onKeyDown={onGridKeyDown}\n data-testid=\"grid-container\"\n >\n {items.map((f, idx) => (\n <SortableTile\n key={f.id}\n file={f}\n selected={selected.has(f.id)}\n disabled={disabled || isDisplay}\n reorderable={reorderable && !isDisplay}\n preferredThumbnail={preferredThumbnail}\n onSelect={() => toggleSelect(f.id)}\n onRemove={() => handleRemove(f.id)}\n onPreview={() => setLightbox(idx)}\n />\n ))}\n {tiles.map((t) => (\n <PendingTile\n key={t.localId}\n tile={t}\n onCancel={() => {\n t.abort();\n cancelledUploadsRef.current.add(t.localId);\n setTiles((prev) =>\n prev.filter((x) => x.localId !== t.localId),\n );\n }}\n onDismiss={() =>\n setTiles((prev) =>\n prev.filter((x) => x.localId !== t.localId),\n )\n }\n />\n ))}\n {items.length === 0 && tiles.length === 0 && (\n <div\n className=\"col-span-full flex h-24 cursor-pointer items-center justify-center text-sm text-muted-foreground\"\n onClick={onPick}\n data-testid=\"grid-empty\"\n >\n {field.display?.placeholder || \"Drop files here or click to browse\"}\n </div>\n )}\n </div>\n </SortableContext>\n </DndContext>\n\n {!isDisplay && (\n <input\n ref={fileInputRef}\n type=\"file\"\n accept={accept}\n multiple\n onChange={onChangeFile}\n style={{ display: \"none\" }}\n data-testid={`file-input-${fieldName}`}\n />\n )}\n\n {lightboxFile && (\n <Lightbox\n file={lightboxFile}\n hasPrev={lightbox! > 0}\n hasNext={lightbox! < items.length - 1}\n onPrev={lightboxPrev}\n onNext={lightboxNext}\n onClose={lightboxClose}\n />\n )}\n </div>\n );\n}\n\nfunction SortableTile({\n file,\n selected,\n disabled,\n reorderable,\n preferredThumbnail,\n onSelect,\n onRemove,\n onPreview,\n}: {\n file: HydratedFile;\n selected: boolean;\n disabled?: boolean;\n reorderable: boolean;\n preferredThumbnail?: string;\n onSelect: () => void;\n onRemove: () => void;\n onPreview: () => void;\n}) {\n const {\n attributes,\n listeners,\n setNodeRef,\n transform,\n transition,\n isDragging,\n } = useSortable({ id: file.id, disabled: !reorderable });\n const style: CSSProperties = {\n transform: CSS.Transform.toString(transform),\n transition,\n opacity: isDragging ? 0.5 : 1,\n };\n // Cookie mode: `thumbUrl` is the static thumbnail URL, returned unchanged.\n // Embed mode: resolved through the Bearer-authenticated file URL endpoint.\n const url = useEmbedFileSrc(\n file.id,\n preferredThumbnailVariant(file, preferredThumbnail),\n thumbUrl(file, preferredThumbnail),\n );\n return (\n <div\n ref={setNodeRef}\n style={style}\n className={`group relative aspect-square overflow-hidden rounded-md border ${\n selected ? \"border-accent\" : \"border-border\"\n } bg-surface-2`}\n data-testid={`grid-tile-${file.id}`}\n data-selected={selected || undefined}\n >\n {/* Lazy thumb: native loading=\"lazy\" approximates IntersectionObserver\n and is what the rest of the SDK uses (see ImageWidget). */}\n {url ? (\n <img\n src={url}\n alt={file.filename}\n className=\"h-full w-full cursor-pointer object-cover\"\n loading=\"lazy\"\n onClick={onPreview}\n />\n ) : (\n <div\n className=\"flex h-full w-full cursor-pointer items-center justify-center bg-surface-3 text-xs text-muted-foreground\"\n onClick={onPreview}\n >\n {file.filename}\n </div>\n )}\n {!disabled && (\n <>\n <input\n type=\"checkbox\"\n className=\"absolute left-1 top-1 h-4 w-4 cursor-pointer\"\n checked={selected}\n onChange={onSelect}\n aria-label={`Select ${file.filename}`}\n data-testid={`select-${file.id}`}\n onClick={(e) => e.stopPropagation()}\n />\n {reorderable && (\n <button\n type=\"button\"\n className=\"absolute right-1 top-1 cursor-grab rounded-sm border border-border bg-card px-1 text-xs\"\n aria-label={`Drag to reorder ${file.filename}`}\n data-testid={`drag-${file.id}`}\n {...attributes}\n {...listeners}\n >\n ⋮⋮\n </button>\n )}\n <div className=\"absolute inset-x-0 bottom-0 flex items-center justify-end gap-1 bg-black/50 p-1 opacity-0 transition-opacity group-hover:opacity-100 group-focus-within:opacity-100\">\n <button\n type=\"button\"\n className=\"rounded-sm border border-border bg-card px-2 py-0.5 text-xs\"\n onClick={onPreview}\n >\n View\n </button>\n <button\n type=\"button\"\n className=\"rounded-sm border border-border bg-card px-2 py-0.5 text-xs text-destructive\"\n onClick={onRemove}\n data-testid={`remove-${file.id}`}\n >\n Delete\n </button>\n </div>\n </>\n )}\n </div>\n );\n}\n\nfunction PendingTile({\n tile,\n onCancel,\n onDismiss,\n}: {\n tile: InFlightTile;\n onCancel: () => void;\n onDismiss: () => void;\n}) {\n return (\n <div\n className={`relative flex aspect-square flex-col items-center justify-center rounded-md border ${\n tile.error ? \"border-destructive\" : \"border-border\"\n } bg-surface-2 p-2 text-center text-xs`}\n data-testid={`pending-tile-${tile.localId}`}\n >\n <span className=\"truncate\" title={tile.filename}>\n {tile.filename}\n </span>\n {tile.progress && !tile.error && (\n <span\n className=\"mt-1 text-muted-foreground\"\n role=\"progressbar\"\n aria-valuenow={tile.progress.pct}\n aria-valuemin={0}\n aria-valuemax={100}\n >\n {tile.progress.pct}%\n </span>\n )}\n {tile.error && (\n <div role=\"alert\" className=\"mt-1 text-destructive\">\n {tile.error.message}\n </div>\n )}\n <button\n type=\"button\"\n className=\"mt-1 rounded-sm border border-border bg-card px-2 py-0.5\"\n onClick={tile.error ? onDismiss : onCancel}\n >\n {tile.error ? \"Dismiss\" : \"Cancel\"}\n </button>\n </div>\n );\n}\n\nfunction Lightbox({\n file,\n hasPrev,\n hasNext,\n onPrev,\n onNext,\n onClose,\n}: {\n file: HydratedFile;\n hasPrev: boolean;\n hasNext: boolean;\n onPrev: () => void;\n onNext: () => void;\n onClose: () => void;\n}) {\n // The lightbox shows the full image. Cookie mode: the static primary URL.\n // Embed mode: the primary file resolved through the embed file URL endpoint.\n const url = useEmbedFileSrc(\n file.id,\n \"preview\",\n file.presigned_url ?? file.url ?? thumbUrl(file),\n );\n return (\n <div\n className=\"fixed inset-0 z-50 flex items-center justify-center bg-black/80\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label={file.filename}\n data-testid=\"lightbox\"\n onClick={onClose}\n >\n <button\n type=\"button\"\n className=\"absolute right-4 top-4 rounded-sm border border-border bg-card px-3 py-1 text-foreground\"\n onClick={(e) => {\n e.stopPropagation();\n onClose();\n }}\n data-testid=\"lightbox-close\"\n >\n Close\n </button>\n {hasPrev && (\n <button\n type=\"button\"\n className=\"absolute left-4 rounded-sm border border-border bg-card px-3 py-2 text-foreground\"\n onClick={(e) => {\n e.stopPropagation();\n onPrev();\n }}\n data-testid=\"lightbox-prev\"\n >\n ‹\n </button>\n )}\n {hasNext && (\n <button\n type=\"button\"\n className=\"absolute right-4 bottom-4 rounded-sm border border-border bg-card px-3 py-2 text-foreground\"\n onClick={(e) => {\n e.stopPropagation();\n onNext();\n }}\n data-testid=\"lightbox-next\"\n >\n ›\n </button>\n )}\n {url ? (\n <img\n src={url}\n alt={file.filename}\n className=\"max-h-[90vh] max-w-[90vw] object-contain\"\n onClick={(e) => e.stopPropagation()}\n />\n ) : (\n <div className=\"rounded-md bg-card p-4 text-foreground\">\n {file.filename} ({formatBytes(file.size_bytes)})\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -11,7 +11,31 @@ function u(e) {
|
|
|
11
11
|
function d(e) {
|
|
12
12
|
return e.startsWith("/") || e.startsWith("https://") || e.startsWith("http://") ? e : null;
|
|
13
13
|
}
|
|
14
|
-
function f(
|
|
14
|
+
function f(e) {
|
|
15
|
+
if (!e) return !1;
|
|
16
|
+
if (e.startsWith("//")) return !0;
|
|
17
|
+
let t = typeof window < "u" ? window.location.href : "http://localhost/", n;
|
|
18
|
+
try {
|
|
19
|
+
n = new URL(e, t);
|
|
20
|
+
} catch {
|
|
21
|
+
return !1;
|
|
22
|
+
}
|
|
23
|
+
if (n.protocol !== "http:" && n.protocol !== "https:") return !0;
|
|
24
|
+
let r = typeof window < "u" ? window.location.origin : "http://localhost";
|
|
25
|
+
return n.origin !== r;
|
|
26
|
+
}
|
|
27
|
+
function p({ href: e, children: n }) {
|
|
28
|
+
return f(e) ? /* @__PURE__ */ t("a", {
|
|
29
|
+
href: e,
|
|
30
|
+
target: "_blank",
|
|
31
|
+
rel: "noopener noreferrer",
|
|
32
|
+
children: n
|
|
33
|
+
}) : /* @__PURE__ */ t("a", {
|
|
34
|
+
href: e,
|
|
35
|
+
children: n
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
function m(r) {
|
|
15
39
|
return {
|
|
16
40
|
img({ src: i, alt: a }) {
|
|
17
41
|
let o = e(i);
|
|
@@ -33,7 +57,7 @@ function f(r) {
|
|
|
33
57
|
},
|
|
34
58
|
a({ href: i, children: a }) {
|
|
35
59
|
let o = e(i);
|
|
36
|
-
if (!o) return /* @__PURE__ */ t(
|
|
60
|
+
if (!o) return /* @__PURE__ */ t(p, {
|
|
37
61
|
href: i,
|
|
38
62
|
children: a
|
|
39
63
|
});
|
|
@@ -53,23 +77,24 @@ function f(r) {
|
|
|
53
77
|
}
|
|
54
78
|
};
|
|
55
79
|
}
|
|
56
|
-
function
|
|
57
|
-
let
|
|
80
|
+
function h({ content: e, plugins: n = "breaks", files: i, components: a, skipHtml: o = !0, prose: d = !0 }) {
|
|
81
|
+
let f = /* @__PURE__ */ t(r, {
|
|
58
82
|
skipHtml: o,
|
|
59
83
|
remarkPlugins: n === "gfm" ? l : c,
|
|
60
|
-
components:
|
|
61
|
-
|
|
84
|
+
components: {
|
|
85
|
+
a: p,
|
|
86
|
+
...i ? m(i) : {},
|
|
62
87
|
...a
|
|
63
|
-
}
|
|
88
|
+
},
|
|
64
89
|
urlTransform: i ? u : void 0,
|
|
65
90
|
children: e
|
|
66
91
|
});
|
|
67
92
|
return d ? /* @__PURE__ */ t("div", {
|
|
68
93
|
className: s,
|
|
69
|
-
children:
|
|
70
|
-
}) :
|
|
94
|
+
children: f
|
|
95
|
+
}) : f;
|
|
71
96
|
}
|
|
72
97
|
//#endregion
|
|
73
|
-
export {
|
|
98
|
+
export { h as MarkdownRenderer };
|
|
74
99
|
|
|
75
|
-
//# sourceMappingURL=MarkdownRenderer-
|
|
100
|
+
//# sourceMappingURL=MarkdownRenderer-Dbl3AFDQ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MarkdownRenderer-Dbl3AFDQ.js","names":[],"sources":["../src/components/shared/MarkdownRenderer.tsx"],"sourcesContent":["// MarkdownRenderer: the heavy markdown rendering path. This module owns ALL\n// react-markdown / remark imports, so the renderer and the remark/micromark\n// parser (~120 kB) land in their own lazy chunk and never the eager SDK\n// bundle. Reached only through the lazy `Markdown` wrapper (Markdown.tsx) -\n// never import this file directly from feature code.\nimport type { ReactNode } from \"react\";\nimport ReactMarkdown, { defaultUrlTransform, type Components } from \"react-markdown\";\nimport remarkBreaks from \"remark-breaks\";\nimport remarkGfm from \"remark-gfm\";\nimport type { PluggableList } from \"unified\";\nimport type { HydratedFile } from \"@/types/files\";\nimport { extractDeclarionFileId } from \"@/lib/rich-text\";\n\n// Standard prose container. Before this consolidation TextField, RichTextField\n// and the markdown cell factories each repeated this exact class string.\nconst PROSE_CLASS =\n \"prose prose-sm dark:prose-invert max-w-none break-words overflow-hidden\";\n\nconst REMARK_BREAKS: PluggableList = [remarkBreaks];\nconst REMARK_GFM: PluggableList = [remarkGfm];\n\nexport interface MarkdownRendererProps {\n content: string;\n // Plugin set: \"breaks\" (remark-breaks, default) or \"gfm\" (remark-gfm: tables,\n // strikethrough, autolinks). One enum, not a plugin array, so callers never\n // import remark packages and pull them back into the eager bundle.\n plugins?: \"breaks\" | \"gfm\";\n // Inline-file map. When provided, declarion://file/<id> refs in links and\n // images resolve to the file URL; unresolved refs render a warning.\n files?: Record<string, HydratedFile>;\n // Component overrides merged over the defaults (e.g. chat code styling).\n components?: Components;\n // Strip raw HTML. Default true; pass false only with security review.\n skipHtml?: boolean;\n // Wrap output in the standard prose container. Default true.\n prose?: boolean;\n}\n\n// declarion://file/<id> refs must survive urlTransform so the file-aware\n// img/a components can resolve them; everything else goes through\n// react-markdown's defaultUrlTransform (which strips javascript: URIs).\nfunction fileUrlTransform(url: string): string {\n if (url.startsWith(\"declarion://file/\")) return url;\n return defaultUrlTransform(url);\n}\n\n// Hydrated file URLs come from the server — either a relative\n// `/api/files/{id}/download` (proxy path) or an absolute presigned\n// `https://...` from S3/SeaweedFS. Defence-in-depth scheme allow-list:\n// reject anything else before rendering so a future server-side bug\n// that produced `javascript:` or `data:` cannot become an XSS sink\n// through the file-aware <img>/<a> path that bypasses urlTransform.\n// Decision 2026-05-27 security followup MEDIUM 6.\nfunction safeFileUrl(url: string): string | null {\n if (url.startsWith(\"/\")) return url; // same-origin relative\n if (url.startsWith(\"https://\") || url.startsWith(\"http://\")) return url;\n return null;\n}\n\n// An href is \"external\" exactly when the global link interceptor will NOT\n// handle it: a cross-origin URL, a protocol-relative URL, or a non-http scheme\n// (mailto:, tel:, ...). Those open in a new window so a cross-origin link can\n// never navigate the embed iframe away from Declarion. Same-origin links -\n// whether relative (/cases/5) OR absolute (https://<this-origin>/cases/5) -\n// are INTERNAL: they stay plain <a href> so the interceptor routes them in-app.\n// This mirrors the interceptor's own predicate (same-origin http(s) only).\nfunction isExternalHref(href: string | undefined): boolean {\n if (!href) return false;\n if (href.startsWith(\"//\")) return true; // protocol-relative\n const base =\n typeof window !== \"undefined\" ? window.location.href : \"http://localhost/\";\n let url: URL;\n try {\n url = new URL(href, base);\n } catch {\n return false;\n }\n if (url.protocol !== \"http:\" && url.protocol !== \"https:\") return true;\n const origin =\n typeof window !== \"undefined\" ? window.location.origin : \"http://localhost\";\n return url.origin !== origin;\n}\n\n// The default markdown-link renderer: external links open a new window with\n// rel=\"noopener noreferrer\"; relative links render plain for the interceptor.\nfunction MarkdownLink({ href, children }: { href?: string; children?: ReactNode }) {\n if (isExternalHref(href)) {\n return (\n <a href={href} target=\"_blank\" rel=\"noopener noreferrer\">\n {children}\n </a>\n );\n }\n return <a href={href}>{children}</a>;\n}\n\nfunction fileAwareComponents(files: Record<string, HydratedFile>): Components {\n return {\n img({ src, alt }: { src?: string; alt?: string }) {\n const fileId = extractDeclarionFileId(src);\n if (!fileId) return <img src={src} alt={alt ?? \"\"} />;\n const file = files[fileId];\n const rawUrl = file?.presigned_url ?? file?.url;\n const url = rawUrl ? safeFileUrl(rawUrl) : null;\n if (!url) {\n return <span style={{ color: \"var(--warn)\", fontSize: 12 }}>Missing image {fileId}</span>;\n }\n return <img src={url} alt={alt ?? file.filename ?? \"\"} />;\n },\n a({ href, children }: { href?: string; children?: ReactNode }) {\n const fileId = extractDeclarionFileId(href);\n if (!fileId) return <MarkdownLink href={href}>{children}</MarkdownLink>;\n const file = files[fileId];\n const rawUrl = file?.presigned_url ?? file?.url;\n const url = rawUrl ? safeFileUrl(rawUrl) : null;\n if (!url) {\n return <span style={{ color: \"var(--warn)\", fontSize: 12 }}>Missing file {fileId}</span>;\n }\n return (\n <a href={url} target=\"_blank\" rel=\"noreferrer\">\n {children}\n </a>\n );\n },\n };\n}\n\nexport function MarkdownRenderer({\n content,\n plugins = \"breaks\",\n files,\n components,\n skipHtml = true,\n prose = true,\n}: MarkdownRendererProps) {\n const remarkPlugins = plugins === \"gfm\" ? REMARK_GFM : REMARK_BREAKS;\n // Always apply the external-aware link renderer as the base so cross-origin\n // markdown links open a new window even when no file map is supplied; the\n // file-aware `a` (when files are present) overrides it and falls back to the\n // same MarkdownLink for non-file links.\n const merged: Components = {\n a: MarkdownLink,\n ...(files ? fileAwareComponents(files) : {}),\n ...components,\n };\n const rendered = (\n <ReactMarkdown\n skipHtml={skipHtml}\n remarkPlugins={remarkPlugins}\n components={merged}\n urlTransform={files ? fileUrlTransform : undefined}\n >\n {content}\n </ReactMarkdown>\n );\n return prose ? <div className={PROSE_CLASS}>{rendered}</div> : rendered;\n}\n"],"mappings":";;;;;;AAeA,IAAM,IACJ,2EAEI,IAA+B,CAAC,CAAY,GAC5C,IAA4B,CAAC,CAAS;AAsB5C,SAAS,EAAiB,GAAqB;CAE7C,OADI,EAAI,WAAW,mBAAmB,IAAU,IACzC,EAAoB,CAAG;AAChC;AASA,SAAS,EAAY,GAA4B;CAG/C,OAFI,EAAI,WAAW,GAAG,KAClB,EAAI,WAAW,UAAU,KAAK,EAAI,WAAW,SAAS,IAAU,IAC7D;AACT;AASA,SAAS,EAAe,GAAmC;CACzD,IAAI,CAAC,GAAM,OAAO;CAClB,IAAI,EAAK,WAAW,IAAI,GAAG,OAAO;CAClC,IAAM,IACJ,OAAO,SAAW,MAAc,OAAO,SAAS,OAAO,qBACrD;CACJ,IAAI;EACF,IAAM,IAAI,IAAI,GAAM,CAAI;CAC1B,QAAQ;EACN,OAAO;CACT;CACA,IAAI,EAAI,aAAa,WAAW,EAAI,aAAa,UAAU,OAAO;CAClE,IAAM,IACJ,OAAO,SAAW,MAAc,OAAO,SAAS,SAAS;CAC3D,OAAO,EAAI,WAAW;AACxB;AAIA,SAAS,EAAa,EAAE,SAAM,eAAqD;CAQjF,OAPI,EAAe,CAAI,IAEnB,kBAAC,KAAD;EAAS;EAAM,QAAO;EAAS,KAAI;EAChC;CACA,CAAA,IAGA,kBAAC,KAAD;EAAS;EAAO;CAAY,CAAA;AACrC;AAEA,SAAS,EAAoB,GAAiD;CAC5E,OAAO;EACL,IAAI,EAAE,QAAK,UAAuC;GAChD,IAAM,IAAS,EAAuB,CAAG;GACzC,IAAI,CAAC,GAAQ,OAAO,kBAAC,OAAD;IAAU;IAAK,KAAK,KAAO;GAAK,CAAA;GACpD,IAAM,IAAO,EAAM,IACb,IAAS,GAAM,iBAAiB,GAAM,KACtC,IAAM,IAAS,EAAY,CAAM,IAAI;GAI3C,OAHK,IAGE,kBAAC,OAAD;IAAK,KAAK;IAAK,KAAK,KAAO,EAAK,YAAY;GAAK,CAAA,IAF/C,kBAAC,QAAD;IAAM,OAAO;KAAE,OAAO;KAAe,UAAU;IAAG;cAAlD,CAAqD,kBAAe,CAAa;;EAG5F;EACA,EAAE,EAAE,SAAM,eAAqD;GAC7D,IAAM,IAAS,EAAuB,CAAI;GAC1C,IAAI,CAAC,GAAQ,OAAO,kBAAC,GAAD;IAAoB;IAAO;GAAuB,CAAA;GACtE,IAAM,IAAO,EAAM,IACb,IAAS,GAAM,iBAAiB,GAAM,KACtC,IAAM,IAAS,EAAY,CAAM,IAAI;GAI3C,OAHK,IAIH,kBAAC,KAAD;IAAG,MAAM;IAAK,QAAO;IAAS,KAAI;IAC/B;GACA,CAAA,IALI,kBAAC,QAAD;IAAM,OAAO;KAAE,OAAO;KAAe,UAAU;IAAG;cAAlD,CAAqD,iBAAc,CAAa;;EAO3F;CACF;AACF;AAEA,SAAgB,EAAiB,EAC/B,YACA,aAAU,UACV,UACA,eACA,cAAW,IACX,WAAQ,MACgB;CAWxB,IAAM,IACJ,kBAAC,GAAD;EACY;EACK,eAbG,MAAY,QAAQ,IAAa;EAcnD,YAAY;GARd,GAAG;GACH,GAAI,IAAQ,EAAoB,CAAK,IAAI,CAAC;GAC1C,GAAG;EAMW;EACZ,cAAc,IAAQ,IAAmB,KAAA;YAExC;CACY,CAAA;CAEjB,OAAO,IAAQ,kBAAC,OAAD;EAAK,WAAW;YAAc;CAAc,CAAA,IAAI;AACjE"}
|
package/dist-lib/app.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare function App(): import("react
|
|
1
|
+
export declare function App(): import("react").JSX.Element;
|
|
@@ -4,4 +4,4 @@ export interface AgentChatPanelProps {
|
|
|
4
4
|
context?: string;
|
|
5
5
|
title?: string;
|
|
6
6
|
}
|
|
7
|
-
export declare function AgentChatPanel({ open, onClose, context, title }: AgentChatPanelProps): import("react
|
|
7
|
+
export declare function AgentChatPanel({ open, onClose, context, title }: AgentChatPanelProps): import("react").JSX.Element | null;
|