@broadpaper/editor 0.2.1 → 0.4.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/README.md +12 -9
- package/dist/index.d.ts +41 -9
- package/dist/index.js +877 -361
- package/dist/index.js.map +1 -1
- package/dist/styles.css +51 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -300,6 +300,11 @@ var EditorSession = class {
|
|
|
300
300
|
if (seq !== this.layoutSeq || this.disposed) return;
|
|
301
301
|
this.layout.set(result);
|
|
302
302
|
this.layoutVersion.set((v) => v + 1);
|
|
303
|
+
if (m.settled) {
|
|
304
|
+
void m.settled().then((arrived) => {
|
|
305
|
+
if (arrived && seq === this.layoutSeq && !this.disposed) this.requestLayout();
|
|
306
|
+
});
|
|
307
|
+
}
|
|
303
308
|
} catch (e) {
|
|
304
309
|
console.error("[broadpaper] layout failed", e);
|
|
305
310
|
}
|
|
@@ -389,7 +394,7 @@ function loadTemplate(input) {
|
|
|
389
394
|
}
|
|
390
395
|
|
|
391
396
|
// src/ReportDesigner.tsx
|
|
392
|
-
import { useEffect as
|
|
397
|
+
import { useEffect as useEffect8, useMemo as useMemo9, useRef as useRef10, useState as useState19 } from "react";
|
|
393
398
|
|
|
394
399
|
// src/state/context.tsx
|
|
395
400
|
import { createContext, useContext, useSyncExternalStore as useSyncExternalStore2, useMemo } from "react";
|
|
@@ -892,6 +897,28 @@ function createActions(session) {
|
|
|
892
897
|
session.reveal(node.id);
|
|
893
898
|
return node.id;
|
|
894
899
|
},
|
|
900
|
+
/**
|
|
901
|
+
* Puts a data field on the page.
|
|
902
|
+
*
|
|
903
|
+
* Shared with the drop handler on purpose: clicking a field in the data
|
|
904
|
+
* panel and dragging it onto the page have to produce the same block, and
|
|
905
|
+
* they did not when each built its own props. The block itself decides what
|
|
906
|
+
* to build, through its `fromField` hook, so a list becomes a finished
|
|
907
|
+
* table and a custom block can answer for itself.
|
|
908
|
+
*/
|
|
909
|
+
insertField(field, at) {
|
|
910
|
+
const type = field.type === "array" ? "table" : "field";
|
|
911
|
+
const def = session.registry.get(type);
|
|
912
|
+
const props = def?.fromField?.({
|
|
913
|
+
expr: field.expr,
|
|
914
|
+
formatted: field.bound ?? field.expr,
|
|
915
|
+
schema: field.schema ?? { type: field.type ?? "string" },
|
|
916
|
+
label: field.label
|
|
917
|
+
});
|
|
918
|
+
const leaf = field.label.split(" \u203A ").pop() ?? field.label;
|
|
919
|
+
const fallback = type === "table" ? { source: field.expr } : { label: leaf, value: field.bound ?? field.expr };
|
|
920
|
+
return this.insertBlock(type, at, props ?? fallback);
|
|
921
|
+
},
|
|
895
922
|
insertRow(layout, at) {
|
|
896
923
|
const row = createRow(layout);
|
|
897
924
|
store.transaction("Add columns", () => store.dispatch({ type: "insertNode", node: row, at: at ?? insertionPoint() }));
|
|
@@ -1677,7 +1704,7 @@ function IssueGroup({ title, blurb, emptyTitle, emptyBody, issues, close, sessio
|
|
|
1677
1704
|
|
|
1678
1705
|
// src/panels/LeftPanel.tsx
|
|
1679
1706
|
import { useMemo as useMemo2, useState as useState5 } from "react";
|
|
1680
|
-
import { ROW_LAYOUTS } from "@broadpaper/core";
|
|
1707
|
+
import { ROW_LAYOUTS, withDefaultPipe } from "@broadpaper/core";
|
|
1681
1708
|
|
|
1682
1709
|
// src/dnd/drag-manager.ts
|
|
1683
1710
|
var handlers = /* @__PURE__ */ new WeakMap();
|
|
@@ -1902,7 +1929,7 @@ function buildNodes(fields, prefix, labels) {
|
|
|
1902
1929
|
const expr = prefix + "." + key;
|
|
1903
1930
|
const label = [...labels, name].join(" \u203A ");
|
|
1904
1931
|
if (f.type === "object" && f.fields) {
|
|
1905
|
-
return { key, label, name, type: f.type, kind: "group", children: buildNodes(f.fields, expr, [...labels, name]) };
|
|
1932
|
+
return { key, label, name, type: f.type, schema: f, kind: "group", children: buildNodes(f.fields, expr, [...labels, name]) };
|
|
1906
1933
|
}
|
|
1907
1934
|
if (f.type === "array" && f.itemSchema?.type === "object" && f.itemSchema.fields) {
|
|
1908
1935
|
const children = Object.entries(f.itemSchema.fields).map(([k2, f2]) => ({
|
|
@@ -1910,13 +1937,14 @@ function buildNodes(fields, prefix, labels) {
|
|
|
1910
1937
|
label: [...labels, name, f2.label ?? k2].join(" \u203A "),
|
|
1911
1938
|
name: f2.label ?? k2,
|
|
1912
1939
|
type: f2.type,
|
|
1940
|
+
schema: f2,
|
|
1913
1941
|
itemOf: name,
|
|
1914
1942
|
kind: "field",
|
|
1915
1943
|
children: []
|
|
1916
1944
|
}));
|
|
1917
|
-
return { key, label, name, type: f.type, expr, kind: "list", children };
|
|
1945
|
+
return { key, label, name, type: f.type, schema: f, expr, kind: "list", children };
|
|
1918
1946
|
}
|
|
1919
|
-
return { key, label, name, type: f.type, expr, kind: "field", children: [] };
|
|
1947
|
+
return { key, label, name, type: f.type, schema: f, expr, kind: "field", children: [] };
|
|
1920
1948
|
});
|
|
1921
1949
|
}
|
|
1922
1950
|
function filterNodes(nodes, q) {
|
|
@@ -1987,6 +2015,7 @@ function FieldList({ nodes, actions, depth = 0 }) {
|
|
|
1987
2015
|
}
|
|
1988
2016
|
const expr = n.expr;
|
|
1989
2017
|
const isList = n.kind === "list";
|
|
2018
|
+
const bound = isList ? expr : withDefaultPipe(expr, n.schema);
|
|
1990
2019
|
return /* @__PURE__ */ jsxs5("div", { children: [
|
|
1991
2020
|
/* @__PURE__ */ jsxs5(
|
|
1992
2021
|
"button",
|
|
@@ -1994,8 +2023,8 @@ function FieldList({ nodes, actions, depth = 0 }) {
|
|
|
1994
2023
|
type: "button",
|
|
1995
2024
|
className: "bpe-data-field",
|
|
1996
2025
|
title: n.label + "\n" + expr + "\nClick to insert, or drag onto the page.",
|
|
1997
|
-
onPointerDown: (e) => beginDrag(session, e, { kind: "field", payload: { expr, label: n.label, type: n.type }, label: n.name, icon: FIELD_TYPE_ICONS[n.type] }),
|
|
1998
|
-
onClick: () =>
|
|
2026
|
+
onPointerDown: (e) => beginDrag(session, e, { kind: "field", payload: { expr, bound, label: n.label, type: n.type, schema: n.schema }, label: n.name, icon: FIELD_TYPE_ICONS[n.type] }),
|
|
2027
|
+
onClick: () => actions.insertField({ expr, bound, label: n.label, type: n.type, schema: n.schema }),
|
|
1999
2028
|
children: [
|
|
2000
2029
|
/* @__PURE__ */ jsx6(Icon, { name: FIELD_TYPE_ICONS[n.type] ?? "box", className: "bpe-data-field__icon" }),
|
|
2001
2030
|
/* @__PURE__ */ jsx6("span", { className: "bpe-truncate", children: n.name }),
|
|
@@ -2698,7 +2727,7 @@ import { useEffect as useEffect3, useImperativeHandle, useMemo as useMemo4, useR
|
|
|
2698
2727
|
|
|
2699
2728
|
// src/inspector/DataPicker.tsx
|
|
2700
2729
|
import { useMemo as useMemo3, useState as useState7 } from "react";
|
|
2701
|
-
import { GLOBAL_SCHEMAS, PIPES, checkExpressionSyntax } from "@broadpaper/core";
|
|
2730
|
+
import { GLOBAL_SCHEMAS, PIPES, checkExpressionSyntax, humanizeKey } from "@broadpaper/core";
|
|
2702
2731
|
import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
2703
2732
|
function flattenSchemas(dataSources, extra = {}, opts = {}) {
|
|
2704
2733
|
const out = [];
|
|
@@ -2721,6 +2750,19 @@ function flattenSchemas(dataSources, extra = {}, opts = {}) {
|
|
|
2721
2750
|
if (name === "theme") continue;
|
|
2722
2751
|
visit(name, schema, [schema.label ?? name], 0, "Document");
|
|
2723
2752
|
}
|
|
2753
|
+
for (const key of Object.keys(opts.themeMeta ?? {})) {
|
|
2754
|
+
const value = (opts.themeMeta ?? {})[key];
|
|
2755
|
+
if (value !== null && typeof value === "object") continue;
|
|
2756
|
+
out.push({
|
|
2757
|
+
expr: `theme.meta.${key}`,
|
|
2758
|
+
label: humanizeKey(key),
|
|
2759
|
+
path: ["Your brand", humanizeKey(key)],
|
|
2760
|
+
type: "string",
|
|
2761
|
+
schema: { type: "string", label: humanizeKey(key) },
|
|
2762
|
+
depth: 1,
|
|
2763
|
+
group: "Your brand"
|
|
2764
|
+
});
|
|
2765
|
+
}
|
|
2724
2766
|
}
|
|
2725
2767
|
return out;
|
|
2726
2768
|
}
|
|
@@ -2738,7 +2780,11 @@ function DataPicker({ dataSources, scopeVars, arraysOnly, types, value, onPick,
|
|
|
2738
2780
|
const [q, setQ] = useState7("");
|
|
2739
2781
|
const [expr, setExpr] = useState7(value ?? "");
|
|
2740
2782
|
const [pending, setPending] = useState7(null);
|
|
2741
|
-
const
|
|
2783
|
+
const themeMeta = useSession().activeTheme.meta;
|
|
2784
|
+
const entries = useMemo3(
|
|
2785
|
+
() => flattenSchemas(dataSources, scopeVars, { arraysOnly, types, ...themeMeta ? { themeMeta } : {} }),
|
|
2786
|
+
[dataSources, scopeVars, arraysOnly, types, themeMeta]
|
|
2787
|
+
);
|
|
2742
2788
|
const filtered = useMemo3(() => {
|
|
2743
2789
|
const needle = q.trim().toLowerCase();
|
|
2744
2790
|
if (!needle) return entries;
|
|
@@ -2767,10 +2813,10 @@ function DataPicker({ dataSources, scopeVars, arraysOnly, types, value, onPick,
|
|
|
2767
2813
|
/* @__PURE__ */ jsx9(Icon, { name: "wand", className: "bpe-data-field__icon" }),
|
|
2768
2814
|
" ",
|
|
2769
2815
|
s.label,
|
|
2770
|
-
/* @__PURE__ */ jsxs8("span", { className: "bpe-data-field__type bpe-mono", style: { textTransform: "none" }, children: [
|
|
2816
|
+
allowExpression ? /* @__PURE__ */ jsxs8("span", { className: "bpe-data-field__type bpe-mono", style: { textTransform: "none" }, children: [
|
|
2771
2817
|
"| ",
|
|
2772
2818
|
s.pipe
|
|
2773
|
-
] })
|
|
2819
|
+
] }) : null
|
|
2774
2820
|
] }, s.pipe)),
|
|
2775
2821
|
pending.type !== "array" && /* @__PURE__ */ jsxs8("button", { type: "button", className: "bpe-data-field", onClick: () => onPick(`${pending.expr} | default:"\u2014"`, pending.label, pending), children: [
|
|
2776
2822
|
/* @__PURE__ */ jsx9(Icon, { name: "eraser", className: "bpe-data-field__icon" }),
|
|
@@ -2785,7 +2831,7 @@ function DataPicker({ dataSources, scopeVars, arraysOnly, types, value, onPick,
|
|
|
2785
2831
|
groups.length === 0 ? /* @__PURE__ */ jsx9("div", { className: "bpe-empty", children: "No matching fields" }) : null,
|
|
2786
2832
|
groups.map(([group, items]) => /* @__PURE__ */ jsxs8("div", { children: [
|
|
2787
2833
|
/* @__PURE__ */ jsx9("div", { className: "bpe-picker__group", children: group }),
|
|
2788
|
-
items.map((e) => /* @__PURE__ */ jsxs8("button", { type: "button", role: "option", "aria-selected": value === e.expr, className: "bpe-data-field", style: { paddingLeft: 6 + Math.max(0, e.depth - 1) * 10 }, title: e.expr, onClick: () => arraysOnly || e.type === "array" || e.type === "object" ? onPick(e.expr, e.label, e) : setPending(e), children: [
|
|
2834
|
+
items.map((e) => /* @__PURE__ */ jsxs8("button", { type: "button", role: "option", "aria-selected": value === e.expr, className: "bpe-data-field", style: { paddingLeft: 6 + Math.max(0, e.depth - 1) * 10 }, title: allowExpression ? e.expr : e.label, onClick: () => arraysOnly || e.type === "array" || e.type === "object" ? onPick(e.expr, e.label, e) : setPending(e), children: [
|
|
2789
2835
|
/* @__PURE__ */ jsx9(Icon, { name: FIELD_TYPE_ICONS[e.type] ?? "box", className: "bpe-data-field__icon" }),
|
|
2790
2836
|
/* @__PURE__ */ jsx9("span", { className: "bpe-truncate", children: e.path[e.path.length - 1] }),
|
|
2791
2837
|
/* @__PURE__ */ jsx9("span", { className: "bpe-data-field__type", children: e.type })
|
|
@@ -3257,9 +3303,11 @@ function nodeForDrop(drag, ctx) {
|
|
|
3257
3303
|
return ctx.cloneLibrary(String(drag.payload));
|
|
3258
3304
|
case "field": {
|
|
3259
3305
|
const p = drag.payload;
|
|
3260
|
-
const
|
|
3261
|
-
|
|
3262
|
-
return ctx.createNode(
|
|
3306
|
+
const type = p.type === "array" ? "table" : "field";
|
|
3307
|
+
const props = ctx.registry.get(type)?.fromField?.({ expr: p.expr, formatted: p.bound ?? p.expr, schema: p.schema ?? { type: p.type ?? "string" }, label: p.label });
|
|
3308
|
+
if (props) return ctx.createNode(type, props);
|
|
3309
|
+
if (type === "table") return ctx.createNode("table", { source: p.expr });
|
|
3310
|
+
return ctx.createNode("field", { label: p.label.split(" \u203A ").pop() ?? p.label, value: p.bound ?? p.expr });
|
|
3263
3311
|
}
|
|
3264
3312
|
default:
|
|
3265
3313
|
return void 0;
|
|
@@ -3629,18 +3677,19 @@ function Canvas({ actions }) {
|
|
|
3629
3677
|
drop: (target, d) => {
|
|
3630
3678
|
if (target.textTargetId && d.kind === "field") {
|
|
3631
3679
|
const p = d.payload;
|
|
3680
|
+
const expr = p.bound ?? p.expr;
|
|
3632
3681
|
const node2 = session.store.getNode(target.textTargetId);
|
|
3633
3682
|
if (!node2) return;
|
|
3634
3683
|
if (node2.type === "heading") {
|
|
3635
3684
|
const text = String(node2.props.text ?? "");
|
|
3636
|
-
session.store.dispatch({ type: "setProp", id: node2.id, path: "text", value: `${text}${text && !text.endsWith(" ") ? " " : ""}{{ ${
|
|
3685
|
+
session.store.dispatch({ type: "setProp", id: node2.id, path: "text", value: `${text}${text && !text.endsWith(" ") ? " " : ""}{{ ${expr} }}` }, { label: "Insert field" });
|
|
3637
3686
|
} else {
|
|
3638
3687
|
const doc = isRichTextDoc2(node2.props.content) ? structuredClone(node2.props.content) : richTextFromTemplate2("");
|
|
3639
3688
|
const last = doc.content[doc.content.length - 1];
|
|
3640
3689
|
if (last && last.type === "paragraph") {
|
|
3641
3690
|
if (last.content.length) last.content.push({ type: "text", text: " " });
|
|
3642
|
-
last.content.push({ type: "token", expr
|
|
3643
|
-
} else doc.content.push({ type: "paragraph", content: [{ type: "token", expr
|
|
3691
|
+
last.content.push({ type: "token", expr });
|
|
3692
|
+
} else doc.content.push({ type: "paragraph", content: [{ type: "token", expr }] });
|
|
3644
3693
|
session.store.dispatch({ type: "setProp", id: node2.id, path: "content", value: doc }, { label: "Insert field" });
|
|
3645
3694
|
}
|
|
3646
3695
|
session.store.select([node2.id]);
|
|
@@ -3735,75 +3784,385 @@ function Canvas({ actions }) {
|
|
|
3735
3784
|
}
|
|
3736
3785
|
|
|
3737
3786
|
// src/inspector/NodeInspector.tsx
|
|
3738
|
-
import { useMemo as
|
|
3739
|
-
import { ROW_LAYOUTS as ROW_LAYOUTS2, itemSchemaOf, createSchemaScope as
|
|
3787
|
+
import { useMemo as useMemo8 } from "react";
|
|
3788
|
+
import { ROW_LAYOUTS as ROW_LAYOUTS2, itemSchemaOf, createSchemaScope as createSchemaScope7, ancestors as ancestors2 } from "@broadpaper/core";
|
|
3740
3789
|
|
|
3741
3790
|
// src/inspector/fields/FieldRenderer.tsx
|
|
3742
|
-
import { useEffect as
|
|
3743
|
-
import { checkExpressionSyntax as checkExpressionSyntax3, createSchemaScope as
|
|
3791
|
+
import { useEffect as useEffect7, useRef as useRef8, useState as useState17 } from "react";
|
|
3792
|
+
import { checkExpressionSyntax as checkExpressionSyntax3, createSchemaScope as createSchemaScope6, textStyleLabel, richTextToPlain as richTextToPlain2, richTextFromTemplate as richTextFromTemplate3, isRichTextDoc as isRichTextDoc3 } from "@broadpaper/core";
|
|
3744
3793
|
|
|
3745
3794
|
// src/inspector/fields/BindingField.tsx
|
|
3746
|
-
import { useMemo as
|
|
3747
|
-
import {
|
|
3795
|
+
import { useMemo as useMemo7, useState as useState13 } from "react";
|
|
3796
|
+
import {
|
|
3797
|
+
createSchemaScope as createSchemaScope4,
|
|
3798
|
+
describeExpression,
|
|
3799
|
+
fromExpression as fromExpression2,
|
|
3800
|
+
humaniseLabel as humaniseLabel2,
|
|
3801
|
+
parseExpression as parseExpression2,
|
|
3802
|
+
typeCheck
|
|
3803
|
+
} from "@broadpaper/core";
|
|
3804
|
+
|
|
3805
|
+
// src/inspector/fields/CalculationBuilder.tsx
|
|
3806
|
+
import { useEffect as useEffect6, useMemo as useMemo6, useRef as useRef6, useState as useState12 } from "react";
|
|
3807
|
+
import {
|
|
3808
|
+
createSchemaScope as createSchemaScope3,
|
|
3809
|
+
fromExpression,
|
|
3810
|
+
labelForPath as labelForPath2,
|
|
3811
|
+
toExpression
|
|
3812
|
+
} from "@broadpaper/core";
|
|
3748
3813
|
import { jsx as jsx16, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
3814
|
+
var NUMERIC = ["number", "currency", "percent"];
|
|
3815
|
+
var OPS = [
|
|
3816
|
+
{ value: "add", label: "add" },
|
|
3817
|
+
{ value: "subtract", label: "subtract" },
|
|
3818
|
+
{ value: "multiply", label: "multiply by" },
|
|
3819
|
+
{ value: "divide", label: "divide by" },
|
|
3820
|
+
{ value: "addPercent", label: "add, as a percentage" },
|
|
3821
|
+
{ value: "subtractPercent", label: "subtract, as a percentage" }
|
|
3822
|
+
];
|
|
3823
|
+
var AGGREGATES = [
|
|
3824
|
+
{ value: "sum", label: "Total of", of: "added up across every row" },
|
|
3825
|
+
{ value: "avg", label: "Average of", of: "the mean across every row" },
|
|
3826
|
+
{ value: "count", label: "How many", of: "how many rows there are" },
|
|
3827
|
+
{ value: "max", label: "Highest", of: "the largest in any row" },
|
|
3828
|
+
{ value: "min", label: "Lowest", of: "the smallest in any row" }
|
|
3829
|
+
];
|
|
3830
|
+
var EMPTY = { start: { kind: "field", path: "" }, steps: [] };
|
|
3831
|
+
function complete(calc) {
|
|
3832
|
+
const ok = (term) => term.kind === "number" || term.path.trim() !== "";
|
|
3833
|
+
return ok(calc.start) && calc.steps.every((s) => ok(s.term));
|
|
3834
|
+
}
|
|
3835
|
+
function CalculationBuilder({ value, onChange, dataSources, scopeVars, formats = [] }) {
|
|
3836
|
+
const entries = useMemo6(() => flattenSchemas(dataSources, scopeVars), [dataSources, scopeVars]);
|
|
3837
|
+
const scope = useMemo6(() => createSchemaScope3(dataSources).child(scopeVars ?? {}), [dataSources, scopeVars]);
|
|
3838
|
+
const [calc, setCalc] = useState12(() => fromExpression(value) ?? EMPTY);
|
|
3839
|
+
const mine = useRef6(null);
|
|
3840
|
+
useEffect6(() => {
|
|
3841
|
+
if (value !== mine.current) setCalc(fromExpression(value) ?? EMPTY);
|
|
3842
|
+
}, [value]);
|
|
3843
|
+
const commit = (next) => {
|
|
3844
|
+
setCalc(next);
|
|
3845
|
+
if (!complete(next)) return;
|
|
3846
|
+
const expr = toExpression(next);
|
|
3847
|
+
mine.current = expr;
|
|
3848
|
+
onChange(expr);
|
|
3849
|
+
};
|
|
3850
|
+
const naming = (term) => termLabel(term, entries, scope);
|
|
3851
|
+
return /* @__PURE__ */ jsxs15("div", { className: "bpe-calc", children: [
|
|
3852
|
+
/* @__PURE__ */ jsxs15("div", { className: "bpe-calc__step", children: [
|
|
3853
|
+
/* @__PURE__ */ jsx16("span", { className: "bpe-calc__lead", children: "Start with" }),
|
|
3854
|
+
/* @__PURE__ */ jsx16(TermField, { term: calc.start, entries, scope, onChange: (start) => commit({ ...calc, start }) })
|
|
3855
|
+
] }),
|
|
3856
|
+
calc.steps.map((step, i) => /* @__PURE__ */ jsxs15("div", { className: "bpe-calc__step", children: [
|
|
3857
|
+
/* @__PURE__ */ jsxs15("div", { className: "bpe-calc__row", children: [
|
|
3858
|
+
/* @__PURE__ */ jsx16("span", { className: "bpe-calc__lead", children: "then" }),
|
|
3859
|
+
/* @__PURE__ */ jsx16("div", { className: "bpe-grow" }),
|
|
3860
|
+
/* @__PURE__ */ jsx16(
|
|
3861
|
+
Button,
|
|
3862
|
+
{
|
|
3863
|
+
size: "sm",
|
|
3864
|
+
variant: "ghost",
|
|
3865
|
+
icon: "x",
|
|
3866
|
+
tooltip: "Remove this step",
|
|
3867
|
+
onClick: () => commit({ ...calc, steps: calc.steps.filter((_, j) => j !== i) })
|
|
3868
|
+
}
|
|
3869
|
+
)
|
|
3870
|
+
] }),
|
|
3871
|
+
/* @__PURE__ */ jsx16(
|
|
3872
|
+
Select,
|
|
3873
|
+
{
|
|
3874
|
+
value: step.op,
|
|
3875
|
+
onChange: (op) => commit({ ...calc, steps: calc.steps.map((s, j) => i === j ? { ...s, op } : s) }),
|
|
3876
|
+
options: OPS
|
|
3877
|
+
}
|
|
3878
|
+
),
|
|
3879
|
+
/* @__PURE__ */ jsx16(
|
|
3880
|
+
TermField,
|
|
3881
|
+
{
|
|
3882
|
+
term: step.term,
|
|
3883
|
+
entries,
|
|
3884
|
+
scope,
|
|
3885
|
+
onChange: (term) => commit({ ...calc, steps: calc.steps.map((s, j) => i === j ? { ...s, term } : s) })
|
|
3886
|
+
}
|
|
3887
|
+
)
|
|
3888
|
+
] }, i)),
|
|
3889
|
+
/* @__PURE__ */ jsx16("div", { className: "bpe-calc__row", children: /* @__PURE__ */ jsx16(
|
|
3890
|
+
Button,
|
|
3891
|
+
{
|
|
3892
|
+
size: "sm",
|
|
3893
|
+
icon: "plus",
|
|
3894
|
+
onClick: () => commit({ ...calc, steps: [...calc.steps, { op: "add", term: { kind: "field", path: "" } }] }),
|
|
3895
|
+
children: "Add a step"
|
|
3896
|
+
}
|
|
3897
|
+
) }),
|
|
3898
|
+
formats.length ? /* @__PURE__ */ jsxs15("div", { className: "bpe-calc__row", children: [
|
|
3899
|
+
/* @__PURE__ */ jsx16("span", { className: "bpe-calc__lead", children: "Shown as" }),
|
|
3900
|
+
/* @__PURE__ */ jsx16(
|
|
3901
|
+
Select,
|
|
3902
|
+
{
|
|
3903
|
+
value: calc.format ?? "",
|
|
3904
|
+
onChange: (pipe) => commit({ ...calc, ...pipe ? { format: pipe } : { format: void 0 } }),
|
|
3905
|
+
options: [{ value: "", label: "As it comes" }, ...formats.map((f) => ({ value: f.pipe, label: f.label }))]
|
|
3906
|
+
}
|
|
3907
|
+
)
|
|
3908
|
+
] }) : null,
|
|
3909
|
+
/* @__PURE__ */ jsxs15("p", { className: "bpe-calc__reads", children: [
|
|
3910
|
+
naming(calc.start),
|
|
3911
|
+
calc.steps.map((step, i) => /* @__PURE__ */ jsxs15("span", { children: [
|
|
3912
|
+
", ",
|
|
3913
|
+
OPS.find((o) => o.value === step.op)?.label,
|
|
3914
|
+
" ",
|
|
3915
|
+
naming(step.term)
|
|
3916
|
+
] }, i))
|
|
3917
|
+
] })
|
|
3918
|
+
] });
|
|
3919
|
+
}
|
|
3920
|
+
function TermField({
|
|
3921
|
+
term,
|
|
3922
|
+
entries,
|
|
3923
|
+
scope,
|
|
3924
|
+
onChange
|
|
3925
|
+
}) {
|
|
3926
|
+
return /* @__PURE__ */ jsxs15("div", { className: "bpe-calc__term", children: [
|
|
3927
|
+
/* @__PURE__ */ jsx16(
|
|
3928
|
+
Select,
|
|
3929
|
+
{
|
|
3930
|
+
value: term.kind,
|
|
3931
|
+
onChange: (kind) => onChange(blankTerm(kind, term)),
|
|
3932
|
+
options: [
|
|
3933
|
+
{ value: "field", label: "a field" },
|
|
3934
|
+
{ value: "aggregate", label: "a total" },
|
|
3935
|
+
{ value: "number", label: "a number" }
|
|
3936
|
+
]
|
|
3937
|
+
}
|
|
3938
|
+
),
|
|
3939
|
+
term.kind === "number" ? /* @__PURE__ */ jsx16(NumberInput, { value: term.value, onChange: (v) => onChange({ kind: "number", value: v ?? 0 }), placeholder: "0" }) : null,
|
|
3940
|
+
term.kind === "aggregate" ? /* @__PURE__ */ jsx16(
|
|
3941
|
+
Select,
|
|
3942
|
+
{
|
|
3943
|
+
value: term.fn,
|
|
3944
|
+
onChange: (fn) => onChange({ ...term, fn }),
|
|
3945
|
+
options: AGGREGATES.map((a) => ({ value: a.value, label: a.label }))
|
|
3946
|
+
}
|
|
3947
|
+
) : null,
|
|
3948
|
+
term.kind !== "number" ? /* @__PURE__ */ jsx16(
|
|
3949
|
+
Popover,
|
|
3950
|
+
{
|
|
3951
|
+
className: "bpe-picker",
|
|
3952
|
+
trigger: /* @__PURE__ */ jsxs15("button", { type: "button", className: `bpe-binding__btn ${term.path ? "" : "bpe-binding__btn--empty"}`, children: [
|
|
3953
|
+
/* @__PURE__ */ jsx16(Icon, { name: term.kind === "aggregate" ? "sigma" : "braces" }),
|
|
3954
|
+
/* @__PURE__ */ jsx16("span", { className: "bpe-truncate", children: term.path ? pathLabel(term.path, scope) : term.kind === "aggregate" ? "Choose a column" : "Choose a field" })
|
|
3955
|
+
] }),
|
|
3956
|
+
children: (close) => /* @__PURE__ */ jsx16(
|
|
3957
|
+
TermPicker,
|
|
3958
|
+
{
|
|
3959
|
+
entries,
|
|
3960
|
+
wants: term.kind,
|
|
3961
|
+
numericOnly: term.kind === "aggregate" ? term.fn !== "count" : true,
|
|
3962
|
+
onPick: (path) => {
|
|
3963
|
+
onChange({ ...term, path });
|
|
3964
|
+
close();
|
|
3965
|
+
}
|
|
3966
|
+
}
|
|
3967
|
+
)
|
|
3968
|
+
}
|
|
3969
|
+
) : null
|
|
3970
|
+
] });
|
|
3971
|
+
}
|
|
3972
|
+
function blankTerm(kind, from) {
|
|
3973
|
+
const path = from.kind === "number" ? "" : from.path;
|
|
3974
|
+
if (kind === "number") return { kind: "number", value: 0 };
|
|
3975
|
+
if (kind === "aggregate") return { kind: "aggregate", fn: "sum", path };
|
|
3976
|
+
return { kind: "field", path };
|
|
3977
|
+
}
|
|
3978
|
+
function termOptions(entries, wants, numericOnly) {
|
|
3979
|
+
return entries.filter((e) => {
|
|
3980
|
+
if (wants === "aggregate") {
|
|
3981
|
+
if (e.type !== "array") return false;
|
|
3982
|
+
const item = e.schema.itemSchema;
|
|
3983
|
+
return numericOnly ? Boolean(item && NUMERIC.includes(item.type)) : true;
|
|
3984
|
+
}
|
|
3985
|
+
return numericOnly ? NUMERIC.includes(e.type) : e.type !== "array" && e.type !== "object";
|
|
3986
|
+
});
|
|
3987
|
+
}
|
|
3988
|
+
function TermPicker({
|
|
3989
|
+
entries,
|
|
3990
|
+
wants,
|
|
3991
|
+
numericOnly,
|
|
3992
|
+
onPick
|
|
3993
|
+
}) {
|
|
3994
|
+
const shown = termOptions(entries, wants, numericOnly);
|
|
3995
|
+
const groups = /* @__PURE__ */ new Map();
|
|
3996
|
+
for (const e of shown) groups.set(e.group, [...groups.get(e.group) ?? [], e]);
|
|
3997
|
+
return /* @__PURE__ */ jsx16("div", { children: /* @__PURE__ */ jsxs15("div", { className: "bpe-picker__list", role: "listbox", children: [
|
|
3998
|
+
!shown.length ? /* @__PURE__ */ jsx16("div", { className: "bpe-empty", children: wants === "aggregate" ? "No lists of numbers in your data to total up." : "No numbers in your data to use here." }) : null,
|
|
3999
|
+
[...groups].map(([group, items]) => /* @__PURE__ */ jsxs15("div", { children: [
|
|
4000
|
+
/* @__PURE__ */ jsx16("div", { className: "bpe-picker__group", children: group }),
|
|
4001
|
+
items.map((e) => /* @__PURE__ */ jsxs15("button", { type: "button", role: "option", className: "bpe-data-field", onClick: () => onPick(e.expr), children: [
|
|
4002
|
+
/* @__PURE__ */ jsx16(Icon, { name: wants === "aggregate" ? "list-tree" : "hash", className: "bpe-data-field__icon" }),
|
|
4003
|
+
/* @__PURE__ */ jsx16("span", { className: "bpe-truncate", children: e.label })
|
|
4004
|
+
] }, e.expr))
|
|
4005
|
+
] }, group))
|
|
4006
|
+
] }) });
|
|
4007
|
+
}
|
|
4008
|
+
function pathLabel(path, scope) {
|
|
4009
|
+
return labelForPath2(
|
|
4010
|
+
path.split(".").map((name) => ({ kind: "prop", name })),
|
|
4011
|
+
scope
|
|
4012
|
+
);
|
|
4013
|
+
}
|
|
4014
|
+
function termLabel(term, entries, scope) {
|
|
4015
|
+
if (term.kind === "number") return String(term.value);
|
|
4016
|
+
if (!term.path) return "\u2026";
|
|
4017
|
+
const named = entries.find((e) => e.expr === term.path)?.label ?? pathLabel(term.path, scope);
|
|
4018
|
+
if (term.kind === "field") return named;
|
|
4019
|
+
return `${AGGREGATES.find((a) => a.value === term.fn)?.label ?? "Total of"} ${named}`;
|
|
4020
|
+
}
|
|
4021
|
+
function canCalculate(types) {
|
|
4022
|
+
return !types || types.some((t) => NUMERIC.includes(t));
|
|
4023
|
+
}
|
|
4024
|
+
|
|
4025
|
+
// src/inspector/fields/BindingField.tsx
|
|
4026
|
+
import { jsx as jsx17, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
3749
4027
|
function useExpressionLabel(dataSources, scopeVars) {
|
|
3750
|
-
return
|
|
3751
|
-
const scope =
|
|
4028
|
+
return useMemo7(() => {
|
|
4029
|
+
const scope = createSchemaScope4(dataSources).child(scopeVars ?? {});
|
|
3752
4030
|
return (expr) => {
|
|
3753
|
-
if (!expr.trim()) return { label: "", error: null };
|
|
3754
|
-
const
|
|
3755
|
-
|
|
4031
|
+
if (!expr.trim()) return { label: "", error: null, computed: false };
|
|
4032
|
+
const error = typeCheck(expr, scope).issues[0]?.message ?? null;
|
|
4033
|
+
let computed = true;
|
|
3756
4034
|
try {
|
|
3757
4035
|
const ast = parseExpression2(expr);
|
|
3758
|
-
|
|
3759
|
-
if (ast.kind === "pipe" && ast.input.kind === "path") return { label: `${labelForPath2(ast.input.segments, scope)} \xB7 ${ast.name}`, error };
|
|
4036
|
+
computed = !(ast.kind === "path" || ast.kind === "pipe" && ast.input.kind === "path");
|
|
3760
4037
|
} catch {
|
|
4038
|
+
computed = false;
|
|
3761
4039
|
}
|
|
3762
|
-
return { label: expr, error };
|
|
4040
|
+
return { label: describeExpression(expr, scope).label, error, computed };
|
|
3763
4041
|
};
|
|
3764
4042
|
}, [dataSources, scopeVars]);
|
|
3765
4043
|
}
|
|
3766
|
-
function
|
|
4044
|
+
function useTokenText(dataSources, scopeVars) {
|
|
4045
|
+
return useMemo7(() => {
|
|
4046
|
+
const scope = createSchemaScope4(dataSources).child(scopeVars ?? {});
|
|
4047
|
+
return (text) => humaniseLabel2(text, scope);
|
|
4048
|
+
}, [dataSources, scopeVars]);
|
|
4049
|
+
}
|
|
4050
|
+
function BindingField({ value, onChange, dataSources, scopeVars, arraysOnly, types, placeholder, allowExpression = true, allowCalculation = true }) {
|
|
3767
4051
|
const describe = useExpressionLabel(dataSources, scopeVars);
|
|
3768
|
-
const { label, error } = describe(value);
|
|
3769
|
-
|
|
3770
|
-
|
|
4052
|
+
const { label, error, computed } = describe(value);
|
|
4053
|
+
const editable = allowCalculation && !arraysOnly && canCalculate(types) && (value.trim() === "" || fromExpression2(value) !== null);
|
|
4054
|
+
if (value && computed && !allowExpression && !editable) {
|
|
4055
|
+
return /* @__PURE__ */ jsx17("div", { className: "bpe-binding", children: /* @__PURE__ */ jsxs16("span", { className: "bpe-binding__btn bpe-binding__btn--locked", title: `Worked out for you, from ${label.replace(/^ƒ\s*/, "")}. Change those and this changes with them.`, children: [
|
|
4056
|
+
/* @__PURE__ */ jsx17(Icon, { name: "sigma" }),
|
|
4057
|
+
/* @__PURE__ */ jsx17("span", { className: "bpe-truncate", children: label.replace(/^ƒ\s*/, "") || "Worked out for you" })
|
|
4058
|
+
] }) });
|
|
4059
|
+
}
|
|
4060
|
+
return /* @__PURE__ */ jsxs16("div", { className: "bpe-binding", children: [
|
|
4061
|
+
/* @__PURE__ */ jsx17(
|
|
3771
4062
|
Popover,
|
|
3772
4063
|
{
|
|
3773
|
-
className: "bpe-picker"
|
|
3774
|
-
trigger: /* @__PURE__ */
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
4064
|
+
className: `bpe-picker${editable ? " bpe-picker--wide" : ""}`,
|
|
4065
|
+
trigger: /* @__PURE__ */ jsxs16(
|
|
4066
|
+
"button",
|
|
4067
|
+
{
|
|
4068
|
+
type: "button",
|
|
4069
|
+
className: `bpe-binding__btn ${value ? "" : "bpe-binding__btn--empty"} ${error ? "bpe-input--invalid" : ""}`,
|
|
4070
|
+
title: error ?? (allowExpression ? value : label),
|
|
4071
|
+
children: [
|
|
4072
|
+
/* @__PURE__ */ jsx17(Icon, { name: error ? "alert-triangle" : computed ? "sigma" : arraysOnly ? "list-tree" : "braces", style: error ? { color: "var(--bpe-danger)" } : void 0 }),
|
|
4073
|
+
/* @__PURE__ */ jsx17("span", { className: "bpe-truncate", children: value ? label.replace(/^ƒ\s*/, "") : placeholder ?? (arraysOnly ? "Choose a list" : "Choose a field") })
|
|
4074
|
+
]
|
|
4075
|
+
}
|
|
4076
|
+
),
|
|
4077
|
+
children: (close) => /* @__PURE__ */ jsx17(
|
|
4078
|
+
ValuePanel,
|
|
3780
4079
|
{
|
|
4080
|
+
value,
|
|
4081
|
+
onChange,
|
|
3781
4082
|
dataSources,
|
|
3782
4083
|
scopeVars,
|
|
3783
4084
|
arraysOnly,
|
|
3784
4085
|
types,
|
|
3785
|
-
value,
|
|
3786
4086
|
allowExpression,
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
},
|
|
3791
|
-
onClear: () => {
|
|
3792
|
-
onChange("");
|
|
3793
|
-
close();
|
|
3794
|
-
}
|
|
4087
|
+
offerCalculation: editable,
|
|
4088
|
+
startOnCalculation: computed && editable,
|
|
4089
|
+
close
|
|
3795
4090
|
}
|
|
3796
4091
|
)
|
|
3797
4092
|
}
|
|
3798
4093
|
),
|
|
3799
|
-
value ? /* @__PURE__ */
|
|
4094
|
+
value ? /* @__PURE__ */ jsx17(Button, { size: "sm", variant: "ghost", icon: "x", tooltip: "Clear", onClick: () => onChange("") }) : null
|
|
4095
|
+
] });
|
|
4096
|
+
}
|
|
4097
|
+
function ValuePanel({
|
|
4098
|
+
value,
|
|
4099
|
+
onChange,
|
|
4100
|
+
dataSources,
|
|
4101
|
+
scopeVars,
|
|
4102
|
+
arraysOnly,
|
|
4103
|
+
types,
|
|
4104
|
+
allowExpression,
|
|
4105
|
+
offerCalculation,
|
|
4106
|
+
startOnCalculation,
|
|
4107
|
+
close
|
|
4108
|
+
}) {
|
|
4109
|
+
const [tab, setTab] = useState13(startOnCalculation ? "calc" : "field");
|
|
4110
|
+
const formats = useMemo7(() => {
|
|
4111
|
+
const wanted = types?.length ? types : ["currency", "number", "percent"];
|
|
4112
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4113
|
+
return wanted.flatMap((t) => FORMAT_SUGGESTIONS[t] ?? []).filter((f) => !seen.has(f.pipe) && seen.add(f.pipe));
|
|
4114
|
+
}, [types]);
|
|
4115
|
+
if (!offerCalculation) {
|
|
4116
|
+
return /* @__PURE__ */ jsx17(
|
|
4117
|
+
DataPicker,
|
|
4118
|
+
{
|
|
4119
|
+
dataSources,
|
|
4120
|
+
scopeVars,
|
|
4121
|
+
arraysOnly,
|
|
4122
|
+
types,
|
|
4123
|
+
value,
|
|
4124
|
+
allowExpression,
|
|
4125
|
+
onPick: (expr) => {
|
|
4126
|
+
onChange(expr);
|
|
4127
|
+
close();
|
|
4128
|
+
},
|
|
4129
|
+
onClear: () => {
|
|
4130
|
+
onChange("");
|
|
4131
|
+
close();
|
|
4132
|
+
}
|
|
4133
|
+
}
|
|
4134
|
+
);
|
|
4135
|
+
}
|
|
4136
|
+
return /* @__PURE__ */ jsxs16("div", { children: [
|
|
4137
|
+
/* @__PURE__ */ jsxs16("div", { className: "bpe-picker__tabs", role: "tablist", children: [
|
|
4138
|
+
/* @__PURE__ */ jsx17("button", { type: "button", role: "tab", "aria-selected": tab === "field", className: "bpe-tab", onClick: () => setTab("field"), children: "A field" }),
|
|
4139
|
+
/* @__PURE__ */ jsx17("button", { type: "button", role: "tab", "aria-selected": tab === "calc", className: "bpe-tab", onClick: () => setTab("calc"), children: "Worked out" })
|
|
4140
|
+
] }),
|
|
4141
|
+
tab === "field" ? /* @__PURE__ */ jsx17(
|
|
4142
|
+
DataPicker,
|
|
4143
|
+
{
|
|
4144
|
+
dataSources,
|
|
4145
|
+
scopeVars,
|
|
4146
|
+
types,
|
|
4147
|
+
value,
|
|
4148
|
+
allowExpression,
|
|
4149
|
+
onPick: (expr) => {
|
|
4150
|
+
onChange(expr);
|
|
4151
|
+
close();
|
|
4152
|
+
},
|
|
4153
|
+
onClear: () => {
|
|
4154
|
+
onChange("");
|
|
4155
|
+
close();
|
|
4156
|
+
}
|
|
4157
|
+
}
|
|
4158
|
+
) : /* @__PURE__ */ jsx17(CalculationBuilder, { value, onChange, dataSources, scopeVars, formats })
|
|
3800
4159
|
] });
|
|
3801
4160
|
}
|
|
3802
4161
|
|
|
3803
4162
|
// src/inspector/fields/ImageField.tsx
|
|
3804
|
-
import { useRef as
|
|
4163
|
+
import { useRef as useRef7, useState as useState14 } from "react";
|
|
3805
4164
|
import { isSafeUrl } from "@broadpaper/core";
|
|
3806
|
-
import { jsx as
|
|
4165
|
+
import { jsx as jsx18, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
3807
4166
|
var MAX_INLINE_IMAGE_BYTES = 1e6;
|
|
3808
4167
|
var ACCEPT = "image/png,image/jpeg,image/gif,image/webp,image/svg+xml";
|
|
3809
4168
|
var BINDABLE = ["image", "url", "string"];
|
|
@@ -3827,8 +4186,8 @@ function ImageField({
|
|
|
3827
4186
|
onError
|
|
3828
4187
|
}) {
|
|
3829
4188
|
const options = useOptions();
|
|
3830
|
-
const fileRef =
|
|
3831
|
-
const [busy, setBusy] =
|
|
4189
|
+
const fileRef = useRef7(null);
|
|
4190
|
+
const [busy, setBusy] = useState14(false);
|
|
3832
4191
|
const bound = /^\{\{[\s\S]*\}\}$/.test(value.trim());
|
|
3833
4192
|
async function accept(file) {
|
|
3834
4193
|
onError(null);
|
|
@@ -3859,8 +4218,8 @@ function ImageField({
|
|
|
3859
4218
|
if (fileRef.current) fileRef.current.value = "";
|
|
3860
4219
|
}
|
|
3861
4220
|
}
|
|
3862
|
-
return /* @__PURE__ */
|
|
3863
|
-
bound ? /* @__PURE__ */
|
|
4221
|
+
return /* @__PURE__ */ jsxs17("div", { className: "bpe-binding bpe-image", children: [
|
|
4222
|
+
bound ? /* @__PURE__ */ jsx18(
|
|
3864
4223
|
BindingField,
|
|
3865
4224
|
{
|
|
3866
4225
|
value: value.replace(/^\{\{\s*|\s*\}\}$/g, ""),
|
|
@@ -3869,8 +4228,8 @@ function ImageField({
|
|
|
3869
4228
|
scopeVars,
|
|
3870
4229
|
types: BINDABLE
|
|
3871
4230
|
}
|
|
3872
|
-
) : /* @__PURE__ */
|
|
3873
|
-
/* @__PURE__ */
|
|
4231
|
+
) : /* @__PURE__ */ jsx18(TextInput, { value, onChange, lazy: true, placeholder: "https://\u2026 or upload" }),
|
|
4232
|
+
/* @__PURE__ */ jsx18(
|
|
3874
4233
|
"input",
|
|
3875
4234
|
{
|
|
3876
4235
|
ref: fileRef,
|
|
@@ -3885,7 +4244,7 @@ function ImageField({
|
|
|
3885
4244
|
}
|
|
3886
4245
|
}
|
|
3887
4246
|
),
|
|
3888
|
-
!bound ? /* @__PURE__ */
|
|
4247
|
+
!bound ? /* @__PURE__ */ jsx18(
|
|
3889
4248
|
Button,
|
|
3890
4249
|
{
|
|
3891
4250
|
size: "sm",
|
|
@@ -3896,7 +4255,7 @@ function ImageField({
|
|
|
3896
4255
|
onClick: () => fileRef.current?.click()
|
|
3897
4256
|
}
|
|
3898
4257
|
) : null,
|
|
3899
|
-
allowBinding ? /* @__PURE__ */
|
|
4258
|
+
allowBinding ? /* @__PURE__ */ jsx18(Popover, { className: "bpe-picker", trigger: /* @__PURE__ */ jsx18(Button, { size: "sm", variant: "ghost", icon: "braces", tooltip: "Bind to a field" }), children: (close) => /* @__PURE__ */ jsx18(
|
|
3900
4259
|
DataPicker,
|
|
3901
4260
|
{
|
|
3902
4261
|
dataSources,
|
|
@@ -3912,64 +4271,64 @@ function ImageField({
|
|
|
3912
4271
|
}
|
|
3913
4272
|
|
|
3914
4273
|
// src/inspector/fields/ConditionBuilder.tsx
|
|
3915
|
-
import { CONDITION_OPERATORS, compileCondition, checkExpressionSyntax as checkExpressionSyntax2 } from "@broadpaper/core";
|
|
3916
|
-
import { useState as
|
|
3917
|
-
import { jsx as
|
|
4274
|
+
import { CONDITION_OPERATORS, compileCondition, checkExpressionSyntax as checkExpressionSyntax2, createSchemaScope as createSchemaScope5, humaniseLabel as humaniseLabel3 } from "@broadpaper/core";
|
|
4275
|
+
import { useState as useState15 } from "react";
|
|
4276
|
+
import { jsx as jsx19, jsxs as jsxs18 } from "react/jsx-runtime";
|
|
3918
4277
|
function isGroup(v) {
|
|
3919
4278
|
return !!v && typeof v === "object" && "op" in v;
|
|
3920
4279
|
}
|
|
3921
4280
|
function ConditionBuilder({ value, onChange, dataSources, scopeVars, allowExpression = true }) {
|
|
3922
|
-
const [advanced, setAdvanced] =
|
|
4281
|
+
const [advanced, setAdvanced] = useState15(typeof value === "string" && value.trim() !== "");
|
|
3923
4282
|
const group = isGroup(value) ? value : { op: "and", rules: [] };
|
|
3924
4283
|
const compiled = compileCondition(value);
|
|
3925
4284
|
if (advanced) {
|
|
3926
4285
|
const src = typeof value === "string" ? value : compiled ?? "";
|
|
3927
4286
|
const err = src.trim() ? checkExpressionSyntax2(src) : null;
|
|
3928
|
-
return /* @__PURE__ */
|
|
3929
|
-
/* @__PURE__ */
|
|
3930
|
-
err ? /* @__PURE__ */
|
|
3931
|
-
/* @__PURE__ */
|
|
3932
|
-
/* @__PURE__ */
|
|
4287
|
+
return /* @__PURE__ */ jsxs18("div", { className: "bpe-cond", children: [
|
|
4288
|
+
/* @__PURE__ */ jsx19(TextInput, { value: src, onChange: (v) => onChange(v.trim() ? v : void 0), mono: true, lazy: true, placeholder: "e.g. client.riskScore > 7 and client.isRetired", invalid: !!err }),
|
|
4289
|
+
err ? /* @__PURE__ */ jsx19("div", { className: "bpe-field__error", children: err.message }) : null,
|
|
4290
|
+
/* @__PURE__ */ jsxs18("div", { style: { display: "flex", gap: 6 }, children: [
|
|
4291
|
+
/* @__PURE__ */ jsx19(Button, { size: "sm", onClick: () => {
|
|
3933
4292
|
setAdvanced(false);
|
|
3934
4293
|
onChange({ op: "and", rules: [] });
|
|
3935
4294
|
}, children: "Use rule builder" }),
|
|
3936
|
-
value ? /* @__PURE__ */
|
|
4295
|
+
value ? /* @__PURE__ */ jsx19(Button, { size: "sm", variant: "ghost", icon: "x", onClick: () => onChange(void 0), children: "Clear" }) : null
|
|
3937
4296
|
] })
|
|
3938
4297
|
] });
|
|
3939
4298
|
}
|
|
3940
4299
|
const update = (g) => onChange(g.rules.length ? g : void 0);
|
|
3941
|
-
return /* @__PURE__ */
|
|
3942
|
-
/* @__PURE__ */
|
|
3943
|
-
/* @__PURE__ */
|
|
3944
|
-
/* @__PURE__ */
|
|
3945
|
-
/* @__PURE__ */
|
|
3946
|
-
allowExpression ? /* @__PURE__ */
|
|
4300
|
+
return /* @__PURE__ */ jsxs18("div", { className: "bpe-cond", children: [
|
|
4301
|
+
/* @__PURE__ */ jsx19(GroupEditor, { group, onChange: update, dataSources, scopeVars, depth: 0 }),
|
|
4302
|
+
/* @__PURE__ */ jsxs18("div", { style: { display: "flex", gap: 6, alignItems: "center", flexWrap: "wrap" }, children: [
|
|
4303
|
+
/* @__PURE__ */ jsx19(Button, { size: "sm", icon: "plus", onClick: () => update({ ...group, rules: [...group.rules, { left: "", operator: "eq", right: "" }] }), children: "Rule" }),
|
|
4304
|
+
/* @__PURE__ */ jsx19(Button, { size: "sm", icon: "plus", onClick: () => update({ ...group, rules: [...group.rules, { op: "or", rules: [{ left: "", operator: "eq", right: "" }] }] }), children: "Group" }),
|
|
4305
|
+
allowExpression ? /* @__PURE__ */ jsx19(Button, { size: "sm", variant: "ghost", icon: "braces", onClick: () => setAdvanced(true), children: "Expression" }) : null
|
|
3947
4306
|
] }),
|
|
3948
|
-
compiled ? /* @__PURE__ */
|
|
4307
|
+
compiled ? allowExpression ? /* @__PURE__ */ jsx19("div", { className: "bpe-field__hint bpe-mono", title: compiled, children: compiled }) : /* @__PURE__ */ jsx19("div", { className: "bpe-field__hint", children: humaniseLabel3(compiled, createSchemaScope5(dataSources).child(scopeVars ?? {})) }) : /* @__PURE__ */ jsx19("div", { className: "bpe-field__hint", children: "Always shown" })
|
|
3949
4308
|
] });
|
|
3950
4309
|
}
|
|
3951
4310
|
function GroupEditor({ group, onChange, dataSources, scopeVars, depth }) {
|
|
3952
4311
|
const setRule = (i, r) => onChange({ ...group, rules: group.rules.map((x, j) => j === i ? r : x) });
|
|
3953
4312
|
const remove = (i) => onChange({ ...group, rules: group.rules.filter((_, j) => j !== i) });
|
|
3954
|
-
return /* @__PURE__ */
|
|
3955
|
-
group.rules.length > 1 ? /* @__PURE__ */
|
|
3956
|
-
/* @__PURE__ */
|
|
3957
|
-
/* @__PURE__ */
|
|
4313
|
+
return /* @__PURE__ */ jsxs18("div", { className: depth > 0 ? "bpe-cond__group" : void 0, children: [
|
|
4314
|
+
group.rules.length > 1 ? /* @__PURE__ */ jsxs18("div", { className: "bpe-cond__op", style: { marginBottom: 6 }, children: [
|
|
4315
|
+
/* @__PURE__ */ jsx19("span", { className: "bpe-muted", style: { fontSize: "var(--bpe-text-xs)" }, children: "Match" }),
|
|
4316
|
+
/* @__PURE__ */ jsx19(Segmented, { value: group.op, onChange: (op) => onChange({ ...group, op }), options: [{ value: "and", label: "All (AND)" }, { value: "or", label: "Any (OR)" }], className: "bpe-mode" })
|
|
3958
4317
|
] }) : null,
|
|
3959
|
-
group.rules.map((r, i) => /* @__PURE__ */
|
|
3960
|
-
"op" in r ? /* @__PURE__ */
|
|
3961
|
-
/* @__PURE__ */
|
|
4318
|
+
group.rules.map((r, i) => /* @__PURE__ */ jsxs18("div", { className: "bpe-cond__rule", style: { marginBottom: 6 }, children: [
|
|
4319
|
+
"op" in r ? /* @__PURE__ */ jsx19(GroupEditor, { group: r, onChange: (g) => setRule(i, g), dataSources, scopeVars, depth: depth + 1 }) : /* @__PURE__ */ jsx19(RuleEditor, { rule: r, onChange: (x) => setRule(i, x), dataSources, scopeVars }),
|
|
4320
|
+
/* @__PURE__ */ jsx19(Button, { size: "sm", variant: "ghost", icon: "x", tooltip: "Remove", onClick: () => remove(i) })
|
|
3962
4321
|
] }, i)),
|
|
3963
|
-
depth > 0 ? /* @__PURE__ */
|
|
4322
|
+
depth > 0 ? /* @__PURE__ */ jsx19(Button, { size: "sm", icon: "plus", onClick: () => onChange({ ...group, rules: [...group.rules, { left: "", operator: "eq", right: "" }] }), children: "Rule" }) : null
|
|
3964
4323
|
] });
|
|
3965
4324
|
}
|
|
3966
4325
|
function RuleEditor({ rule, onChange, dataSources, scopeVars }) {
|
|
3967
4326
|
const op = CONDITION_OPERATORS.find((o) => o.value === rule.operator) ?? CONDITION_OPERATORS[0];
|
|
3968
4327
|
const rightStr = rule.right === void 0 || rule.right === null ? "" : String(rule.right);
|
|
3969
|
-
return /* @__PURE__ */
|
|
3970
|
-
/* @__PURE__ */
|
|
3971
|
-
/* @__PURE__ */
|
|
3972
|
-
op.needsValue ? /* @__PURE__ */
|
|
4328
|
+
return /* @__PURE__ */ jsxs18("div", { className: "bpe-cond__row", children: [
|
|
4329
|
+
/* @__PURE__ */ jsx19(BindingField, { value: rule.left, onChange: (left) => onChange({ ...rule, left }), dataSources, scopeVars, placeholder: "Choose a field" }),
|
|
4330
|
+
/* @__PURE__ */ jsx19(Select, { value: rule.operator, onChange: (v) => onChange({ ...rule, operator: v }), options: CONDITION_OPERATORS.map((o) => ({ value: o.value, label: o.label })) }),
|
|
4331
|
+
op.needsValue ? /* @__PURE__ */ jsx19(
|
|
3973
4332
|
TextInput,
|
|
3974
4333
|
{
|
|
3975
4334
|
value: rightStr,
|
|
@@ -3989,11 +4348,11 @@ function RuleEditor({ rule, onChange, dataSources, scopeVars }) {
|
|
|
3989
4348
|
}
|
|
3990
4349
|
|
|
3991
4350
|
// src/inspector/fields/ListEditors.tsx
|
|
3992
|
-
import { useState as
|
|
4351
|
+
import { useState as useState16 } from "react";
|
|
3993
4352
|
import { createId as createId2 } from "@broadpaper/core";
|
|
3994
|
-
import { Fragment as Fragment3, jsx as
|
|
4353
|
+
import { Fragment as Fragment3, jsx as jsx20, jsxs as jsxs19 } from "react/jsx-runtime";
|
|
3995
4354
|
function ListEditor({ items, onChange, renderItem, label, itemLabel = "Item", create, summary }) {
|
|
3996
|
-
const [open, setOpen] =
|
|
4355
|
+
const [open, setOpen] = useState16(null);
|
|
3997
4356
|
const update = (i, patch) => onChange(items.map((it, j) => j === i ? { ...it, ...patch } : it));
|
|
3998
4357
|
const noun = itemLabel.toLowerCase();
|
|
3999
4358
|
const name = (item, i) => summary?.(item, i) || `${itemLabel} ${i + 1}`;
|
|
@@ -4005,18 +4364,18 @@ function ListEditor({ items, onChange, renderItem, label, itemLabel = "Item", cr
|
|
|
4005
4364
|
onChange(next);
|
|
4006
4365
|
setOpen(j);
|
|
4007
4366
|
};
|
|
4008
|
-
return /* @__PURE__ */
|
|
4009
|
-
items.map((it, i) => /* @__PURE__ */
|
|
4010
|
-
/* @__PURE__ */
|
|
4011
|
-
/* @__PURE__ */
|
|
4012
|
-
/* @__PURE__ */
|
|
4013
|
-
/* @__PURE__ */
|
|
4014
|
-
/* @__PURE__ */
|
|
4015
|
-
/* @__PURE__ */
|
|
4367
|
+
return /* @__PURE__ */ jsxs19("div", { className: "bpe-list-editor", children: [
|
|
4368
|
+
items.map((it, i) => /* @__PURE__ */ jsxs19("div", { className: "bpe-list-editor__item", children: [
|
|
4369
|
+
/* @__PURE__ */ jsxs19("div", { className: "bpe-list-editor__head", children: [
|
|
4370
|
+
/* @__PURE__ */ jsx20("button", { type: "button", className: "bpe-layer__toggle", "aria-expanded": open === i, "aria-label": `${open === i ? "Collapse" : "Expand"} ${name(it, i)}`, onClick: () => setOpen(open === i ? null : i), children: /* @__PURE__ */ jsx20(Icon, { name: "chevron-down" }) }),
|
|
4371
|
+
/* @__PURE__ */ jsx20("span", { className: "bpe-truncate", title: summary?.(it, i), children: name(it, i) }),
|
|
4372
|
+
/* @__PURE__ */ jsx20(Button, { size: "sm", variant: "ghost", icon: "arrow-up", tooltip: `Move ${noun} up`, disabled: i === 0, onClick: () => move(i, -1) }),
|
|
4373
|
+
/* @__PURE__ */ jsx20(Button, { size: "sm", variant: "ghost", icon: "arrow-down", tooltip: `Move ${noun} down`, disabled: i === items.length - 1, onClick: () => move(i, 1) }),
|
|
4374
|
+
/* @__PURE__ */ jsx20(Button, { size: "sm", variant: "ghost", icon: "trash", tooltip: `Remove ${noun}`, onClick: () => onChange(items.filter((_, j) => j !== i)) })
|
|
4016
4375
|
] }),
|
|
4017
|
-
open === i ? /* @__PURE__ */
|
|
4376
|
+
open === i ? /* @__PURE__ */ jsx20("div", { className: "bpe-list-editor__body", children: renderItem(it, (patch) => update(i, patch), i) }) : null
|
|
4018
4377
|
] }, it.id ?? i)),
|
|
4019
|
-
/* @__PURE__ */
|
|
4378
|
+
/* @__PURE__ */ jsxs19(Button, { size: "sm", icon: "plus", onClick: () => {
|
|
4020
4379
|
onChange([...items, create()]);
|
|
4021
4380
|
setOpen(items.length);
|
|
4022
4381
|
}, children: [
|
|
@@ -4025,8 +4384,9 @@ function ListEditor({ items, onChange, renderItem, label, itemLabel = "Item", cr
|
|
|
4025
4384
|
] })
|
|
4026
4385
|
] });
|
|
4027
4386
|
}
|
|
4387
|
+
var TOTAL_FORMATS = [...FORMAT_SUGGESTIONS.currency ?? [], ...FORMAT_SUGGESTIONS.number ?? [], ...FORMAT_SUGGESTIONS.percent ?? []];
|
|
4028
4388
|
function ColumnsEditor({ value, onChange, dataSources, scopeVars }) {
|
|
4029
|
-
return /* @__PURE__ */
|
|
4389
|
+
return /* @__PURE__ */ jsx20(
|
|
4030
4390
|
ListEditor,
|
|
4031
4391
|
{
|
|
4032
4392
|
items: value,
|
|
@@ -4034,21 +4394,28 @@ function ColumnsEditor({ value, onChange, dataSources, scopeVars }) {
|
|
|
4034
4394
|
itemLabel: "Column",
|
|
4035
4395
|
summary: (c) => c.header || c.value || "Column",
|
|
4036
4396
|
create: () => ({ id: createId2("c"), header: "New column", value: "", align: "left", width: null }),
|
|
4037
|
-
renderItem: (c, update) => /* @__PURE__ */
|
|
4038
|
-
/* @__PURE__ */
|
|
4039
|
-
/* @__PURE__ */
|
|
4040
|
-
/* @__PURE__ */
|
|
4041
|
-
/* @__PURE__ */
|
|
4042
|
-
/* @__PURE__ */
|
|
4043
|
-
c.total && c.total !== "none" ? /* @__PURE__ */
|
|
4044
|
-
|
|
4045
|
-
|
|
4397
|
+
renderItem: (c, update) => /* @__PURE__ */ jsxs19(Fragment3, { children: [
|
|
4398
|
+
/* @__PURE__ */ jsx20(Field, { label: "Heading", stacked: true, children: /* @__PURE__ */ jsx20(TextInput, { value: c.header, onChange: (header) => update({ header }), lazy: true }) }),
|
|
4399
|
+
/* @__PURE__ */ jsx20(Field, { label: "Value", stacked: true, children: /* @__PURE__ */ jsx20(BindingField, { value: c.value, onChange: (v) => update({ value: v }), dataSources, scopeVars, placeholder: "Field per row" }) }),
|
|
4400
|
+
/* @__PURE__ */ jsx20(Field, { label: "Align", children: /* @__PURE__ */ jsx20(Select, { value: c.align ?? "left", onChange: (v) => update({ align: v }), options: [{ value: "left", label: "Left" }, { value: "center", label: "Centre" }, { value: "right", label: "Right" }] }) }),
|
|
4401
|
+
/* @__PURE__ */ jsx20(Field, { label: "Width", children: /* @__PURE__ */ jsx20(NumberInput, { value: c.width ?? null, onChange: (w) => update({ width: w }), min: 3, max: 100, unit: "%", placeholder: "auto" }) }),
|
|
4402
|
+
/* @__PURE__ */ jsx20(Field, { label: "Total", children: /* @__PURE__ */ jsx20(Select, { value: c.total ?? "none", onChange: (v) => update({ total: v }), options: [{ value: "none", label: "None" }, { value: "sum", label: "Sum" }, { value: "avg", label: "Average" }, { value: "count", label: "Count" }, { value: "min", label: "Minimum" }, { value: "max", label: "Maximum" }] }) }),
|
|
4403
|
+
c.total && c.total !== "none" ? /* @__PURE__ */ jsx20(Field, { label: "Total shown as", children: /* @__PURE__ */ jsx20(
|
|
4404
|
+
Select,
|
|
4405
|
+
{
|
|
4406
|
+
value: c.totalFormat ?? "",
|
|
4407
|
+
onChange: (totalFormat) => update({ totalFormat: totalFormat || void 0 }),
|
|
4408
|
+
options: [{ value: "", label: "As it comes" }, ...TOTAL_FORMATS.map((f) => ({ value: f.pipe, label: f.label }))]
|
|
4409
|
+
}
|
|
4410
|
+
) }) : null,
|
|
4411
|
+
/* @__PURE__ */ jsx20(Field, { label: "Bold", children: /* @__PURE__ */ jsx20(Switch, { checked: !!c.bold, onChange: (bold) => update({ bold }) }) }),
|
|
4412
|
+
/* @__PURE__ */ jsx20(Field, { label: "Wrap text", children: /* @__PURE__ */ jsx20(Switch, { checked: c.wrap !== false, onChange: (wrap) => update({ wrap }) }) })
|
|
4046
4413
|
] })
|
|
4047
4414
|
}
|
|
4048
4415
|
);
|
|
4049
4416
|
}
|
|
4050
4417
|
function SeriesEditor({ value, onChange, dataSources, scopeVars, renderColor }) {
|
|
4051
|
-
return /* @__PURE__ */
|
|
4418
|
+
return /* @__PURE__ */ jsx20(
|
|
4052
4419
|
ListEditor,
|
|
4053
4420
|
{
|
|
4054
4421
|
items: value,
|
|
@@ -4056,33 +4423,98 @@ function SeriesEditor({ value, onChange, dataSources, scopeVars, renderColor })
|
|
|
4056
4423
|
itemLabel: "Series",
|
|
4057
4424
|
summary: (s) => s.label || s.value || "Series",
|
|
4058
4425
|
create: () => ({ id: createId2("s"), label: `Series ${value.length + 1}`, value: "" }),
|
|
4059
|
-
renderItem: (s, update) => /* @__PURE__ */
|
|
4060
|
-
/* @__PURE__ */
|
|
4061
|
-
/* @__PURE__ */
|
|
4062
|
-
/* @__PURE__ */
|
|
4426
|
+
renderItem: (s, update) => /* @__PURE__ */ jsxs19(Fragment3, { children: [
|
|
4427
|
+
/* @__PURE__ */ jsx20(Field, { label: "Label", children: /* @__PURE__ */ jsx20(TextInput, { value: s.label, onChange: (label) => update({ label }), lazy: true }) }),
|
|
4428
|
+
/* @__PURE__ */ jsx20(Field, { label: "Value", stacked: true, children: /* @__PURE__ */ jsx20(BindingField, { value: s.value, onChange: (v) => update({ value: v }), dataSources, scopeVars, placeholder: "Numeric field per item" }) }),
|
|
4429
|
+
/* @__PURE__ */ jsx20(Field, { label: "Colour", children: renderColor(s.color, (color) => update({ color })) })
|
|
4063
4430
|
] })
|
|
4064
4431
|
}
|
|
4065
4432
|
);
|
|
4066
4433
|
}
|
|
4067
|
-
function KeyValuesEditor({
|
|
4068
|
-
|
|
4434
|
+
function KeyValuesEditor({
|
|
4435
|
+
value,
|
|
4436
|
+
onChange,
|
|
4437
|
+
dataSources,
|
|
4438
|
+
scopeVars,
|
|
4439
|
+
allowExpressions = true
|
|
4440
|
+
}) {
|
|
4441
|
+
const humanise = useTokenText(dataSources, scopeVars);
|
|
4442
|
+
const summarise = (text) => allowExpressions ? text : humanise(text);
|
|
4443
|
+
return /* @__PURE__ */ jsx20(
|
|
4069
4444
|
ListEditor,
|
|
4070
4445
|
{
|
|
4071
4446
|
items: value,
|
|
4072
4447
|
onChange,
|
|
4073
4448
|
itemLabel: "Item",
|
|
4074
|
-
summary: (k) => k.label || "Item",
|
|
4449
|
+
summary: (k) => summarise(k.label) || "Item",
|
|
4075
4450
|
create: () => ({ id: createId2("k"), label: "Label", value: "" }),
|
|
4076
|
-
renderItem: (k, update) =>
|
|
4077
|
-
|
|
4078
|
-
/* @__PURE__ */
|
|
4079
|
-
|
|
4080
|
-
|
|
4451
|
+
renderItem: (k, update) => {
|
|
4452
|
+
const bound = /^\{\{[^}]*\}\}$/.test(k.value.trim());
|
|
4453
|
+
return /* @__PURE__ */ jsxs19(Fragment3, { children: [
|
|
4454
|
+
/* @__PURE__ */ jsx20(Field, { label: "Label", children: /* @__PURE__ */ jsx20(
|
|
4455
|
+
TextOrTokens,
|
|
4456
|
+
{
|
|
4457
|
+
value: k.label,
|
|
4458
|
+
onChange: (label) => update({ label }),
|
|
4459
|
+
dataSources,
|
|
4460
|
+
scopeVars,
|
|
4461
|
+
allowExpressions
|
|
4462
|
+
}
|
|
4463
|
+
) }),
|
|
4464
|
+
/* @__PURE__ */ jsx20(Field, { label: "Shows", stacked: true, children: /* @__PURE__ */ jsx20(
|
|
4465
|
+
Segmented,
|
|
4466
|
+
{
|
|
4467
|
+
value: bound ? "field" : "text",
|
|
4468
|
+
ariaLabel: "What this row shows",
|
|
4469
|
+
onChange: (mode) => update({ value: mode === "field" ? "" : k.value.replace(/\{\{|\}\}/g, "").trim() }),
|
|
4470
|
+
options: [
|
|
4471
|
+
{ value: "field", label: "A field" },
|
|
4472
|
+
{ value: "text", label: "Some text" }
|
|
4473
|
+
]
|
|
4474
|
+
}
|
|
4475
|
+
) }),
|
|
4476
|
+
bound ? /* @__PURE__ */ jsx20(Field, { label: "Field", stacked: true, children: /* @__PURE__ */ jsx20(
|
|
4477
|
+
BindingField,
|
|
4478
|
+
{
|
|
4479
|
+
value: k.value.replace(/^\{\{\s*|\s*\}\}$/g, ""),
|
|
4480
|
+
onChange: (v) => update({ value: v ? `{{ ${v} }}` : "" }),
|
|
4481
|
+
dataSources,
|
|
4482
|
+
scopeVars,
|
|
4483
|
+
placeholder: "Choose a field",
|
|
4484
|
+
allowExpression: allowExpressions
|
|
4485
|
+
}
|
|
4486
|
+
) }) : /* @__PURE__ */ jsx20(Field, { label: "Text", stacked: true, children: /* @__PURE__ */ jsx20(
|
|
4487
|
+
TextOrTokens,
|
|
4488
|
+
{
|
|
4489
|
+
value: k.value,
|
|
4490
|
+
onChange: (value2) => update({ value: value2 }),
|
|
4491
|
+
dataSources,
|
|
4492
|
+
scopeVars,
|
|
4493
|
+
allowExpressions,
|
|
4494
|
+
placeholder: "Type what this row should say"
|
|
4495
|
+
}
|
|
4496
|
+
) })
|
|
4497
|
+
] });
|
|
4498
|
+
}
|
|
4081
4499
|
}
|
|
4082
4500
|
);
|
|
4083
4501
|
}
|
|
4502
|
+
function TextOrTokens({
|
|
4503
|
+
value,
|
|
4504
|
+
onChange,
|
|
4505
|
+
dataSources,
|
|
4506
|
+
scopeVars,
|
|
4507
|
+
allowExpressions,
|
|
4508
|
+
placeholder
|
|
4509
|
+
}) {
|
|
4510
|
+
const humanise = useTokenText(dataSources, scopeVars);
|
|
4511
|
+
if (!allowExpressions && value.includes("{{")) {
|
|
4512
|
+
return /* @__PURE__ */ jsx20(TextInput, { value: humanise(value), onChange: () => void 0, readOnly: true, title: "This line fills itself in from your data." });
|
|
4513
|
+
}
|
|
4514
|
+
return /* @__PURE__ */ jsx20(TextInput, { value, onChange, lazy: true, ...placeholder ? { placeholder } : {} });
|
|
4515
|
+
}
|
|
4084
4516
|
function SubFormList({ value, onChange, fields, itemLabel, summary, renderField }) {
|
|
4085
|
-
return /* @__PURE__ */
|
|
4517
|
+
return /* @__PURE__ */ jsx20(
|
|
4086
4518
|
ListEditor,
|
|
4087
4519
|
{
|
|
4088
4520
|
items: value,
|
|
@@ -4090,13 +4522,30 @@ function SubFormList({ value, onChange, fields, itemLabel, summary, renderField
|
|
|
4090
4522
|
itemLabel: itemLabel ?? "Item",
|
|
4091
4523
|
summary: (it, i) => summary?.(it, i) || String(it.label ?? it.header ?? `${itemLabel ?? "Item"} ${i + 1}`),
|
|
4092
4524
|
create: () => ({ id: createId2("i") }),
|
|
4093
|
-
renderItem: (it, update) => /* @__PURE__ */
|
|
4525
|
+
renderItem: (it, update) => /* @__PURE__ */ jsx20(Fragment3, { children: fields.map((f) => /* @__PURE__ */ jsx20("div", { children: renderField(f, it[f.prop], (v) => update({ [f.prop]: v })) }, f.prop)) })
|
|
4094
4526
|
}
|
|
4095
4527
|
);
|
|
4096
4528
|
}
|
|
4097
4529
|
|
|
4530
|
+
// src/inspector/font-options.ts
|
|
4531
|
+
function fontOptions(theme, current) {
|
|
4532
|
+
const first = (stack) => stack.split(",")[0].trim().replace(/^['"]|['"]$/g, "");
|
|
4533
|
+
const options = [
|
|
4534
|
+
{ value: "$typography.headingFont", label: `Heading font (${first(theme.typography.headingFont)})` },
|
|
4535
|
+
{ value: "$typography.bodyFont", label: `Body font (${first(theme.typography.bodyFont)})` },
|
|
4536
|
+
...theme.typography.monoFont ? [{ value: "$typography.monoFont", label: `Mono font (${first(theme.typography.monoFont)})` }] : []
|
|
4537
|
+
];
|
|
4538
|
+
for (const face of theme.fonts ?? []) {
|
|
4539
|
+
if (!options.some((o) => o.value === face.family)) options.push({ value: face.family, label: face.family });
|
|
4540
|
+
}
|
|
4541
|
+
if (current && !options.some((o) => o.value === current)) {
|
|
4542
|
+
options.push({ value: current, label: `${current.startsWith("$typography.") ? current.slice("$typography.".length) : current} (not in this theme)` });
|
|
4543
|
+
}
|
|
4544
|
+
return options;
|
|
4545
|
+
}
|
|
4546
|
+
|
|
4098
4547
|
// src/inspector/fields/FieldRenderer.tsx
|
|
4099
|
-
import { jsx as
|
|
4548
|
+
import { jsx as jsx21, jsxs as jsxs20 } from "react/jsx-runtime";
|
|
4100
4549
|
function getPath(obj, path) {
|
|
4101
4550
|
let cur = obj;
|
|
4102
4551
|
for (const p of path.split(".")) {
|
|
@@ -4105,18 +4554,24 @@ function getPath(obj, path) {
|
|
|
4105
4554
|
}
|
|
4106
4555
|
return cur;
|
|
4107
4556
|
}
|
|
4108
|
-
var COMMON_FONTS = ["Inter", "Roboto", "Open Sans", "Lato", "Source Sans 3", "Helvetica Neue", "Arial", "Georgia", "Times New Roman", "Merriweather", "Playfair Display", "Courier New", "JetBrains Mono"];
|
|
4109
4557
|
function FieldRenderer({ field, value, onChange, ctx }) {
|
|
4110
4558
|
const description = typeof field.description === "function" ? field.description(ctx.props) : field.description;
|
|
4111
|
-
const
|
|
4559
|
+
const humanise = useTokenText(ctx.dataSources, ctx.scopeVars);
|
|
4560
|
+
const wrap = (control, opts = {}) => /* @__PURE__ */ jsx21(Field, { label: field.label, stacked: opts.stacked ?? field.fullWidth, hint: opts.hint ?? description, error: opts.error, children: control });
|
|
4112
4561
|
const str = typeof value === "string" ? value : value === void 0 || value === null ? "" : String(value);
|
|
4113
4562
|
switch (field.kind) {
|
|
4114
4563
|
case "text": {
|
|
4115
4564
|
const tokens = field.allowTokens !== false && ctx.allowExpressions;
|
|
4565
|
+
if (!tokens && str.includes("{{")) {
|
|
4566
|
+
return wrap(
|
|
4567
|
+
/* @__PURE__ */ jsx21("div", { className: "bpe-binding", children: /* @__PURE__ */ jsx21(TextInput, { value: humanise(str), onChange: () => void 0, readOnly: true, title: "This line fills itself in from your data." }) }),
|
|
4568
|
+
{ hint: description ?? "Fills itself in from your data." }
|
|
4569
|
+
);
|
|
4570
|
+
}
|
|
4116
4571
|
return wrap(
|
|
4117
|
-
/* @__PURE__ */
|
|
4118
|
-
/* @__PURE__ */
|
|
4119
|
-
tokens ? /* @__PURE__ */
|
|
4572
|
+
/* @__PURE__ */ jsxs20("div", { className: "bpe-binding", children: [
|
|
4573
|
+
/* @__PURE__ */ jsx21(TextInput, { value: str, onChange: (v) => onChange(v), lazy: true, placeholder: field.placeholder }),
|
|
4574
|
+
tokens ? /* @__PURE__ */ jsx21(Popover, { className: "bpe-picker", trigger: /* @__PURE__ */ jsx21(Button, { size: "sm", variant: "ghost", icon: "braces", tooltip: "Insert field" }), children: (close) => /* @__PURE__ */ jsx21(DataPicker, { dataSources: ctx.dataSources, scopeVars: ctx.scopeVars, onPick: (expr) => {
|
|
4120
4575
|
onChange(`${str}${str && !str.endsWith(" ") ? " " : ""}{{ ${expr} }}`);
|
|
4121
4576
|
close();
|
|
4122
4577
|
} }) }) : null
|
|
@@ -4124,41 +4579,46 @@ function FieldRenderer({ field, value, onChange, ctx }) {
|
|
|
4124
4579
|
);
|
|
4125
4580
|
}
|
|
4126
4581
|
case "textarea":
|
|
4127
|
-
return wrap(/* @__PURE__ */
|
|
4582
|
+
return wrap(/* @__PURE__ */ jsx21(TextArea, { value: str, rows: field.rows ?? 3, placeholder: field.placeholder, onChange: (e) => onChange(e.target.value) }), { stacked: true });
|
|
4128
4583
|
case "richtext": {
|
|
4129
4584
|
const doc = isRichTextDoc3(value) ? value : richTextFromTemplate3(str);
|
|
4130
4585
|
const plain = richTextToPlain2(doc);
|
|
4131
4586
|
return wrap(
|
|
4132
|
-
/* @__PURE__ */
|
|
4587
|
+
/* @__PURE__ */ jsx21(TextArea, { value: plain, rows: 4, onChange: (e) => onChange(richTextFromTemplate3(e.target.value)), placeholder: "Type text, use {{ field }} for data. Double-click the block on the canvas for rich formatting." }),
|
|
4133
4588
|
{ stacked: true, hint: "Double-click the block on the canvas for formatting, lists and links." }
|
|
4134
4589
|
);
|
|
4135
4590
|
}
|
|
4136
4591
|
case "number":
|
|
4137
|
-
return wrap(/* @__PURE__ */
|
|
4592
|
+
return wrap(/* @__PURE__ */ jsx21(NumberInput, { value: typeof value === "number" ? value : null, onChange: (v) => onChange(v), min: field.min, max: field.max, step: field.step, unit: field.unit, placeholder: "auto" }));
|
|
4138
4593
|
case "toggle":
|
|
4139
|
-
return wrap(/* @__PURE__ */
|
|
4594
|
+
return wrap(/* @__PURE__ */ jsx21(Switch, { checked: !!value, onChange: (v) => onChange(v), label: field.label }));
|
|
4140
4595
|
case "select": {
|
|
4141
4596
|
const options = field.optionsFrom ? field.optionsFrom(ctx.props) : field.options ?? [];
|
|
4142
|
-
return wrap(/* @__PURE__ */
|
|
4597
|
+
return wrap(/* @__PURE__ */ jsx21(Select, { value: str, onChange: (v) => onChange(coerce(v, options)), options: options.map((o) => ({ value: String(o.value), label: o.label })) }));
|
|
4143
4598
|
}
|
|
4144
4599
|
case "segmented":
|
|
4145
|
-
return wrap(/* @__PURE__ */
|
|
4600
|
+
return wrap(/* @__PURE__ */ jsx21(Segmented, { value: str, onChange: (v) => onChange(coerce(v, field.options)), options: field.options.map((o) => ({ value: String(o.value), label: o.label, icon: o.icon })), iconOnly: field.options.every((o) => !!o.icon) && field.options.length > 3, ariaLabel: field.label }));
|
|
4146
4601
|
case "color":
|
|
4147
|
-
return wrap(/* @__PURE__ */
|
|
4602
|
+
return wrap(/* @__PURE__ */ jsx21(ColorField, { value: str || void 0, onChange: (v) => onChange(v ?? ""), theme: ctx.theme, allowTransparent: field.allowTransparent }));
|
|
4148
4603
|
case "font":
|
|
4149
|
-
return wrap(/* @__PURE__ */
|
|
4604
|
+
return wrap(/* @__PURE__ */ jsx21(FontSelect, { value: str, onChange, theme: ctx.theme }));
|
|
4150
4605
|
case "textStyle":
|
|
4151
|
-
return wrap(/* @__PURE__ */
|
|
4606
|
+
return wrap(/* @__PURE__ */ jsx21(Select, { value: str, onChange: (v) => onChange(v || void 0), placeholder: "Default", options: ctx.textStyles.map((s) => ({ value: s, label: textStyleLabel(s) })) }));
|
|
4152
4607
|
case "binding":
|
|
4153
|
-
return wrap(/* @__PURE__ */
|
|
4608
|
+
return wrap(/* @__PURE__ */ jsx21(BindingField, { value: str, onChange: (v) => onChange(v), dataSources: ctx.dataSources, scopeVars: ctx.scopeVars, types: field.valueType ? Array.isArray(field.valueType) ? field.valueType : [field.valueType] : void 0, placeholder: field.placeholder, allowExpression: ctx.allowExpressions }));
|
|
4154
4609
|
case "collection":
|
|
4155
|
-
return wrap(/* @__PURE__ */
|
|
4610
|
+
return wrap(/* @__PURE__ */ jsx21(BindingField, { value: str, onChange: (v) => onChange(v), dataSources: ctx.dataSources, scopeVars: ctx.scopeVars, arraysOnly: true, placeholder: field.placeholder, allowExpression: ctx.allowExpressions }));
|
|
4156
4611
|
case "expression": {
|
|
4612
|
+
if (!ctx.allowExpressions) {
|
|
4613
|
+
return wrap(
|
|
4614
|
+
/* @__PURE__ */ jsx21(BindingField, { value: str, onChange: (v) => onChange(v), dataSources: ctx.dataSources, scopeVars: ctx.scopeVars, placeholder: field.placeholder, allowExpression: false, allowCalculation: false })
|
|
4615
|
+
);
|
|
4616
|
+
}
|
|
4157
4617
|
const err = str.trim() ? checkExpressionSyntax3(str) : null;
|
|
4158
4618
|
return wrap(
|
|
4159
|
-
/* @__PURE__ */
|
|
4160
|
-
/* @__PURE__ */
|
|
4161
|
-
/* @__PURE__ */
|
|
4619
|
+
/* @__PURE__ */ jsxs20("div", { className: "bpe-binding", children: [
|
|
4620
|
+
/* @__PURE__ */ jsx21(TextInput, { value: str, onChange: (v) => onChange(v), lazy: true, mono: true, placeholder: field.placeholder, invalid: !!err }),
|
|
4621
|
+
/* @__PURE__ */ jsx21(Popover, { className: "bpe-picker", trigger: /* @__PURE__ */ jsx21(Button, { size: "sm", variant: "ghost", icon: "braces", tooltip: "Pick field" }), children: (close) => /* @__PURE__ */ jsx21(DataPicker, { dataSources: ctx.dataSources, scopeVars: ctx.scopeVars, value: str, onPick: (expr) => {
|
|
4162
4622
|
onChange(expr);
|
|
4163
4623
|
close();
|
|
4164
4624
|
} }) })
|
|
@@ -4167,7 +4627,7 @@ function FieldRenderer({ field, value, onChange, ctx }) {
|
|
|
4167
4627
|
);
|
|
4168
4628
|
}
|
|
4169
4629
|
case "image":
|
|
4170
|
-
return /* @__PURE__ */
|
|
4630
|
+
return /* @__PURE__ */ jsx21(
|
|
4171
4631
|
ImageFieldRow,
|
|
4172
4632
|
{
|
|
4173
4633
|
field,
|
|
@@ -4178,16 +4638,28 @@ function FieldRenderer({ field, value, onChange, ctx }) {
|
|
|
4178
4638
|
}
|
|
4179
4639
|
);
|
|
4180
4640
|
case "condition":
|
|
4181
|
-
return wrap(/* @__PURE__ */
|
|
4641
|
+
return wrap(/* @__PURE__ */ jsx21(ConditionBuilder, { value, onChange: (v) => onChange(v ?? ""), dataSources: ctx.dataSources, scopeVars: ctx.scopeVars, allowExpression: ctx.allowExpressions }), { stacked: true });
|
|
4182
4642
|
case "columns":
|
|
4183
|
-
return wrap(/* @__PURE__ */
|
|
4643
|
+
return wrap(/* @__PURE__ */ jsx21(ColumnsEditor, { value: Array.isArray(value) ? value : [], onChange, dataSources: ctx.dataSources, scopeVars: ctx.scopeVars }), { stacked: true });
|
|
4184
4644
|
case "series":
|
|
4185
|
-
return wrap(/* @__PURE__ */
|
|
4645
|
+
return wrap(/* @__PURE__ */ jsx21(SeriesEditor, { value: Array.isArray(value) ? value : [], onChange, dataSources: ctx.dataSources, scopeVars: ctx.scopeVars, renderColor: (v, set) => /* @__PURE__ */ jsx21(ColorField, { value: v, onChange: set, theme: ctx.theme, placeholder: "Palette" }) }), { stacked: true });
|
|
4186
4646
|
case "keyValues":
|
|
4187
|
-
return wrap(
|
|
4647
|
+
return wrap(
|
|
4648
|
+
/* @__PURE__ */ jsx21(
|
|
4649
|
+
KeyValuesEditor,
|
|
4650
|
+
{
|
|
4651
|
+
value: Array.isArray(value) ? value : [],
|
|
4652
|
+
onChange,
|
|
4653
|
+
dataSources: ctx.dataSources,
|
|
4654
|
+
scopeVars: ctx.scopeVars,
|
|
4655
|
+
allowExpressions: ctx.allowExpressions
|
|
4656
|
+
}
|
|
4657
|
+
),
|
|
4658
|
+
{ stacked: true }
|
|
4659
|
+
);
|
|
4188
4660
|
case "list":
|
|
4189
4661
|
return wrap(
|
|
4190
|
-
/* @__PURE__ */
|
|
4662
|
+
/* @__PURE__ */ jsx21(
|
|
4191
4663
|
SubFormList,
|
|
4192
4664
|
{
|
|
4193
4665
|
value: Array.isArray(value) ? value : [],
|
|
@@ -4195,13 +4667,13 @@ function FieldRenderer({ field, value, onChange, ctx }) {
|
|
|
4195
4667
|
fields: field.fields,
|
|
4196
4668
|
itemLabel: field.itemLabel,
|
|
4197
4669
|
summary: field.itemSummary ? (item, i) => field.itemSummary(item, i, ctx.props) : void 0,
|
|
4198
|
-
renderField: (f, v, set) => /* @__PURE__ */
|
|
4670
|
+
renderField: (f, v, set) => /* @__PURE__ */ jsx21(FieldRenderer, { field: f, value: v, onChange: set, ctx })
|
|
4199
4671
|
}
|
|
4200
4672
|
),
|
|
4201
4673
|
{ stacked: true }
|
|
4202
4674
|
);
|
|
4203
4675
|
case "custom":
|
|
4204
|
-
return wrap(/* @__PURE__ */
|
|
4676
|
+
return wrap(/* @__PURE__ */ jsx21(CustomFieldHost, { field, value, onChange, ctx }), { stacked: true });
|
|
4205
4677
|
}
|
|
4206
4678
|
}
|
|
4207
4679
|
function coerce(v, options) {
|
|
@@ -4209,18 +4681,12 @@ function coerce(v, options) {
|
|
|
4209
4681
|
return match ? match.value : v;
|
|
4210
4682
|
}
|
|
4211
4683
|
function FontSelect({ value, onChange, theme }) {
|
|
4212
|
-
|
|
4213
|
-
{ value: "$typography.headingFont", label: `Heading font (${theme.typography.headingFont.split(",")[0]})` },
|
|
4214
|
-
{ value: "$typography.bodyFont", label: `Body font (${theme.typography.bodyFont.split(",")[0]})` },
|
|
4215
|
-
...theme.typography.monoFont ? [{ value: "$typography.monoFont", label: "Mono font" }] : []
|
|
4216
|
-
];
|
|
4217
|
-
const fonts = [...(theme.fonts ?? []).map((f) => f.family), ...COMMON_FONTS].filter((f, i, a) => a.indexOf(f) === i);
|
|
4218
|
-
return /* @__PURE__ */ jsx20(Select, { value, onChange, placeholder: "Default", options: [...themeFonts, ...fonts.map((f) => ({ value: f, label: f }))] });
|
|
4684
|
+
return /* @__PURE__ */ jsx21(Select, { value, onChange, placeholder: "Default", options: fontOptions(theme, value) });
|
|
4219
4685
|
}
|
|
4220
4686
|
function CustomFieldHost({ field, value, onChange, ctx }) {
|
|
4221
4687
|
const session = useSession();
|
|
4222
|
-
const ref =
|
|
4223
|
-
const mounted =
|
|
4688
|
+
const ref = useRef8(null);
|
|
4689
|
+
const mounted = useRef8(false);
|
|
4224
4690
|
const api = () => ({
|
|
4225
4691
|
value,
|
|
4226
4692
|
props: ctx.props,
|
|
@@ -4228,10 +4694,10 @@ function CustomFieldHost({ field, value, onChange, ctx }) {
|
|
|
4228
4694
|
setValue: onChange,
|
|
4229
4695
|
setProps: ctx.setProps,
|
|
4230
4696
|
dataSources: ctx.dataSources,
|
|
4231
|
-
schemaScope:
|
|
4697
|
+
schemaScope: createSchemaScope6(ctx.dataSources).child(ctx.scopeVars ?? {}),
|
|
4232
4698
|
theme: ctx.theme
|
|
4233
4699
|
});
|
|
4234
|
-
|
|
4700
|
+
useEffect7(() => {
|
|
4235
4701
|
const el = ref.current;
|
|
4236
4702
|
if (!el) return;
|
|
4237
4703
|
if (!mounted.current) {
|
|
@@ -4239,14 +4705,14 @@ function CustomFieldHost({ field, value, onChange, ctx }) {
|
|
|
4239
4705
|
mounted.current = true;
|
|
4240
4706
|
} else field.component.update?.(api());
|
|
4241
4707
|
});
|
|
4242
|
-
|
|
4708
|
+
useEffect7(() => {
|
|
4243
4709
|
const el = ref.current;
|
|
4244
4710
|
return () => {
|
|
4245
4711
|
if (el && mounted.current) field.component.unmount?.(el);
|
|
4246
4712
|
mounted.current = false;
|
|
4247
4713
|
};
|
|
4248
4714
|
}, [field.component, session]);
|
|
4249
|
-
return /* @__PURE__ */
|
|
4715
|
+
return /* @__PURE__ */ jsx21("div", { ref, style: { width: "100%" } });
|
|
4250
4716
|
}
|
|
4251
4717
|
function ImageFieldRow({
|
|
4252
4718
|
field,
|
|
@@ -4255,9 +4721,9 @@ function ImageFieldRow({
|
|
|
4255
4721
|
ctx,
|
|
4256
4722
|
render
|
|
4257
4723
|
}) {
|
|
4258
|
-
const [error, setError] =
|
|
4724
|
+
const [error, setError] = useState17(null);
|
|
4259
4725
|
return render(
|
|
4260
|
-
/* @__PURE__ */
|
|
4726
|
+
/* @__PURE__ */ jsx21(
|
|
4261
4727
|
ImageField,
|
|
4262
4728
|
{
|
|
4263
4729
|
value,
|
|
@@ -4277,13 +4743,13 @@ function ImageFieldRow({
|
|
|
4277
4743
|
|
|
4278
4744
|
// src/inspector/StyleGroups.tsx
|
|
4279
4745
|
import { textStyleLabel as textStyleLabel2 } from "@broadpaper/core";
|
|
4280
|
-
import { Fragment as Fragment4, jsx as
|
|
4746
|
+
import { Fragment as Fragment4, jsx as jsx22, jsxs as jsxs21 } from "react/jsx-runtime";
|
|
4281
4747
|
var SIDES = ["top", "right", "bottom", "left"];
|
|
4282
4748
|
function SpacingGrid({ value, onChange }) {
|
|
4283
4749
|
const v = value ?? {};
|
|
4284
|
-
return /* @__PURE__ */
|
|
4285
|
-
/* @__PURE__ */
|
|
4286
|
-
/* @__PURE__ */
|
|
4750
|
+
return /* @__PURE__ */ jsxs21("div", { children: [
|
|
4751
|
+
/* @__PURE__ */ jsx22("div", { className: "bpe-spacing-grid", children: SIDES.map((s) => /* @__PURE__ */ jsx22(NumberInput, { value: v[s] ?? null, onChange: (n) => onChange({ ...v, [s]: n ?? void 0 }), min: -200, max: 400, placeholder: "0", "aria-label": s }, s)) }),
|
|
4752
|
+
/* @__PURE__ */ jsx22("div", { className: "bpe-spacing-grid", children: SIDES.map((s) => /* @__PURE__ */ jsx22("div", { className: "bpe-spacing-grid__lbl", children: s }, s)) })
|
|
4287
4753
|
] });
|
|
4288
4754
|
}
|
|
4289
4755
|
function StyleGroups({ node, groups, theme, textStyles, setStyle, setFlow, allowOverrides }) {
|
|
@@ -4291,62 +4757,62 @@ function StyleGroups({ node, groups, theme, textStyles, setStyle, setFlow, allow
|
|
|
4291
4757
|
const f = node.flow ?? {};
|
|
4292
4758
|
const has = (g) => groups.includes(g);
|
|
4293
4759
|
const hasOverrides = Object.keys(s).length > 0;
|
|
4294
|
-
return /* @__PURE__ */
|
|
4295
|
-
allowOverrides && (has("textStyle") || has("typography") || has("alignment")) ? /* @__PURE__ */
|
|
4296
|
-
has("textStyle") ? /* @__PURE__ */
|
|
4297
|
-
has("typography") ? /* @__PURE__ */
|
|
4298
|
-
/* @__PURE__ */
|
|
4299
|
-
/* @__PURE__ */
|
|
4300
|
-
/* @__PURE__ */
|
|
4301
|
-
/* @__PURE__ */
|
|
4302
|
-
/* @__PURE__ */
|
|
4303
|
-
/* @__PURE__ */
|
|
4304
|
-
/* @__PURE__ */
|
|
4305
|
-
/* @__PURE__ */
|
|
4760
|
+
return /* @__PURE__ */ jsxs21(Fragment4, { children: [
|
|
4761
|
+
allowOverrides && (has("textStyle") || has("typography") || has("alignment")) ? /* @__PURE__ */ jsx22(Section, { title: "Typography", actions: hasOverrides ? /* @__PURE__ */ jsx22(Button, { size: "sm", variant: "ghost", icon: "rotate-ccw", tooltip: "Reset style overrides", onClick: () => setStyle(null) }) : null, children: /* @__PURE__ */ jsxs21("div", { className: "bpe-prop-grid", children: [
|
|
4762
|
+
has("textStyle") ? /* @__PURE__ */ jsx22(Field, { label: "Text style", children: /* @__PURE__ */ jsx22(Select, { value: s.textStyle ?? "", onChange: (v) => setStyle({ textStyle: v || void 0 }), placeholder: "Block default", options: textStyles.map((t) => ({ value: t, label: textStyleLabel2(t) })) }) }) : null,
|
|
4763
|
+
has("typography") ? /* @__PURE__ */ jsxs21(Fragment4, { children: [
|
|
4764
|
+
/* @__PURE__ */ jsx22(Field, { label: "Font", children: /* @__PURE__ */ jsx22(Select, { value: s.fontFamily ?? "", onChange: (v) => setStyle({ fontFamily: v || void 0 }), placeholder: "Inherit", options: fontOptions(theme, s.fontFamily) }) }),
|
|
4765
|
+
/* @__PURE__ */ jsx22(Field, { label: "Size", children: /* @__PURE__ */ jsx22(NumberInput, { value: s.fontSize ?? null, onChange: (v) => setStyle({ fontSize: v ?? void 0 }), min: 4, max: 200, unit: "px", placeholder: "inherit" }) }),
|
|
4766
|
+
/* @__PURE__ */ jsx22(Field, { label: "Weight", children: /* @__PURE__ */ jsx22(Select, { value: s.fontWeight === void 0 ? "" : String(s.fontWeight), onChange: (v) => setStyle({ fontWeight: v ? Number(v) : void 0 }), placeholder: "Inherit", options: [300, 400, 500, 600, 700, 800].map((w) => ({ value: String(w), label: w === 400 ? "Regular" : w === 700 ? "Bold" : String(w) })) }) }),
|
|
4767
|
+
/* @__PURE__ */ jsx22(Field, { label: "Line height", children: /* @__PURE__ */ jsx22(NumberInput, { value: s.lineHeight ?? null, onChange: (v) => setStyle({ lineHeight: v ?? void 0 }), min: 0.6, max: 4, step: 0.05, placeholder: "inherit" }) }),
|
|
4768
|
+
/* @__PURE__ */ jsx22(Field, { label: "Spacing", children: /* @__PURE__ */ jsx22(NumberInput, { value: s.letterSpacing ?? null, onChange: (v) => setStyle({ letterSpacing: v ?? void 0 }), min: -0.2, max: 1, step: 0.01, unit: "em", placeholder: "0" }) }),
|
|
4769
|
+
/* @__PURE__ */ jsx22(Field, { label: "Case", children: /* @__PURE__ */ jsx22(Segmented, { value: s.textTransform ?? "none", onChange: (v) => setStyle({ textTransform: v === "none" ? void 0 : v }), options: [{ value: "none", label: "Aa" }, { value: "uppercase", label: "AA" }, { value: "lowercase", label: "aa" }, { value: "capitalize", label: "Aa Bb" }] }) }),
|
|
4770
|
+
/* @__PURE__ */ jsx22(Field, { label: "Style", children: /* @__PURE__ */ jsx22(Segmented, { value: s.fontStyle ?? "normal", onChange: (v) => setStyle({ fontStyle: v === "normal" ? void 0 : "italic" }), options: [{ value: "normal", label: "Normal" }, { value: "italic", label: "Italic" }] }) }),
|
|
4771
|
+
/* @__PURE__ */ jsx22(Field, { label: "Colour", children: /* @__PURE__ */ jsx22(ColorField, { value: s.color, onChange: (v) => setStyle({ color: v }), theme, allowTransparent: false, placeholder: "Inherit" }) })
|
|
4306
4772
|
] }) : null,
|
|
4307
|
-
has("alignment") ? /* @__PURE__ */
|
|
4773
|
+
has("alignment") ? /* @__PURE__ */ jsx22(Field, { label: "Align", children: /* @__PURE__ */ jsx22(Segmented, { value: s.textAlign ?? "left", onChange: (v) => setStyle({ textAlign: v === "left" ? void 0 : v }), iconOnly: true, ariaLabel: "Text alignment", options: [{ value: "left", label: "Left", icon: "align-left" }, { value: "center", label: "Centre", icon: "align-center" }, { value: "right", label: "Right", icon: "align-right" }, { value: "justify", label: "Justify", icon: "align-justify" }] }) }) : null
|
|
4308
4774
|
] }) }) : null,
|
|
4309
|
-
allowOverrides && (has("spacing") || has("size")) ? /* @__PURE__ */
|
|
4310
|
-
has("spacing") ? /* @__PURE__ */
|
|
4311
|
-
/* @__PURE__ */
|
|
4312
|
-
/* @__PURE__ */
|
|
4775
|
+
allowOverrides && (has("spacing") || has("size")) ? /* @__PURE__ */ jsx22(Section, { title: "Spacing & size", defaultOpen: false, children: /* @__PURE__ */ jsxs21("div", { className: "bpe-prop-grid", children: [
|
|
4776
|
+
has("spacing") ? /* @__PURE__ */ jsxs21(Fragment4, { children: [
|
|
4777
|
+
/* @__PURE__ */ jsx22(Field, { label: "Margin", stacked: true, children: /* @__PURE__ */ jsx22(SpacingGrid, { value: s.margin, onChange: (v) => setStyle({ margin: v }) }) }),
|
|
4778
|
+
/* @__PURE__ */ jsx22(Field, { label: "Padding", stacked: true, children: /* @__PURE__ */ jsx22(SpacingGrid, { value: s.padding, onChange: (v) => setStyle({ padding: v }) }) })
|
|
4313
4779
|
] }) : null,
|
|
4314
|
-
has("size") ? /* @__PURE__ */
|
|
4315
|
-
/* @__PURE__ */
|
|
4316
|
-
/* @__PURE__ */
|
|
4780
|
+
has("size") ? /* @__PURE__ */ jsxs21(Fragment4, { children: [
|
|
4781
|
+
/* @__PURE__ */ jsx22(Field, { label: "Width", children: /* @__PURE__ */ jsx22(NumberInput, { value: typeof s.width === "number" ? s.width : null, onChange: (v) => setStyle({ width: v ?? void 0 }), min: 8, max: 2e3, unit: "px", placeholder: "auto" }) }),
|
|
4782
|
+
/* @__PURE__ */ jsx22(Field, { label: "Placement", children: /* @__PURE__ */ jsx22(Segmented, { value: s.align ?? "left", onChange: (v) => setStyle({ align: v === "left" ? void 0 : v }), options: [{ value: "left", label: "Left" }, { value: "center", label: "Centre" }, { value: "right", label: "Right" }] }) })
|
|
4317
4783
|
] }) : null
|
|
4318
4784
|
] }) }) : null,
|
|
4319
|
-
allowOverrides && (has("background") || has("border")) ? /* @__PURE__ */
|
|
4320
|
-
has("background") ? /* @__PURE__ */
|
|
4321
|
-
has("border") ? /* @__PURE__ */
|
|
4322
|
-
/* @__PURE__ */
|
|
4323
|
-
/* @__PURE__ */
|
|
4324
|
-
/* @__PURE__ */
|
|
4325
|
-
/* @__PURE__ */
|
|
4326
|
-
/* @__PURE__ */
|
|
4785
|
+
allowOverrides && (has("background") || has("border")) ? /* @__PURE__ */ jsx22(Section, { title: "Background & border", defaultOpen: false, children: /* @__PURE__ */ jsxs21("div", { className: "bpe-prop-grid", children: [
|
|
4786
|
+
has("background") ? /* @__PURE__ */ jsx22(Field, { label: "Background", children: /* @__PURE__ */ jsx22(ColorField, { value: s.background, onChange: (v) => setStyle({ background: v }), theme, placeholder: "None" }) }) : null,
|
|
4787
|
+
has("border") ? /* @__PURE__ */ jsxs21(Fragment4, { children: [
|
|
4788
|
+
/* @__PURE__ */ jsx22(Field, { label: "Border", children: /* @__PURE__ */ jsx22(NumberInput, { value: s.border?.width ?? null, onChange: (v) => setStyle({ border: { ...s.border ?? {}, width: v ?? void 0 } }), min: 0, max: 20, unit: "px", placeholder: "0" }) }),
|
|
4789
|
+
/* @__PURE__ */ jsx22(Field, { label: "Border colour", children: /* @__PURE__ */ jsx22(ColorField, { value: s.border?.color, onChange: (v) => setStyle({ border: { ...s.border ?? {}, color: v } }), theme, placeholder: "Theme border" }) }),
|
|
4790
|
+
/* @__PURE__ */ jsx22(Field, { label: "Line", children: /* @__PURE__ */ jsx22(Segmented, { value: s.border?.style ?? "solid", onChange: (v) => setStyle({ border: { ...s.border ?? {}, style: v } }), options: [{ value: "solid", label: "Solid" }, { value: "dashed", label: "Dashed" }, { value: "dotted", label: "Dotted" }] }) }),
|
|
4791
|
+
/* @__PURE__ */ jsx22(Field, { label: "Radius", children: /* @__PURE__ */ jsx22(NumberInput, { value: s.border?.radius ?? null, onChange: (v) => setStyle({ border: { ...s.border ?? {}, radius: v ?? void 0 } }), min: 0, max: 100, unit: "px", placeholder: "0" }) }),
|
|
4792
|
+
/* @__PURE__ */ jsx22(Field, { label: "Sides", children: /* @__PURE__ */ jsx22("div", { style: { display: "flex", gap: 4 }, children: SIDES.map((side) => {
|
|
4327
4793
|
const sides = s.border?.sides ?? [...SIDES];
|
|
4328
4794
|
const on = sides.includes(side);
|
|
4329
|
-
return /* @__PURE__ */
|
|
4795
|
+
return /* @__PURE__ */ jsx22(Button, { size: "sm", active: on, onClick: () => setStyle({ border: { ...s.border ?? {}, sides: on ? sides.filter((x) => x !== side) : [...sides, side] } }), style: { flex: 1, textTransform: "capitalize" }, children: side[0].toUpperCase() }, side);
|
|
4330
4796
|
}) }) })
|
|
4331
4797
|
] }) : null,
|
|
4332
|
-
/* @__PURE__ */
|
|
4798
|
+
/* @__PURE__ */ jsx22(Field, { label: "Opacity", children: /* @__PURE__ */ jsx22(NumberInput, { value: s.opacity ?? null, onChange: (v) => setStyle({ opacity: v ?? void 0 }), min: 0, max: 1, step: 0.05, placeholder: "1" }) })
|
|
4333
4799
|
] }) }) : null,
|
|
4334
|
-
has("flow") ? /* @__PURE__ */
|
|
4335
|
-
/* @__PURE__ */
|
|
4336
|
-
/* @__PURE__ */
|
|
4337
|
-
/* @__PURE__ */
|
|
4338
|
-
/* @__PURE__ */
|
|
4339
|
-
/* @__PURE__ */
|
|
4340
|
-
/* @__PURE__ */
|
|
4800
|
+
has("flow") ? /* @__PURE__ */ jsx22(Section, { title: "Page flow", defaultOpen: false, icon: "file-output", children: /* @__PURE__ */ jsxs21("div", { className: "bpe-prop-grid", children: [
|
|
4801
|
+
/* @__PURE__ */ jsx22(Field, { label: "Keep together", hint: "Never split this block across pages (unless it is taller than a page).", children: /* @__PURE__ */ jsx22(Switch, { checked: !!f.keepTogether, onChange: (v) => setFlow({ keepTogether: v || void 0 }) }) }),
|
|
4802
|
+
/* @__PURE__ */ jsx22(Field, { label: "Keep with next", hint: "Stay on the same page as the start of the next block.", children: /* @__PURE__ */ jsx22(Switch, { checked: !!f.keepWithNext, onChange: (v) => setFlow({ keepWithNext: v || void 0 }) }) }),
|
|
4803
|
+
/* @__PURE__ */ jsx22(Field, { label: "Break before", children: /* @__PURE__ */ jsx22(Segmented, { value: f.breakBefore ?? "auto", onChange: (v) => setFlow({ breakBefore: v === "auto" ? void 0 : "page" }), options: [{ value: "auto", label: "Auto" }, { value: "page", label: "New page" }] }) }),
|
|
4804
|
+
/* @__PURE__ */ jsx22(Field, { label: "Break after", children: /* @__PURE__ */ jsx22(Segmented, { value: f.breakAfter ?? "auto", onChange: (v) => setFlow({ breakAfter: v === "auto" ? void 0 : "page" }), options: [{ value: "auto", label: "Auto" }, { value: "page", label: "New page" }] }) }),
|
|
4805
|
+
/* @__PURE__ */ jsx22(Field, { label: "Min. lines before", children: /* @__PURE__ */ jsx22(NumberInput, { value: f.minOrphans ?? null, onChange: (v) => setFlow({ minOrphans: v ?? void 0 }), min: 1, max: 10, placeholder: "2" }) }),
|
|
4806
|
+
/* @__PURE__ */ jsx22(Field, { label: "Min. lines after", children: /* @__PURE__ */ jsx22(NumberInput, { value: f.minWidows ?? null, onChange: (v) => setFlow({ minWidows: v ?? void 0 }), min: 1, max: 10, placeholder: "2" }) })
|
|
4341
4807
|
] }) }) : null
|
|
4342
4808
|
] });
|
|
4343
4809
|
}
|
|
4344
4810
|
|
|
4345
4811
|
// src/inspector/NodeInspector.tsx
|
|
4346
|
-
import { Fragment as Fragment5, jsx as
|
|
4812
|
+
import { Fragment as Fragment5, jsx as jsx23, jsxs as jsxs22 } from "react/jsx-runtime";
|
|
4347
4813
|
function scopeVarsFor(entry, session) {
|
|
4348
4814
|
const chain = ancestors2(session.store.index(), entry.node.id);
|
|
4349
|
-
const root =
|
|
4815
|
+
const root = createSchemaScope7(session.dataSources);
|
|
4350
4816
|
let vars = {};
|
|
4351
4817
|
let scope = root;
|
|
4352
4818
|
for (const e of chain) {
|
|
@@ -4376,8 +4842,8 @@ function NodeInspector({ entry, actions }) {
|
|
|
4376
4842
|
const def = session.registry.get(node.type);
|
|
4377
4843
|
const theme = session.activeTheme;
|
|
4378
4844
|
const textStyles = Object.keys(session.store.template.styles.text);
|
|
4379
|
-
const scopeVars =
|
|
4380
|
-
const props =
|
|
4845
|
+
const scopeVars = useMemo8(() => scopeVarsFor(entry, session), [entry, session]);
|
|
4846
|
+
const props = useMemo8(() => ({ ...def ? typeof def.defaultProps === "function" ? def.defaultProps() : def.defaultProps : {}, ...node.props }), [def, node.props]);
|
|
4381
4847
|
const setProp = (path, value, label2 = "Edit properties") => session.store.dispatch({ type: "setProp", id: node.id, path, value }, { label: label2, mergeKey: `prop:${node.id}:${path}` });
|
|
4382
4848
|
const ctx = {
|
|
4383
4849
|
dataSources: session.dataSources,
|
|
@@ -4391,53 +4857,53 @@ function NodeInspector({ entry, actions }) {
|
|
|
4391
4857
|
};
|
|
4392
4858
|
const label = nodeLabel(session, node);
|
|
4393
4859
|
const issues = session.ui.get().issues.filter((i) => i.nodeId === node.id);
|
|
4394
|
-
return /* @__PURE__ */
|
|
4395
|
-
/* @__PURE__ */
|
|
4396
|
-
/* @__PURE__ */
|
|
4397
|
-
/* @__PURE__ */
|
|
4398
|
-
/* @__PURE__ */
|
|
4399
|
-
/* @__PURE__ */
|
|
4860
|
+
return /* @__PURE__ */ jsxs22(Fragment5, { children: [
|
|
4861
|
+
/* @__PURE__ */ jsxs22("div", { className: "bpe-inspector__header", children: [
|
|
4862
|
+
/* @__PURE__ */ jsx23("span", { className: "bpe-inspector__icon", children: /* @__PURE__ */ jsx23(Icon, { name: def?.icon ?? "box" }) }),
|
|
4863
|
+
/* @__PURE__ */ jsxs22("div", { style: { minWidth: 0 }, children: [
|
|
4864
|
+
/* @__PURE__ */ jsx23("div", { className: "bpe-inspector__title", title: label, children: label }),
|
|
4865
|
+
/* @__PURE__ */ jsxs22("div", { className: "bpe-inspector__subtitle", children: [
|
|
4400
4866
|
def?.name ?? node.type,
|
|
4401
4867
|
def?.license && def.license !== "community" ? ` \xB7 ${def.license}` : ""
|
|
4402
4868
|
] })
|
|
4403
4869
|
] }),
|
|
4404
|
-
/* @__PURE__ */
|
|
4405
|
-
/* @__PURE__ */
|
|
4406
|
-
node.type !== "column" ? /* @__PURE__ */
|
|
4870
|
+
/* @__PURE__ */ jsxs22("div", { className: "bpe-inspector__actions", children: [
|
|
4871
|
+
/* @__PURE__ */ jsx23(Button, { size: "sm", variant: "ghost", icon: node.locked ? "lock" : "unlock", tooltip: node.locked ? "Unlock" : "Lock", onClick: () => actions.toggleLock() }),
|
|
4872
|
+
node.type !== "column" ? /* @__PURE__ */ jsx23(Button, { size: "sm", variant: "ghost", icon: "trash", tooltip: "Delete", onClick: () => actions.deleteSelection(), disabled: !!node.locked }) : null
|
|
4407
4873
|
] })
|
|
4408
4874
|
] }),
|
|
4409
|
-
/* @__PURE__ */
|
|
4410
|
-
issues.length ? /* @__PURE__ */
|
|
4411
|
-
/* @__PURE__ */
|
|
4412
|
-
/* @__PURE__ */
|
|
4875
|
+
/* @__PURE__ */ jsxs22("div", { className: "bpe-inspector__body", children: [
|
|
4876
|
+
issues.length ? /* @__PURE__ */ jsx23("div", { style: { padding: "8px 12px 0" }, children: issues.map((i, k) => /* @__PURE__ */ jsxs22("div", { className: `bpe-issue bpe-issue--${i.severity}`, style: { padding: "4px 0" }, children: [
|
|
4877
|
+
/* @__PURE__ */ jsx23(Icon, { name: i.severity === "error" ? "alert-circle" : i.severity === "warning" ? "alert-triangle" : "info" }),
|
|
4878
|
+
/* @__PURE__ */ jsx23("span", { children: i.message })
|
|
4413
4879
|
] }, k)) }) : null,
|
|
4414
|
-
!def ? /* @__PURE__ */
|
|
4415
|
-
/* @__PURE__ */
|
|
4416
|
-
/* @__PURE__ */
|
|
4880
|
+
!def ? /* @__PURE__ */ jsxs22("div", { className: "bpe-empty", children: [
|
|
4881
|
+
/* @__PURE__ */ jsx23(Icon, { name: "alert-triangle" }),
|
|
4882
|
+
/* @__PURE__ */ jsxs22("div", { className: "bpe-empty__title", children: [
|
|
4417
4883
|
"Unknown block \u201C",
|
|
4418
4884
|
node.type,
|
|
4419
4885
|
"\u201D"
|
|
4420
4886
|
] }),
|
|
4421
4887
|
"This block is not registered in this editor. Its settings are preserved."
|
|
4422
4888
|
] }) : null,
|
|
4423
|
-
node.type === "row" ? /* @__PURE__ */
|
|
4424
|
-
/* @__PURE__ */
|
|
4425
|
-
/* @__PURE__ */
|
|
4889
|
+
node.type === "row" ? /* @__PURE__ */ jsx23(Section, { title: "Layout", children: /* @__PURE__ */ jsx23("div", { className: "bpe-prop-grid", children: /* @__PURE__ */ jsx23(Field, { label: "Columns", stacked: true, children: /* @__PURE__ */ jsx23("div", { className: "bpe-layouts__grid", style: { width: "100%" }, children: ROW_LAYOUTS2.map((l) => /* @__PURE__ */ jsxs22("button", { type: "button", className: "bpe-layout-item", "aria-pressed": JSON.stringify(l.layout) === JSON.stringify(node.props.layout), style: JSON.stringify(l.layout) === JSON.stringify(node.props.layout) ? { borderColor: "var(--bpe-accent)", background: "var(--bpe-accent-soft)" } : void 0, onClick: () => session.store.dispatch({ type: "setRowLayout", id: node.id, layout: l.layout }), children: [
|
|
4890
|
+
/* @__PURE__ */ jsx23("div", { className: "bpe-layout-item__cols", style: { gridTemplateColumns: l.layout.map((c) => `${c}fr`).join(" ") }, children: l.layout.map((_, i) => /* @__PURE__ */ jsx23("div", { className: "bpe-layout-item__col" }, i)) }),
|
|
4891
|
+
/* @__PURE__ */ jsx23("span", { className: "bpe-layout-item__name", children: l.label })
|
|
4426
4892
|
] }, l.id)) }) }) }) }) : null,
|
|
4427
|
-
node.type === "column" ? /* @__PURE__ */
|
|
4428
|
-
/* @__PURE__ */
|
|
4893
|
+
node.type === "column" ? /* @__PURE__ */ jsx23(Section, { title: "Column", children: /* @__PURE__ */ jsxs22("div", { className: "bpe-prop-grid", children: [
|
|
4894
|
+
/* @__PURE__ */ jsx23(Field, { label: "Width", hint: "Twelfths of the row. Change the row layout to resize columns.", children: /* @__PURE__ */ jsxs22(Badge, { children: [
|
|
4429
4895
|
String(node.props.span),
|
|
4430
4896
|
" / 12"
|
|
4431
4897
|
] }) }),
|
|
4432
|
-
/* @__PURE__ */
|
|
4898
|
+
/* @__PURE__ */ jsx23(Field, { label: "Row", children: /* @__PURE__ */ jsx23(Button, { size: "sm", onClick: () => actions.selectParent(), children: "Select row" }) })
|
|
4433
4899
|
] }) }) : null,
|
|
4434
4900
|
(def?.inspector ?? []).map((section, si) => {
|
|
4435
4901
|
if (section.when && !section.when(props)) return null;
|
|
4436
4902
|
const fields = section.fields.filter((f) => !f.when || f.when(props));
|
|
4437
4903
|
if (!fields.length) return null;
|
|
4438
|
-
return /* @__PURE__ */
|
|
4904
|
+
return /* @__PURE__ */ jsx23(Section, { title: section.title, defaultOpen: !section.collapsed, children: /* @__PURE__ */ jsx23("div", { className: "bpe-prop-grid", children: fields.map((f) => /* @__PURE__ */ jsx23(FieldRenderer, { field: f, value: getPath(props, f.prop), onChange: (v) => setProp(f.prop, v), ctx }, f.prop)) }) }, si);
|
|
4439
4905
|
}),
|
|
4440
|
-
def ? /* @__PURE__ */
|
|
4906
|
+
def ? /* @__PURE__ */ jsx23(
|
|
4441
4907
|
StyleGroups,
|
|
4442
4908
|
{
|
|
4443
4909
|
node,
|
|
@@ -4449,22 +4915,22 @@ function NodeInspector({ entry, actions }) {
|
|
|
4449
4915
|
setFlow: (patch) => session.store.dispatch({ type: "setFlow", id: node.id, patch }, { label: "Edit page flow" })
|
|
4450
4916
|
}
|
|
4451
4917
|
) : null,
|
|
4452
|
-
node.type !== "column" ? /* @__PURE__ */
|
|
4453
|
-
/* @__PURE__ */
|
|
4454
|
-
/* @__PURE__ */
|
|
4455
|
-
/* @__PURE__ */
|
|
4456
|
-
/* @__PURE__ */
|
|
4457
|
-
/* @__PURE__ */
|
|
4458
|
-
node.type !== "section" && node.type !== "column" ? /* @__PURE__ */
|
|
4918
|
+
node.type !== "column" ? /* @__PURE__ */ jsx23(Section, { title: "Visibility", defaultOpen: !!node.visibleWhen, icon: "eye", children: /* @__PURE__ */ jsx23("div", { className: "bpe-prop-grid", children: /* @__PURE__ */ jsx23(Field, { label: "Show when", stacked: true, hint: "Leave empty to always show this block.", children: /* @__PURE__ */ jsx23(ConditionBuilder, { value: node.visibleWhen, onChange: (v) => session.store.dispatch({ type: "setVisibleWhen", id: node.id, condition: v }, { label: "Edit visibility" }), dataSources: session.dataSources, scopeVars, allowExpression: features.expressions }) }) }) }) : null,
|
|
4919
|
+
/* @__PURE__ */ jsx23(Section, { title: "Advanced", defaultOpen: false, icon: "settings", children: /* @__PURE__ */ jsxs22("div", { className: "bpe-prop-grid", children: [
|
|
4920
|
+
/* @__PURE__ */ jsx23(Field, { label: "Name", hint: "Shown in the layers panel and breadcrumb.", children: /* @__PURE__ */ jsx23(TextInput, { value: node.name ?? "", onChange: (v) => session.store.dispatch({ type: "setName", id: node.id, name: v || void 0 }), lazy: true, placeholder: def?.name }) }),
|
|
4921
|
+
/* @__PURE__ */ jsx23(Field, { label: "Locked", children: /* @__PURE__ */ jsx23(Switch, { checked: !!node.locked, onChange: (v) => session.store.dispatch({ type: "setLocked", id: node.id, locked: v }) }) }),
|
|
4922
|
+
/* @__PURE__ */ jsx23(Field, { label: "ID", children: /* @__PURE__ */ jsx23("code", { className: "bpe-mono bpe-muted", style: { fontSize: 10 }, children: node.id }) }),
|
|
4923
|
+
/* @__PURE__ */ jsx23(Field, { label: "Type", children: /* @__PURE__ */ jsx23("code", { className: "bpe-mono bpe-muted", style: { fontSize: 10 }, children: node.type }) }),
|
|
4924
|
+
node.type !== "section" && node.type !== "column" ? /* @__PURE__ */ jsx23(Field, { label: "Wrap in", children: /* @__PURE__ */ jsx23(Segmented, { value: "", onChange: (v) => actions.wrapSelectionIn(v), options: [{ value: "card", label: "Card" }, { value: "keepTogether", label: "Keep" }, { value: "conditional", label: "If" }] }) }) : null
|
|
4459
4925
|
] }) })
|
|
4460
4926
|
] })
|
|
4461
4927
|
] });
|
|
4462
4928
|
}
|
|
4463
4929
|
|
|
4464
4930
|
// src/inspector/DocumentInspector.tsx
|
|
4465
|
-
import { useState as
|
|
4931
|
+
import { useState as useState18 } from "react";
|
|
4466
4932
|
import { PAGE_SIZES_MM, textStyleLabel as textStyleLabel3 } from "@broadpaper/core";
|
|
4467
|
-
import { Fragment as Fragment6, jsx as
|
|
4933
|
+
import { Fragment as Fragment6, jsx as jsx24, jsxs as jsxs23 } from "react/jsx-runtime";
|
|
4468
4934
|
var SIZE_OPTIONS = [...Object.keys(PAGE_SIZES_MM).map((k) => ({ value: k, label: `${k} (${PAGE_SIZES_MM[k].width} \xD7 ${PAGE_SIZES_MM[k].height} mm)` })), { value: "custom", label: "Custom" }];
|
|
4469
4935
|
function DocumentInspector() {
|
|
4470
4936
|
const session = useSession();
|
|
@@ -4474,67 +4940,67 @@ function DocumentInspector() {
|
|
|
4474
4940
|
const page = template.page;
|
|
4475
4941
|
const setPage = (patch, label = "Page settings") => session.store.dispatch({ type: "setPage", patch }, { label, mergeKey: "page" });
|
|
4476
4942
|
const sizeName = typeof page.size === "string" ? page.size : "custom";
|
|
4477
|
-
const [editingStyle, setEditingStyle] =
|
|
4478
|
-
return /* @__PURE__ */
|
|
4479
|
-
/* @__PURE__ */
|
|
4480
|
-
/* @__PURE__ */
|
|
4481
|
-
/* @__PURE__ */
|
|
4482
|
-
/* @__PURE__ */
|
|
4483
|
-
/* @__PURE__ */
|
|
4943
|
+
const [editingStyle, setEditingStyle] = useState18(null);
|
|
4944
|
+
return /* @__PURE__ */ jsxs23(Fragment6, { children: [
|
|
4945
|
+
/* @__PURE__ */ jsxs23("div", { className: "bpe-inspector__header", children: [
|
|
4946
|
+
/* @__PURE__ */ jsx24("span", { className: "bpe-inspector__icon", children: /* @__PURE__ */ jsx24(Icon, { name: "file-text" }) }),
|
|
4947
|
+
/* @__PURE__ */ jsxs23("div", { style: { minWidth: 0 }, children: [
|
|
4948
|
+
/* @__PURE__ */ jsx24("div", { className: "bpe-inspector__title", children: "Document" }),
|
|
4949
|
+
/* @__PURE__ */ jsx24("div", { className: "bpe-inspector__subtitle", children: "Page, styles and settings" })
|
|
4484
4950
|
] })
|
|
4485
4951
|
] }),
|
|
4486
|
-
/* @__PURE__ */
|
|
4487
|
-
features.pageSettings ? /* @__PURE__ */
|
|
4488
|
-
/* @__PURE__ */
|
|
4489
|
-
sizeName === "custom" && typeof page.size === "object" ? /* @__PURE__ */
|
|
4490
|
-
/* @__PURE__ */
|
|
4491
|
-
/* @__PURE__ */
|
|
4952
|
+
/* @__PURE__ */ jsxs23("div", { className: "bpe-inspector__body", children: [
|
|
4953
|
+
features.pageSettings ? /* @__PURE__ */ jsx24(Section, { title: "Page", icon: "layout", children: /* @__PURE__ */ jsxs23("div", { className: "bpe-prop-grid", children: [
|
|
4954
|
+
/* @__PURE__ */ jsx24(Field, { label: "Size", children: /* @__PURE__ */ jsx24(Select, { value: sizeName, onChange: (v) => setPage({ size: v === "custom" ? { width: 210, height: 297 } : v }), options: SIZE_OPTIONS }) }),
|
|
4955
|
+
sizeName === "custom" && typeof page.size === "object" ? /* @__PURE__ */ jsxs23(Fragment6, { children: [
|
|
4956
|
+
/* @__PURE__ */ jsx24(Field, { label: "Width", children: /* @__PURE__ */ jsx24(NumberInput, { value: page.size.width, onChange: (v) => setPage({ size: { width: v ?? 210, height: page.size.height } }), min: 50, max: 2e3, unit: "mm" }) }),
|
|
4957
|
+
/* @__PURE__ */ jsx24(Field, { label: "Height", children: /* @__PURE__ */ jsx24(NumberInput, { value: page.size.height, onChange: (v) => setPage({ size: { width: page.size.width, height: v ?? 297 } }), min: 50, max: 2e3, unit: "mm" }) })
|
|
4492
4958
|
] }) : null,
|
|
4493
|
-
/* @__PURE__ */
|
|
4494
|
-
/* @__PURE__ */
|
|
4495
|
-
/* @__PURE__ */
|
|
4496
|
-
/* @__PURE__ */
|
|
4497
|
-
/* @__PURE__ */
|
|
4959
|
+
/* @__PURE__ */ jsx24(Field, { label: "Orientation", children: /* @__PURE__ */ jsx24(Segmented, { value: page.orientation, onChange: (v) => setPage({ orientation: v }), options: [{ value: "portrait", label: "Portrait", icon: "rectangle-vertical" }, { value: "landscape", label: "Landscape", icon: "rectangle-horizontal" }] }) }),
|
|
4960
|
+
/* @__PURE__ */ jsx24(Field, { label: "Margins", stacked: true, children: /* @__PURE__ */ jsxs23("div", { style: { width: "100%" }, children: [
|
|
4961
|
+
/* @__PURE__ */ jsx24("div", { className: "bpe-spacing-grid", children: ["top", "right", "bottom", "left"].map((s) => /* @__PURE__ */ jsx24(NumberInput, { value: page.margins[s], onChange: (v) => setPage({ margins: { ...page.margins, [s]: v ?? 0 } }), min: 0, max: 80, "aria-label": `${s} margin` }, s)) }),
|
|
4962
|
+
/* @__PURE__ */ jsx24("div", { className: "bpe-spacing-grid", children: ["top", "right", "bottom", "left"].map((s) => /* @__PURE__ */ jsx24("div", { className: "bpe-spacing-grid__lbl", children: s }, s)) }),
|
|
4963
|
+
/* @__PURE__ */ jsx24("div", { className: "bpe-field__hint", children: "Millimetres" })
|
|
4498
4964
|
] }) }),
|
|
4499
|
-
/* @__PURE__ */
|
|
4965
|
+
/* @__PURE__ */ jsx24(Field, { label: "Background", children: /* @__PURE__ */ jsx24(ColorField, { value: page.background, onChange: (v) => setPage({ background: v }), theme, placeholder: "White" }) })
|
|
4500
4966
|
] }) }) : null,
|
|
4501
|
-
features.headerFooter ? /* @__PURE__ */
|
|
4502
|
-
/* @__PURE__ */
|
|
4503
|
-
/* @__PURE__ */
|
|
4504
|
-
/* @__PURE__ */
|
|
4505
|
-
/* @__PURE__ */
|
|
4506
|
-
/* @__PURE__ */
|
|
4967
|
+
features.headerFooter ? /* @__PURE__ */ jsx24(Section, { title: "Header & footer", icon: "panel-top", children: /* @__PURE__ */ jsxs23("div", { className: "bpe-prop-grid", children: [
|
|
4968
|
+
/* @__PURE__ */ jsx24(Field, { label: "Header height", children: /* @__PURE__ */ jsx24(NumberInput, { value: page.header.height, onChange: (v) => setPage({ header: { ...page.header, height: v ?? 0 } }), min: 0, max: 100, unit: "mm" }) }),
|
|
4969
|
+
/* @__PURE__ */ jsx24(Field, { label: "Footer height", children: /* @__PURE__ */ jsx24(NumberInput, { value: page.footer.height, onChange: (v) => setPage({ footer: { ...page.footer, height: v ?? 0 } }), min: 0, max: 100, unit: "mm" }) }),
|
|
4970
|
+
/* @__PURE__ */ jsx24(Field, { label: "First page header", hint: "Use a different header on page 1 (e.g. a cover).", children: /* @__PURE__ */ jsx24(Switch, { checked: page.header.differentFirstPage, onChange: (v) => session.store.dispatch({ type: "setRegionFirstPage", region: "header", enabled: v }) }) }),
|
|
4971
|
+
/* @__PURE__ */ jsx24(Field, { label: "First page footer", children: /* @__PURE__ */ jsx24(Switch, { checked: page.footer.differentFirstPage, onChange: (v) => session.store.dispatch({ type: "setRegionFirstPage", region: "footer", enabled: v }) }) }),
|
|
4972
|
+
/* @__PURE__ */ jsx24("div", { className: "bpe-field__hint", children: "Click inside the header or footer area on the canvas to edit its content. Use the \u201CPage number\u201D block for numbering." })
|
|
4507
4973
|
] }) }) : null,
|
|
4508
|
-
features.documentStyles ? /* @__PURE__ */
|
|
4509
|
-
Object.entries(template.styles.text).map(([id, s]) => /* @__PURE__ */
|
|
4510
|
-
/* @__PURE__ */
|
|
4511
|
-
/* @__PURE__ */
|
|
4974
|
+
features.documentStyles ? /* @__PURE__ */ jsx24(Section, { title: "Text styles", icon: "type", children: editingStyle ? /* @__PURE__ */ jsx24(TextStyleEditor, { id: editingStyle, style: template.styles.text[editingStyle] ?? {}, onBack: () => setEditingStyle(null), onChange: (patch) => session.store.dispatch({ type: "setTextStyle", id: editingStyle, patch }, { label: "Document styles", mergeKey: `textstyle:${editingStyle}` }) }) : /* @__PURE__ */ jsxs23("div", { children: [
|
|
4975
|
+
Object.entries(template.styles.text).map(([id, s]) => /* @__PURE__ */ jsxs23("button", { type: "button", className: "bpe-doc-styles__item", onClick: () => setEditingStyle(id), children: [
|
|
4976
|
+
/* @__PURE__ */ jsx24("span", { className: "bpe-doc-styles__sample", style: { fontWeight: s.fontWeight, fontSize: Math.min(16, Math.max(10, (s.fontSize ?? 11) * 0.7)), textTransform: s.textTransform }, children: textStyleLabel3(id) }),
|
|
4977
|
+
/* @__PURE__ */ jsxs23("span", { className: "bpe-doc-styles__id", children: [
|
|
4512
4978
|
id,
|
|
4513
4979
|
" \xB7 ",
|
|
4514
4980
|
s.fontSize ?? "\u2013",
|
|
4515
4981
|
"px"
|
|
4516
4982
|
] }),
|
|
4517
|
-
/* @__PURE__ */
|
|
4983
|
+
/* @__PURE__ */ jsx24(Icon, { name: "chevron-right", size: 12 })
|
|
4518
4984
|
] }, id)),
|
|
4519
|
-
/* @__PURE__ */
|
|
4985
|
+
/* @__PURE__ */ jsx24("div", { className: "bpe-field__hint", style: { padding: "4px 12px 8px" }, children: "Changing a style updates every block that uses it." })
|
|
4520
4986
|
] }) }) : null,
|
|
4521
|
-
features.documentStyles ? /* @__PURE__ */
|
|
4522
|
-
/* @__PURE__ */
|
|
4523
|
-
/* @__PURE__ */
|
|
4524
|
-
/* @__PURE__ */
|
|
4525
|
-
/* @__PURE__ */
|
|
4526
|
-
/* @__PURE__ */
|
|
4527
|
-
/* @__PURE__ */
|
|
4528
|
-
/* @__PURE__ */
|
|
4529
|
-
/* @__PURE__ */
|
|
4530
|
-
/* @__PURE__ */
|
|
4531
|
-
/* @__PURE__ */
|
|
4987
|
+
features.documentStyles ? /* @__PURE__ */ jsx24(Section, { title: "Tables & cards", icon: "table", defaultOpen: false, children: /* @__PURE__ */ jsxs23("div", { className: "bpe-prop-grid", children: [
|
|
4988
|
+
/* @__PURE__ */ jsx24(Field, { label: "Table lines", children: /* @__PURE__ */ jsx24(Select, { value: template.styles.table.borders ?? "horizontal", onChange: (v) => session.store.dispatch({ type: "setDocumentStyles", patch: { table: { borders: v } } }), options: [{ value: "horizontal", label: "Horizontal" }, { value: "all", label: "Grid" }, { value: "outer", label: "Outer" }, { value: "none", label: "None" }] }) }),
|
|
4989
|
+
/* @__PURE__ */ jsx24(Field, { label: "Header fill", children: /* @__PURE__ */ jsx24(ColorField, { value: template.styles.table.headerBackground, onChange: (v) => session.store.dispatch({ type: "setDocumentStyles", patch: { table: { headerBackground: v } } }), theme }) }),
|
|
4990
|
+
/* @__PURE__ */ jsx24(Field, { label: "Zebra fill", children: /* @__PURE__ */ jsx24(ColorField, { value: template.styles.table.zebraBackground, onChange: (v) => session.store.dispatch({ type: "setDocumentStyles", patch: { table: { zebraBackground: v } } }), theme }) }),
|
|
4991
|
+
/* @__PURE__ */ jsx24(Field, { label: "Table font", children: /* @__PURE__ */ jsx24(NumberInput, { value: template.styles.table.fontSize ?? null, onChange: (v) => session.store.dispatch({ type: "setDocumentStyles", patch: { table: { fontSize: v ?? void 0 } } }), min: 6, max: 20, unit: "px" }) }),
|
|
4992
|
+
/* @__PURE__ */ jsx24(Field, { label: "Cell padding", children: /* @__PURE__ */ jsx24(NumberInput, { value: template.styles.table.cellPaddingY ?? null, onChange: (v) => session.store.dispatch({ type: "setDocumentStyles", patch: { table: { cellPaddingY: v ?? void 0 } } }), min: 0, max: 30, unit: "px" }) }),
|
|
4993
|
+
/* @__PURE__ */ jsx24(Field, { label: "Card radius", children: /* @__PURE__ */ jsx24(NumberInput, { value: template.styles.card.borderRadius ?? null, onChange: (v) => session.store.dispatch({ type: "setDocumentStyles", patch: { card: { borderRadius: v ?? void 0 } } }), min: 0, max: 40, unit: "px" }) }),
|
|
4994
|
+
/* @__PURE__ */ jsx24(Field, { label: "Card padding", children: /* @__PURE__ */ jsx24(NumberInput, { value: template.styles.card.padding ?? null, onChange: (v) => session.store.dispatch({ type: "setDocumentStyles", patch: { card: { padding: v ?? void 0 } } }), min: 0, max: 60, unit: "px" }) }),
|
|
4995
|
+
/* @__PURE__ */ jsx24(Field, { label: "Card shadow", children: /* @__PURE__ */ jsx24(Switch, { checked: !!template.styles.card.shadow, onChange: (v) => session.store.dispatch({ type: "setDocumentStyles", patch: { card: { shadow: v } } }) }) }),
|
|
4996
|
+
/* @__PURE__ */ jsx24(Field, { label: "Block gap", children: /* @__PURE__ */ jsx24(NumberInput, { value: template.styles.spacing.blockGap, onChange: (v) => session.store.dispatch({ type: "setDocumentStyles", patch: { spacing: { ...template.styles.spacing, blockGap: v ?? 8 } } }), min: 0, max: 60, unit: "px" }) }),
|
|
4997
|
+
/* @__PURE__ */ jsx24(Field, { label: "Column gap", children: /* @__PURE__ */ jsx24(NumberInput, { value: template.styles.spacing.columnGap, onChange: (v) => session.store.dispatch({ type: "setDocumentStyles", patch: { spacing: { ...template.styles.spacing, columnGap: v ?? 16 } } }), min: 0, max: 80, unit: "px" }) })
|
|
4532
4998
|
] }) }) : null,
|
|
4533
|
-
/* @__PURE__ */
|
|
4534
|
-
/* @__PURE__ */
|
|
4535
|
-
/* @__PURE__ */
|
|
4536
|
-
/* @__PURE__ */
|
|
4537
|
-
/* @__PURE__ */
|
|
4999
|
+
/* @__PURE__ */ jsx24(Section, { title: "Settings", icon: "settings", defaultOpen: false, children: /* @__PURE__ */ jsxs23("div", { className: "bpe-prop-grid", children: [
|
|
5000
|
+
/* @__PURE__ */ jsx24(Field, { label: "Locale", hint: "Number and date formatting, e.g. en-GB, en-US, de-DE.", children: /* @__PURE__ */ jsx24(TextInput, { value: template.settings?.locale ?? "", onChange: (v) => session.store.dispatch({ type: "setSettings", patch: { locale: v || void 0 } }), lazy: true, placeholder: "en-GB" }) }),
|
|
5001
|
+
/* @__PURE__ */ jsx24(Field, { label: "Currency", children: /* @__PURE__ */ jsx24(TextInput, { value: template.settings?.currency ?? "", onChange: (v) => session.store.dispatch({ type: "setSettings", patch: { currency: v.toUpperCase() || void 0 } }), lazy: true, placeholder: "GBP" }) }),
|
|
5002
|
+
/* @__PURE__ */ jsx24(Field, { label: "Time zone", children: /* @__PURE__ */ jsx24(TextInput, { value: template.settings?.timeZone ?? "", onChange: (v) => session.store.dispatch({ type: "setSettings", patch: { timeZone: v || void 0 } }), lazy: true, placeholder: "Europe/London" }) }),
|
|
5003
|
+
/* @__PURE__ */ jsx24(Field, { label: "Template ID", children: /* @__PURE__ */ jsx24("code", { className: "bpe-mono bpe-muted", style: { fontSize: 10 }, children: template.id }) })
|
|
4538
5004
|
] }) })
|
|
4539
5005
|
] })
|
|
4540
5006
|
] });
|
|
@@ -4542,69 +5008,69 @@ function DocumentInspector() {
|
|
|
4542
5008
|
function TextStyleEditor({ id, style, onChange, onBack }) {
|
|
4543
5009
|
const session = useSession();
|
|
4544
5010
|
const theme = session.activeTheme;
|
|
4545
|
-
return /* @__PURE__ */
|
|
4546
|
-
/* @__PURE__ */
|
|
4547
|
-
/* @__PURE__ */
|
|
4548
|
-
/* @__PURE__ */
|
|
5011
|
+
return /* @__PURE__ */ jsxs23("div", { className: "bpe-prop-grid", children: [
|
|
5012
|
+
/* @__PURE__ */ jsxs23("div", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [
|
|
5013
|
+
/* @__PURE__ */ jsx24(Button, { size: "sm", variant: "ghost", icon: "chevron-right", style: { transform: "rotate(180deg)" }, onClick: onBack, tooltip: "Back" }),
|
|
5014
|
+
/* @__PURE__ */ jsx24("strong", { children: id })
|
|
4549
5015
|
] }),
|
|
4550
|
-
/* @__PURE__ */
|
|
4551
|
-
/* @__PURE__ */
|
|
4552
|
-
/* @__PURE__ */
|
|
4553
|
-
/* @__PURE__ */
|
|
4554
|
-
/* @__PURE__ */
|
|
4555
|
-
/* @__PURE__ */
|
|
4556
|
-
/* @__PURE__ */
|
|
4557
|
-
/* @__PURE__ */
|
|
4558
|
-
/* @__PURE__ */
|
|
4559
|
-
/* @__PURE__ */
|
|
5016
|
+
/* @__PURE__ */ jsx24(Field, { label: "Font", children: /* @__PURE__ */ jsx24(Select, { value: style.fontFamily ?? "", onChange: (v) => onChange({ fontFamily: v || void 0 }), placeholder: "Theme body font", options: fontOptions(theme, style.fontFamily) }) }),
|
|
5017
|
+
/* @__PURE__ */ jsx24(Field, { label: "Size", children: /* @__PURE__ */ jsx24(NumberInput, { value: style.fontSize ?? null, onChange: (v) => onChange({ fontSize: v ?? void 0 }), min: 4, max: 120, unit: "px" }) }),
|
|
5018
|
+
/* @__PURE__ */ jsx24(Field, { label: "Weight", children: /* @__PURE__ */ jsx24(Select, { value: style.fontWeight === void 0 ? "" : String(style.fontWeight), onChange: (v) => onChange({ fontWeight: v ? Number(v) : void 0 }), placeholder: "Regular", options: [300, 400, 500, 600, 700, 800].map((w) => ({ value: String(w), label: String(w) })) }) }),
|
|
5019
|
+
/* @__PURE__ */ jsx24(Field, { label: "Line height", children: /* @__PURE__ */ jsx24(NumberInput, { value: style.lineHeight ?? null, onChange: (v) => onChange({ lineHeight: v ?? void 0 }), min: 0.8, max: 3, step: 0.05 }) }),
|
|
5020
|
+
/* @__PURE__ */ jsx24(Field, { label: "Letter spacing", children: /* @__PURE__ */ jsx24(NumberInput, { value: style.letterSpacing ?? null, onChange: (v) => onChange({ letterSpacing: v ?? void 0 }), min: -0.1, max: 0.5, step: 0.01, unit: "em" }) }),
|
|
5021
|
+
/* @__PURE__ */ jsx24(Field, { label: "Case", children: /* @__PURE__ */ jsx24(Segmented, { value: style.textTransform ?? "none", onChange: (v) => onChange({ textTransform: v === "none" ? void 0 : v }), options: [{ value: "none", label: "Aa" }, { value: "uppercase", label: "AA" }, { value: "capitalize", label: "Aa Bb" }] }) }),
|
|
5022
|
+
/* @__PURE__ */ jsx24(Field, { label: "Colour", children: /* @__PURE__ */ jsx24(ColorField, { value: style.color, onChange: (v) => onChange({ color: v }), theme, allowTransparent: false, placeholder: "Theme text" }) }),
|
|
5023
|
+
/* @__PURE__ */ jsx24(Field, { label: "Space above", children: /* @__PURE__ */ jsx24(NumberInput, { value: style.marginTop ?? null, onChange: (v) => onChange({ marginTop: v ?? void 0 }), min: 0, max: 100, unit: "px" }) }),
|
|
5024
|
+
/* @__PURE__ */ jsx24(Field, { label: "Space below", children: /* @__PURE__ */ jsx24(NumberInput, { value: style.marginBottom ?? null, onChange: (v) => onChange({ marginBottom: v ?? void 0 }), min: 0, max: 100, unit: "px" }) }),
|
|
5025
|
+
/* @__PURE__ */ jsx24(Field, { label: "Keep with next", children: /* @__PURE__ */ jsx24(Switch, { checked: !!style.keepWithNext, onChange: (v) => onChange({ keepWithNext: v || void 0 }) }) })
|
|
4560
5026
|
] });
|
|
4561
5027
|
}
|
|
4562
5028
|
|
|
4563
5029
|
// src/inspector/Inspector.tsx
|
|
4564
|
-
import { jsx as
|
|
5030
|
+
import { jsx as jsx25, jsxs as jsxs24 } from "react/jsx-runtime";
|
|
4565
5031
|
function Inspector({ actions }) {
|
|
4566
5032
|
const session = useSession();
|
|
4567
5033
|
const selection = useEditorState((s) => s.selection);
|
|
4568
5034
|
const mode = useEditorState((s) => s.mode);
|
|
4569
5035
|
const entry = useNode(selection[0] ?? null);
|
|
4570
5036
|
if (mode === "preview") {
|
|
4571
|
-
return /* @__PURE__ */
|
|
4572
|
-
/* @__PURE__ */
|
|
4573
|
-
/* @__PURE__ */
|
|
5037
|
+
return /* @__PURE__ */ jsx25("aside", { className: "bpe-inspector", "aria-label": "Properties", children: /* @__PURE__ */ jsxs24("div", { className: "bpe-empty", style: { marginTop: 40 }, children: [
|
|
5038
|
+
/* @__PURE__ */ jsx25(Icon, { name: "eye" }),
|
|
5039
|
+
/* @__PURE__ */ jsx25("div", { className: "bpe-empty__title", children: "Preview mode" }),
|
|
4574
5040
|
"Properties are hidden while previewing. Switch back to Design to edit.",
|
|
4575
|
-
/* @__PURE__ */
|
|
5041
|
+
/* @__PURE__ */ jsx25(Button, { size: "sm", onClick: () => session.setMode("design"), style: { marginTop: 8 }, children: "Back to design" })
|
|
4576
5042
|
] }) });
|
|
4577
5043
|
}
|
|
4578
5044
|
if (selection.length > 1) {
|
|
4579
|
-
return /* @__PURE__ */
|
|
4580
|
-
/* @__PURE__ */
|
|
4581
|
-
/* @__PURE__ */
|
|
4582
|
-
/* @__PURE__ */
|
|
4583
|
-
/* @__PURE__ */
|
|
5045
|
+
return /* @__PURE__ */ jsxs24("aside", { className: "bpe-inspector", "aria-label": "Properties", children: [
|
|
5046
|
+
/* @__PURE__ */ jsxs24("div", { className: "bpe-inspector__header", children: [
|
|
5047
|
+
/* @__PURE__ */ jsx25("span", { className: "bpe-inspector__icon", children: /* @__PURE__ */ jsx25(Icon, { name: "square-stack" }) }),
|
|
5048
|
+
/* @__PURE__ */ jsxs24("div", { children: [
|
|
5049
|
+
/* @__PURE__ */ jsxs24("div", { className: "bpe-inspector__title", children: [
|
|
4584
5050
|
selection.length,
|
|
4585
5051
|
" blocks selected"
|
|
4586
5052
|
] }),
|
|
4587
|
-
/* @__PURE__ */
|
|
5053
|
+
/* @__PURE__ */ jsx25("div", { className: "bpe-inspector__subtitle", children: "Actions apply to all" })
|
|
4588
5054
|
] })
|
|
4589
5055
|
] }),
|
|
4590
|
-
/* @__PURE__ */
|
|
4591
|
-
/* @__PURE__ */
|
|
4592
|
-
/* @__PURE__ */
|
|
4593
|
-
/* @__PURE__ */
|
|
4594
|
-
/* @__PURE__ */
|
|
5056
|
+
/* @__PURE__ */ jsxs24("div", { className: "bpe-prop-grid", children: [
|
|
5057
|
+
/* @__PURE__ */ jsx25(Button, { icon: "copy", onClick: () => actions.duplicateSelection(), children: "Duplicate all" }),
|
|
5058
|
+
/* @__PURE__ */ jsx25(Button, { icon: "panel-top", onClick: () => actions.wrapSelectionIn("card"), children: "Wrap in card" }),
|
|
5059
|
+
/* @__PURE__ */ jsx25(Button, { icon: "link", onClick: () => actions.wrapSelectionIn("keepTogether"), children: "Keep together" }),
|
|
5060
|
+
/* @__PURE__ */ jsx25(Button, { icon: "trash", variant: "danger", onClick: () => actions.deleteSelection(), children: "Delete all" })
|
|
4595
5061
|
] })
|
|
4596
5062
|
] });
|
|
4597
5063
|
}
|
|
4598
|
-
return /* @__PURE__ */
|
|
5064
|
+
return /* @__PURE__ */ jsx25("aside", { className: "bpe-inspector", "aria-label": "Properties", children: entry ? /* @__PURE__ */ jsx25(NodeInspector, { entry, actions }, entry.node.id) : /* @__PURE__ */ jsx25(DocumentInspector, {}) });
|
|
4599
5065
|
}
|
|
4600
5066
|
|
|
4601
5067
|
// src/ui/PanelResizer.tsx
|
|
4602
|
-
import { useRef as
|
|
4603
|
-
import { jsx as
|
|
5068
|
+
import { useRef as useRef9 } from "react";
|
|
5069
|
+
import { jsx as jsx26 } from "react/jsx-runtime";
|
|
4604
5070
|
function PanelResizer({ side, width, min, max, onChange, label }) {
|
|
4605
|
-
const start =
|
|
5071
|
+
const start = useRef9(null);
|
|
4606
5072
|
const clamp = (w) => Math.round(Math.min(max, Math.max(min, w)));
|
|
4607
|
-
return /* @__PURE__ */
|
|
5073
|
+
return /* @__PURE__ */ jsx26(
|
|
4608
5074
|
"div",
|
|
4609
5075
|
{
|
|
4610
5076
|
className: `bpe-resizer bpe-resizer--${side}`,
|
|
@@ -4642,7 +5108,7 @@ function PanelResizer({ side, width, min, max, onChange, label }) {
|
|
|
4642
5108
|
|
|
4643
5109
|
// src/ui/Boundary.tsx
|
|
4644
5110
|
import { Component } from "react";
|
|
4645
|
-
import { jsx as
|
|
5111
|
+
import { jsx as jsx27, jsxs as jsxs25 } from "react/jsx-runtime";
|
|
4646
5112
|
var Boundary = class extends Component {
|
|
4647
5113
|
state = { error: null };
|
|
4648
5114
|
static getDerivedStateFromError(error) {
|
|
@@ -4654,48 +5120,48 @@ var Boundary = class extends Component {
|
|
|
4654
5120
|
render() {
|
|
4655
5121
|
const { error } = this.state;
|
|
4656
5122
|
if (!error) return this.props.children;
|
|
4657
|
-
return /* @__PURE__ */
|
|
4658
|
-
/* @__PURE__ */
|
|
4659
|
-
/* @__PURE__ */
|
|
4660
|
-
/* @__PURE__ */
|
|
5123
|
+
return /* @__PURE__ */ jsxs25("div", { className: "bpe-boundary", role: "alert", style: this.props.slot ? { gridArea: this.props.slot } : void 0, children: [
|
|
5124
|
+
/* @__PURE__ */ jsx27(Icon, { name: "alert-triangle", size: 16 }),
|
|
5125
|
+
/* @__PURE__ */ jsxs25("div", { className: "bpe-boundary__body", children: [
|
|
5126
|
+
/* @__PURE__ */ jsxs25("div", { className: "bpe-boundary__title", children: [
|
|
4661
5127
|
this.props.area,
|
|
4662
5128
|
" stopped"
|
|
4663
5129
|
] }),
|
|
4664
|
-
/* @__PURE__ */
|
|
5130
|
+
/* @__PURE__ */ jsxs25("div", { className: "bpe-boundary__detail", children: [
|
|
4665
5131
|
error.message || "Something went wrong.",
|
|
4666
5132
|
" The rest of the designer, and your document, are unaffected."
|
|
4667
5133
|
] })
|
|
4668
5134
|
] }),
|
|
4669
|
-
/* @__PURE__ */
|
|
5135
|
+
/* @__PURE__ */ jsx27(Button, { size: "sm", icon: "rotate-ccw", onClick: () => this.setState({ error: null }), children: "Try again" })
|
|
4670
5136
|
] });
|
|
4671
5137
|
}
|
|
4672
5138
|
};
|
|
4673
5139
|
|
|
4674
5140
|
// src/ReportDesigner.tsx
|
|
4675
|
-
import { Fragment as Fragment7, jsx as
|
|
5141
|
+
import { Fragment as Fragment7, jsx as jsx28, jsxs as jsxs26 } from "react/jsx-runtime";
|
|
4676
5142
|
var NARROW_BELOW = 820;
|
|
4677
5143
|
function ReportDesignerRoot({ session }) {
|
|
4678
|
-
const actions =
|
|
4679
|
-
return /* @__PURE__ */
|
|
5144
|
+
const actions = useMemo9(() => createActions(session), [session]);
|
|
5145
|
+
return /* @__PURE__ */ jsx28(SessionContext.Provider, { value: session, children: /* @__PURE__ */ jsx28(Shell, { actions }) });
|
|
4680
5146
|
}
|
|
4681
5147
|
function Shell({ actions }) {
|
|
4682
5148
|
const options = useOptions();
|
|
4683
|
-
const [portal, setPortal] =
|
|
5149
|
+
const [portal, setPortal] = useState19(null);
|
|
4684
5150
|
const session = useSession();
|
|
4685
5151
|
const leftOpen = useUi((u) => u.leftOpen);
|
|
4686
5152
|
const rightOpen = useUi((u) => u.rightOpen);
|
|
4687
5153
|
const leftWidth = useUi((u) => u.leftWidth);
|
|
4688
5154
|
const rightWidth = useUi((u) => u.rightWidth);
|
|
4689
5155
|
const narrow = useUi((u) => u.narrow);
|
|
4690
|
-
const rootRef =
|
|
5156
|
+
const rootRef = useRef10(null);
|
|
4691
5157
|
const width = useElementWidth(rootRef);
|
|
4692
5158
|
const isNarrow = width > 0 && width < NARROW_BELOW;
|
|
4693
|
-
|
|
5159
|
+
useEffect8(() => {
|
|
4694
5160
|
if (isNarrow === narrow) return;
|
|
4695
5161
|
session.ui.patch(isNarrow ? { narrow: true, leftOpen: false, rightOpen: false } : { narrow: false });
|
|
4696
5162
|
}, [isNarrow, narrow, session]);
|
|
4697
5163
|
const appearance = options.appearance === "system" ? typeof matchMedia === "function" && matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light" : options.appearance ?? "light";
|
|
4698
|
-
|
|
5164
|
+
useEffect8(() => {
|
|
4699
5165
|
const onKey = (e) => {
|
|
4700
5166
|
const mod = e.metaKey || e.ctrlKey;
|
|
4701
5167
|
const target = e.target;
|
|
@@ -4725,18 +5191,18 @@ function Shell({ actions }) {
|
|
|
4725
5191
|
options.className
|
|
4726
5192
|
].filter(Boolean).join(" ");
|
|
4727
5193
|
const scrim = narrow && (leftOpen || rightOpen);
|
|
4728
|
-
return /* @__PURE__ */
|
|
4729
|
-
/* @__PURE__ */
|
|
4730
|
-
leftOpen ? /* @__PURE__ */
|
|
4731
|
-
/* @__PURE__ */
|
|
4732
|
-
!narrow ? /* @__PURE__ */
|
|
4733
|
-
] }) : /* @__PURE__ */
|
|
4734
|
-
/* @__PURE__ */
|
|
4735
|
-
rightOpen ? /* @__PURE__ */
|
|
4736
|
-
!narrow ? /* @__PURE__ */
|
|
4737
|
-
/* @__PURE__ */
|
|
4738
|
-
] }) : /* @__PURE__ */
|
|
4739
|
-
scrim ? /* @__PURE__ */
|
|
5194
|
+
return /* @__PURE__ */ jsx28(PortalContext.Provider, { value: portal, children: /* @__PURE__ */ jsxs26("div", { ref: rootRef, className: cls, "data-appearance": appearance, style: { "--bpe-panel-w": `${leftWidth}px`, "--bpe-inspector-w": `${rightWidth}px` }, children: [
|
|
5195
|
+
/* @__PURE__ */ jsx28(Boundary, { area: "The toolbar", slot: "toolbar", children: /* @__PURE__ */ jsx28(Toolbar, { actions }) }),
|
|
5196
|
+
leftOpen ? /* @__PURE__ */ jsxs26(Fragment7, { children: [
|
|
5197
|
+
/* @__PURE__ */ jsx28(Boundary, { area: "The insert panel", slot: "left", children: /* @__PURE__ */ jsx28(LeftPanel, { actions }) }),
|
|
5198
|
+
!narrow ? /* @__PURE__ */ jsx28(PanelResizer, { side: "left", width: leftWidth, min: 200, max: 420, label: "Resize the insert panel", onChange: (leftWidth2) => session.ui.patch({ leftWidth: leftWidth2 }) }) : null
|
|
5199
|
+
] }) : /* @__PURE__ */ jsx28("div", {}),
|
|
5200
|
+
/* @__PURE__ */ jsx28(Boundary, { area: "The canvas", slot: "canvas", children: /* @__PURE__ */ jsx28(Canvas, { actions }) }),
|
|
5201
|
+
rightOpen ? /* @__PURE__ */ jsxs26(Fragment7, { children: [
|
|
5202
|
+
!narrow ? /* @__PURE__ */ jsx28(PanelResizer, { side: "right", width: rightWidth, min: 260, max: 560, label: "Resize the properties panel", onChange: (rightWidth2) => session.ui.patch({ rightWidth: rightWidth2 }) }) : null,
|
|
5203
|
+
/* @__PURE__ */ jsx28(Boundary, { area: "The properties panel", slot: "right", children: /* @__PURE__ */ jsx28(Inspector, { actions }) })
|
|
5204
|
+
] }) : /* @__PURE__ */ jsx28("div", {}),
|
|
5205
|
+
scrim ? /* @__PURE__ */ jsx28(
|
|
4740
5206
|
"button",
|
|
4741
5207
|
{
|
|
4742
5208
|
type: "button",
|
|
@@ -4745,7 +5211,7 @@ function Shell({ actions }) {
|
|
|
4745
5211
|
onClick: () => session.ui.patch({ leftOpen: false, rightOpen: false })
|
|
4746
5212
|
}
|
|
4747
5213
|
) : null,
|
|
4748
|
-
/* @__PURE__ */
|
|
5214
|
+
/* @__PURE__ */ jsx28("div", { className: "bpe-portal bpe-scope", "data-appearance": appearance, ref: setPortal })
|
|
4749
5215
|
] }) });
|
|
4750
5216
|
}
|
|
4751
5217
|
|
|
@@ -4940,7 +5406,11 @@ var EDITOR_CSS = `/* BroadPaper editor design tokens.
|
|
|
4940
5406
|
.bpe-input--invalid { border-color: var(--bpe-danger) !important; }
|
|
4941
5407
|
.bpe-textarea { height: auto; min-height: 56px; padding: 6px 8px; resize: vertical; line-height: 1.45; }
|
|
4942
5408
|
.bpe-select { appearance: none; padding-right: 24px; background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'><path d='M1 1l4 4 4-4' fill='none' stroke='%236b7280' stroke-width='1.5' stroke-linecap='round'/></svg>"); background-repeat: no-repeat; background-position: right 8px center; }
|
|
4943
|
-
|
|
5409
|
+
/* \`width: 100%\`, because a flex container sizes to its content and this one holds
|
|
5410
|
+
an input. Without it every field with a unit or a suffix \u2014 a width in \`%\`, a
|
|
5411
|
+
size in \`px\` \u2014 shrank to about twenty characters and left the rest of the row
|
|
5412
|
+
blank, while the plain inputs beside it filled. */
|
|
5413
|
+
.bpe-input-group { position: relative; display: flex; align-items: center; width: 100%; }
|
|
4944
5414
|
.bpe-input-group .bpe-input { padding-right: 28px; }
|
|
4945
5415
|
.bpe-input-group__suffix { position: absolute; right: 8px; font-size: var(--bpe-text-xs); color: var(--bpe-fg-faint); pointer-events: none; }
|
|
4946
5416
|
.bpe-input-group__btn { position: absolute; right: 2px; top: 2px; }
|
|
@@ -5288,14 +5758,49 @@ var EDITOR_CSS = `/* BroadPaper editor design tokens.
|
|
|
5288
5758
|
.bpe-binding__btn { display: flex; align-items: center; gap: 6px; flex: 1; min-width: 0; height: var(--bpe-control-h); padding: 0 8px; border: 1px solid var(--bpe-border); border-radius: var(--bpe-radius); background: var(--bpe-bg); text-align: left; font-size: var(--bpe-text-sm); }
|
|
5289
5759
|
.bpe-binding__btn:hover { border-color: var(--bpe-border-strong); }
|
|
5290
5760
|
.bpe-binding__btn--empty { color: var(--bpe-fg-faint); }
|
|
5761
|
+
/* A value the document works out. Shown, named after the fields it reads, and not
|
|
5762
|
+
editable where there is no expression editor to edit it with. */
|
|
5763
|
+
.bpe-binding__btn--locked { background: var(--bpe-bg-subtle); color: var(--bpe-fg-muted); cursor: default; }
|
|
5764
|
+
.bpe-binding__btn--locked:hover { border-color: var(--bpe-border); }
|
|
5291
5765
|
.bpe-binding__btn svg { width: 13px; height: 13px; flex-shrink: 0; color: var(--bpe-fg-muted); }
|
|
5292
5766
|
.bpe-expr { font-family: var(--bpe-font-mono); font-size: 11px; }
|
|
5293
5767
|
.bpe-picker { width: 300px; max-height: 380px; display: flex; flex-direction: column; padding: 0; overflow: hidden; }
|
|
5768
|
+
/* Wider when it is holding the calculation builder, which is three controls on a
|
|
5769
|
+
row plus a field name. At 300px every one of them truncated \u2014 "a total",
|
|
5770
|
+
"Total of", "In\u2026" \u2014 so the panel read as an interface that had been broken
|
|
5771
|
+
rather than one that was cramped. The field list on the other tab is a single
|
|
5772
|
+
column and does not need it, but one width for the panel is better than a
|
|
5773
|
+
popover that changes size when you change tab. */
|
|
5774
|
+
.bpe-picker--wide { width: 420px; max-height: 460px; }
|
|
5294
5775
|
.bpe-picker__search { padding: 8px; border-bottom: 1px solid var(--bpe-border); }
|
|
5295
5776
|
.bpe-picker__list { flex: 1; overflow-y: auto; padding: 4px; }
|
|
5296
5777
|
.bpe-picker__foot { display: flex; gap: 6px; align-items: center; padding: 8px; border-top: 1px solid var(--bpe-border); }
|
|
5297
5778
|
.bpe-picker__foot .bpe-input { flex: 1; }
|
|
5298
5779
|
.bpe-picker__group { padding: 6px 6px 2px; font-size: var(--bpe-text-xs); font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; color: var(--bpe-fg-faint); }
|
|
5780
|
+
/* The value panel's two answers: a field, or a value worked out. Shorter than the
|
|
5781
|
+
inspector's own tabs because these carry a word rather than a word and an icon. */
|
|
5782
|
+
.bpe-picker__tabs { display: flex; gap: 2px; padding: 6px 6px 0; border-bottom: 1px solid var(--bpe-border); }
|
|
5783
|
+
.bpe-picker__tabs .bpe-tab { height: 28px; }
|
|
5784
|
+
|
|
5785
|
+
/* The calculation builder. A column of rows, each reading as part of a sentence,
|
|
5786
|
+
and every control allowed to shrink \u2014 the properties panel is resizable, so
|
|
5787
|
+
anything in it can be squeezed and \`min-width: 0\` is what keeps the remove
|
|
5788
|
+
button inside the panel rather than out through its \`overflow: hidden\`. */
|
|
5789
|
+
.bpe-calc { display: flex; flex-direction: column; gap: 10px; padding: 10px; min-width: 0; }
|
|
5790
|
+
/* A step is a labelled block, not a line of controls.
|
|
5791
|
+
Three selects and a field name across one row is what made this unreadable:
|
|
5792
|
+
every control shrank to its truncation point and the sentence the builder is
|
|
5793
|
+
supposed to read as was unrecoverable from the abbreviations. */
|
|
5794
|
+
.bpe-calc__step { display: flex; flex-direction: column; gap: 4px; min-width: 0; padding: 8px; border: 1px solid var(--bpe-border); border-radius: var(--bpe-radius); background: var(--bpe-bg-subtle); }
|
|
5795
|
+
.bpe-calc__row { display: flex; align-items: center; gap: 6px; min-width: 0; }
|
|
5796
|
+
.bpe-grow { flex: 1 1 auto; }
|
|
5797
|
+
.bpe-calc__lead { flex: 0 0 auto; font-size: var(--bpe-text-xs); font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em; color: var(--bpe-fg-faint); }
|
|
5798
|
+
.bpe-calc__row .bpe-select { flex: 1 1 auto; min-width: 0; }
|
|
5799
|
+
.bpe-calc__term { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
|
|
5800
|
+
.bpe-calc__term .bpe-binding { width: 100%; }
|
|
5801
|
+
.bpe-calc__term .bpe-binding__btn { flex: 1 1 auto; min-width: 0; }
|
|
5802
|
+
.bpe-calc__term .bpe-input { width: 100%; }
|
|
5803
|
+
.bpe-calc__reads { margin: 2px 0 0; padding-top: 8px; border-top: 1px solid var(--bpe-border); font-size: var(--bpe-text-xs); color: var(--bpe-fg-muted); line-height: 1.5; }
|
|
5299
5804
|
.bpe-cond { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
|
|
5300
5805
|
.bpe-cond__rule { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 4px; min-width: 0; padding: 6px; border: 1px solid var(--bpe-border); border-radius: var(--bpe-radius); background: var(--bpe-bg-subtle); }
|
|
5301
5806
|
.bpe-cond__row { display: grid; grid-template-columns: minmax(0, 1fr); gap: 4px; min-width: 0; }
|
|
@@ -5307,6 +5812,17 @@ var EDITOR_CSS = `/* BroadPaper editor design tokens.
|
|
|
5307
5812
|
.bpe-list-editor__head .bpe-truncate { flex: 1 1 0; min-width: 0; }
|
|
5308
5813
|
.bpe-list-editor__item { min-width: 0; }
|
|
5309
5814
|
.bpe-list-editor__body { padding: 6px 8px 8px; border-top: 1px solid var(--bpe-border); display: flex; flex-direction: column; gap: 6px; }
|
|
5815
|
+
/*
|
|
5816
|
+
* Inside a list item, every field is stacked.
|
|
5817
|
+
*
|
|
5818
|
+
* The two-column field \u2014 a 96px label and the control beside it \u2014 is right in the
|
|
5819
|
+
* panel and wrong one level in: the item is already indented and bordered, so the
|
|
5820
|
+
* label column eats a third of what is left and the control ends in the middle of
|
|
5821
|
+
* the row with the rest blank. Stacked gives the control the whole width, and it
|
|
5822
|
+
* matches the fields in there that were already stacked because they needed it.
|
|
5823
|
+
*/
|
|
5824
|
+
.bpe-list-editor__body .bpe-field { grid-template-columns: 1fr; gap: 4px; }
|
|
5825
|
+
.bpe-list-editor__body .bpe-field__label { white-space: normal; overflow: visible; }
|
|
5310
5826
|
.bpe-list-editor__drag { cursor: grab; color: var(--bpe-fg-faint); display: inline-flex; }
|
|
5311
5827
|
.bpe-issue { display: flex; gap: 8px; align-items: flex-start; width: 100%; padding: 6px 8px; border: 0; border-radius: var(--bpe-radius-sm); background: transparent; text-align: left; font-size: var(--bpe-text-sm); color: var(--bpe-fg); }
|
|
5312
5828
|
.bpe-issue:hover { background: var(--bpe-bg-muted); }
|