@broadpaper/editor 0.2.0 → 0.3.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 +72 -0
- package/dist/index.d.ts +41 -9
- package/dist/index.js +860 -354
- package/dist/index.js.map +1 -1
- package/dist/styles.css +51 -1
- package/package.json +25 -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
|
-
/* @__PURE__ */ jsx16("span", { className: "bpe-truncate", children: value ? label : placeholder ?? (arraysOnly ? "Choose a list" : "Choose a field") })
|
|
3777
|
-
] }),
|
|
3778
|
-
children: (close) => /* @__PURE__ */ jsx16(
|
|
3779
|
-
DataPicker,
|
|
4064
|
+
className: `bpe-picker${editable ? " bpe-picker--wide" : ""}`,
|
|
4065
|
+
trigger: /* @__PURE__ */ jsxs16(
|
|
4066
|
+
"button",
|
|
3780
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,
|
|
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,13 @@ 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
|
|
|
4098
4530
|
// src/inspector/fields/FieldRenderer.tsx
|
|
4099
|
-
import { jsx as
|
|
4531
|
+
import { jsx as jsx21, jsxs as jsxs20 } from "react/jsx-runtime";
|
|
4100
4532
|
function getPath(obj, path) {
|
|
4101
4533
|
let cur = obj;
|
|
4102
4534
|
for (const p of path.split(".")) {
|
|
@@ -4108,15 +4540,22 @@ function getPath(obj, path) {
|
|
|
4108
4540
|
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
4541
|
function FieldRenderer({ field, value, onChange, ctx }) {
|
|
4110
4542
|
const description = typeof field.description === "function" ? field.description(ctx.props) : field.description;
|
|
4111
|
-
const
|
|
4543
|
+
const humanise = useTokenText(ctx.dataSources, ctx.scopeVars);
|
|
4544
|
+
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
4545
|
const str = typeof value === "string" ? value : value === void 0 || value === null ? "" : String(value);
|
|
4113
4546
|
switch (field.kind) {
|
|
4114
4547
|
case "text": {
|
|
4115
4548
|
const tokens = field.allowTokens !== false && ctx.allowExpressions;
|
|
4549
|
+
if (!tokens && str.includes("{{")) {
|
|
4550
|
+
return wrap(
|
|
4551
|
+
/* @__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." }) }),
|
|
4552
|
+
{ hint: description ?? "Fills itself in from your data." }
|
|
4553
|
+
);
|
|
4554
|
+
}
|
|
4116
4555
|
return wrap(
|
|
4117
|
-
/* @__PURE__ */
|
|
4118
|
-
/* @__PURE__ */
|
|
4119
|
-
tokens ? /* @__PURE__ */
|
|
4556
|
+
/* @__PURE__ */ jsxs20("div", { className: "bpe-binding", children: [
|
|
4557
|
+
/* @__PURE__ */ jsx21(TextInput, { value: str, onChange: (v) => onChange(v), lazy: true, placeholder: field.placeholder }),
|
|
4558
|
+
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
4559
|
onChange(`${str}${str && !str.endsWith(" ") ? " " : ""}{{ ${expr} }}`);
|
|
4121
4560
|
close();
|
|
4122
4561
|
} }) }) : null
|
|
@@ -4124,41 +4563,46 @@ function FieldRenderer({ field, value, onChange, ctx }) {
|
|
|
4124
4563
|
);
|
|
4125
4564
|
}
|
|
4126
4565
|
case "textarea":
|
|
4127
|
-
return wrap(/* @__PURE__ */
|
|
4566
|
+
return wrap(/* @__PURE__ */ jsx21(TextArea, { value: str, rows: field.rows ?? 3, placeholder: field.placeholder, onChange: (e) => onChange(e.target.value) }), { stacked: true });
|
|
4128
4567
|
case "richtext": {
|
|
4129
4568
|
const doc = isRichTextDoc3(value) ? value : richTextFromTemplate3(str);
|
|
4130
4569
|
const plain = richTextToPlain2(doc);
|
|
4131
4570
|
return wrap(
|
|
4132
|
-
/* @__PURE__ */
|
|
4571
|
+
/* @__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
4572
|
{ stacked: true, hint: "Double-click the block on the canvas for formatting, lists and links." }
|
|
4134
4573
|
);
|
|
4135
4574
|
}
|
|
4136
4575
|
case "number":
|
|
4137
|
-
return wrap(/* @__PURE__ */
|
|
4576
|
+
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
4577
|
case "toggle":
|
|
4139
|
-
return wrap(/* @__PURE__ */
|
|
4578
|
+
return wrap(/* @__PURE__ */ jsx21(Switch, { checked: !!value, onChange: (v) => onChange(v), label: field.label }));
|
|
4140
4579
|
case "select": {
|
|
4141
4580
|
const options = field.optionsFrom ? field.optionsFrom(ctx.props) : field.options ?? [];
|
|
4142
|
-
return wrap(/* @__PURE__ */
|
|
4581
|
+
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
4582
|
}
|
|
4144
4583
|
case "segmented":
|
|
4145
|
-
return wrap(/* @__PURE__ */
|
|
4584
|
+
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
4585
|
case "color":
|
|
4147
|
-
return wrap(/* @__PURE__ */
|
|
4586
|
+
return wrap(/* @__PURE__ */ jsx21(ColorField, { value: str || void 0, onChange: (v) => onChange(v ?? ""), theme: ctx.theme, allowTransparent: field.allowTransparent }));
|
|
4148
4587
|
case "font":
|
|
4149
|
-
return wrap(/* @__PURE__ */
|
|
4588
|
+
return wrap(/* @__PURE__ */ jsx21(FontSelect, { value: str, onChange, theme: ctx.theme }));
|
|
4150
4589
|
case "textStyle":
|
|
4151
|
-
return wrap(/* @__PURE__ */
|
|
4590
|
+
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
4591
|
case "binding":
|
|
4153
|
-
return wrap(/* @__PURE__ */
|
|
4592
|
+
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
4593
|
case "collection":
|
|
4155
|
-
return wrap(/* @__PURE__ */
|
|
4594
|
+
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
4595
|
case "expression": {
|
|
4596
|
+
if (!ctx.allowExpressions) {
|
|
4597
|
+
return wrap(
|
|
4598
|
+
/* @__PURE__ */ jsx21(BindingField, { value: str, onChange: (v) => onChange(v), dataSources: ctx.dataSources, scopeVars: ctx.scopeVars, placeholder: field.placeholder, allowExpression: false, allowCalculation: false })
|
|
4599
|
+
);
|
|
4600
|
+
}
|
|
4157
4601
|
const err = str.trim() ? checkExpressionSyntax3(str) : null;
|
|
4158
4602
|
return wrap(
|
|
4159
|
-
/* @__PURE__ */
|
|
4160
|
-
/* @__PURE__ */
|
|
4161
|
-
/* @__PURE__ */
|
|
4603
|
+
/* @__PURE__ */ jsxs20("div", { className: "bpe-binding", children: [
|
|
4604
|
+
/* @__PURE__ */ jsx21(TextInput, { value: str, onChange: (v) => onChange(v), lazy: true, mono: true, placeholder: field.placeholder, invalid: !!err }),
|
|
4605
|
+
/* @__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
4606
|
onChange(expr);
|
|
4163
4607
|
close();
|
|
4164
4608
|
} }) })
|
|
@@ -4167,7 +4611,7 @@ function FieldRenderer({ field, value, onChange, ctx }) {
|
|
|
4167
4611
|
);
|
|
4168
4612
|
}
|
|
4169
4613
|
case "image":
|
|
4170
|
-
return /* @__PURE__ */
|
|
4614
|
+
return /* @__PURE__ */ jsx21(
|
|
4171
4615
|
ImageFieldRow,
|
|
4172
4616
|
{
|
|
4173
4617
|
field,
|
|
@@ -4178,16 +4622,28 @@ function FieldRenderer({ field, value, onChange, ctx }) {
|
|
|
4178
4622
|
}
|
|
4179
4623
|
);
|
|
4180
4624
|
case "condition":
|
|
4181
|
-
return wrap(/* @__PURE__ */
|
|
4625
|
+
return wrap(/* @__PURE__ */ jsx21(ConditionBuilder, { value, onChange: (v) => onChange(v ?? ""), dataSources: ctx.dataSources, scopeVars: ctx.scopeVars, allowExpression: ctx.allowExpressions }), { stacked: true });
|
|
4182
4626
|
case "columns":
|
|
4183
|
-
return wrap(/* @__PURE__ */
|
|
4627
|
+
return wrap(/* @__PURE__ */ jsx21(ColumnsEditor, { value: Array.isArray(value) ? value : [], onChange, dataSources: ctx.dataSources, scopeVars: ctx.scopeVars }), { stacked: true });
|
|
4184
4628
|
case "series":
|
|
4185
|
-
return wrap(/* @__PURE__ */
|
|
4629
|
+
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
4630
|
case "keyValues":
|
|
4187
|
-
return wrap(
|
|
4631
|
+
return wrap(
|
|
4632
|
+
/* @__PURE__ */ jsx21(
|
|
4633
|
+
KeyValuesEditor,
|
|
4634
|
+
{
|
|
4635
|
+
value: Array.isArray(value) ? value : [],
|
|
4636
|
+
onChange,
|
|
4637
|
+
dataSources: ctx.dataSources,
|
|
4638
|
+
scopeVars: ctx.scopeVars,
|
|
4639
|
+
allowExpressions: ctx.allowExpressions
|
|
4640
|
+
}
|
|
4641
|
+
),
|
|
4642
|
+
{ stacked: true }
|
|
4643
|
+
);
|
|
4188
4644
|
case "list":
|
|
4189
4645
|
return wrap(
|
|
4190
|
-
/* @__PURE__ */
|
|
4646
|
+
/* @__PURE__ */ jsx21(
|
|
4191
4647
|
SubFormList,
|
|
4192
4648
|
{
|
|
4193
4649
|
value: Array.isArray(value) ? value : [],
|
|
@@ -4195,13 +4651,13 @@ function FieldRenderer({ field, value, onChange, ctx }) {
|
|
|
4195
4651
|
fields: field.fields,
|
|
4196
4652
|
itemLabel: field.itemLabel,
|
|
4197
4653
|
summary: field.itemSummary ? (item, i) => field.itemSummary(item, i, ctx.props) : void 0,
|
|
4198
|
-
renderField: (f, v, set) => /* @__PURE__ */
|
|
4654
|
+
renderField: (f, v, set) => /* @__PURE__ */ jsx21(FieldRenderer, { field: f, value: v, onChange: set, ctx })
|
|
4199
4655
|
}
|
|
4200
4656
|
),
|
|
4201
4657
|
{ stacked: true }
|
|
4202
4658
|
);
|
|
4203
4659
|
case "custom":
|
|
4204
|
-
return wrap(/* @__PURE__ */
|
|
4660
|
+
return wrap(/* @__PURE__ */ jsx21(CustomFieldHost, { field, value, onChange, ctx }), { stacked: true });
|
|
4205
4661
|
}
|
|
4206
4662
|
}
|
|
4207
4663
|
function coerce(v, options) {
|
|
@@ -4215,12 +4671,12 @@ function FontSelect({ value, onChange, theme }) {
|
|
|
4215
4671
|
...theme.typography.monoFont ? [{ value: "$typography.monoFont", label: "Mono font" }] : []
|
|
4216
4672
|
];
|
|
4217
4673
|
const fonts = [...(theme.fonts ?? []).map((f) => f.family), ...COMMON_FONTS].filter((f, i, a) => a.indexOf(f) === i);
|
|
4218
|
-
return /* @__PURE__ */
|
|
4674
|
+
return /* @__PURE__ */ jsx21(Select, { value, onChange, placeholder: "Default", options: [...themeFonts, ...fonts.map((f) => ({ value: f, label: f }))] });
|
|
4219
4675
|
}
|
|
4220
4676
|
function CustomFieldHost({ field, value, onChange, ctx }) {
|
|
4221
4677
|
const session = useSession();
|
|
4222
|
-
const ref =
|
|
4223
|
-
const mounted =
|
|
4678
|
+
const ref = useRef8(null);
|
|
4679
|
+
const mounted = useRef8(false);
|
|
4224
4680
|
const api = () => ({
|
|
4225
4681
|
value,
|
|
4226
4682
|
props: ctx.props,
|
|
@@ -4228,10 +4684,10 @@ function CustomFieldHost({ field, value, onChange, ctx }) {
|
|
|
4228
4684
|
setValue: onChange,
|
|
4229
4685
|
setProps: ctx.setProps,
|
|
4230
4686
|
dataSources: ctx.dataSources,
|
|
4231
|
-
schemaScope:
|
|
4687
|
+
schemaScope: createSchemaScope6(ctx.dataSources).child(ctx.scopeVars ?? {}),
|
|
4232
4688
|
theme: ctx.theme
|
|
4233
4689
|
});
|
|
4234
|
-
|
|
4690
|
+
useEffect7(() => {
|
|
4235
4691
|
const el = ref.current;
|
|
4236
4692
|
if (!el) return;
|
|
4237
4693
|
if (!mounted.current) {
|
|
@@ -4239,14 +4695,14 @@ function CustomFieldHost({ field, value, onChange, ctx }) {
|
|
|
4239
4695
|
mounted.current = true;
|
|
4240
4696
|
} else field.component.update?.(api());
|
|
4241
4697
|
});
|
|
4242
|
-
|
|
4698
|
+
useEffect7(() => {
|
|
4243
4699
|
const el = ref.current;
|
|
4244
4700
|
return () => {
|
|
4245
4701
|
if (el && mounted.current) field.component.unmount?.(el);
|
|
4246
4702
|
mounted.current = false;
|
|
4247
4703
|
};
|
|
4248
4704
|
}, [field.component, session]);
|
|
4249
|
-
return /* @__PURE__ */
|
|
4705
|
+
return /* @__PURE__ */ jsx21("div", { ref, style: { width: "100%" } });
|
|
4250
4706
|
}
|
|
4251
4707
|
function ImageFieldRow({
|
|
4252
4708
|
field,
|
|
@@ -4255,9 +4711,9 @@ function ImageFieldRow({
|
|
|
4255
4711
|
ctx,
|
|
4256
4712
|
render
|
|
4257
4713
|
}) {
|
|
4258
|
-
const [error, setError] =
|
|
4714
|
+
const [error, setError] = useState17(null);
|
|
4259
4715
|
return render(
|
|
4260
|
-
/* @__PURE__ */
|
|
4716
|
+
/* @__PURE__ */ jsx21(
|
|
4261
4717
|
ImageField,
|
|
4262
4718
|
{
|
|
4263
4719
|
value,
|
|
@@ -4277,13 +4733,13 @@ function ImageFieldRow({
|
|
|
4277
4733
|
|
|
4278
4734
|
// src/inspector/StyleGroups.tsx
|
|
4279
4735
|
import { textStyleLabel as textStyleLabel2 } from "@broadpaper/core";
|
|
4280
|
-
import { Fragment as Fragment4, jsx as
|
|
4736
|
+
import { Fragment as Fragment4, jsx as jsx22, jsxs as jsxs21 } from "react/jsx-runtime";
|
|
4281
4737
|
var SIDES = ["top", "right", "bottom", "left"];
|
|
4282
4738
|
function SpacingGrid({ value, onChange }) {
|
|
4283
4739
|
const v = value ?? {};
|
|
4284
|
-
return /* @__PURE__ */
|
|
4285
|
-
/* @__PURE__ */
|
|
4286
|
-
/* @__PURE__ */
|
|
4740
|
+
return /* @__PURE__ */ jsxs21("div", { children: [
|
|
4741
|
+
/* @__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)) }),
|
|
4742
|
+
/* @__PURE__ */ jsx22("div", { className: "bpe-spacing-grid", children: SIDES.map((s) => /* @__PURE__ */ jsx22("div", { className: "bpe-spacing-grid__lbl", children: s }, s)) })
|
|
4287
4743
|
] });
|
|
4288
4744
|
}
|
|
4289
4745
|
function StyleGroups({ node, groups, theme, textStyles, setStyle, setFlow, allowOverrides }) {
|
|
@@ -4291,62 +4747,62 @@ function StyleGroups({ node, groups, theme, textStyles, setStyle, setFlow, allow
|
|
|
4291
4747
|
const f = node.flow ?? {};
|
|
4292
4748
|
const has = (g) => groups.includes(g);
|
|
4293
4749
|
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__ */
|
|
4750
|
+
return /* @__PURE__ */ jsxs21(Fragment4, { children: [
|
|
4751
|
+
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: [
|
|
4752
|
+
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,
|
|
4753
|
+
has("typography") ? /* @__PURE__ */ jsxs21(Fragment4, { children: [
|
|
4754
|
+
/* @__PURE__ */ jsx22(Field, { label: "Font", children: /* @__PURE__ */ jsx22(Select, { value: s.fontFamily ?? "", onChange: (v) => setStyle({ fontFamily: v || void 0 }), placeholder: "Inherit", options: [{ value: "$typography.headingFont", label: "Heading font" }, { value: "$typography.bodyFont", label: "Body font" }, ...(theme.fonts ?? []).map((x) => ({ value: x.family, label: x.family })), ...["Inter", "Roboto", "Georgia", "Arial", "Times New Roman"].map((x) => ({ value: x, label: x }))] }) }),
|
|
4755
|
+
/* @__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" }) }),
|
|
4756
|
+
/* @__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) })) }) }),
|
|
4757
|
+
/* @__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" }) }),
|
|
4758
|
+
/* @__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" }) }),
|
|
4759
|
+
/* @__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" }] }) }),
|
|
4760
|
+
/* @__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" }] }) }),
|
|
4761
|
+
/* @__PURE__ */ jsx22(Field, { label: "Colour", children: /* @__PURE__ */ jsx22(ColorField, { value: s.color, onChange: (v) => setStyle({ color: v }), theme, allowTransparent: false, placeholder: "Inherit" }) })
|
|
4306
4762
|
] }) : null,
|
|
4307
|
-
has("alignment") ? /* @__PURE__ */
|
|
4763
|
+
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
4764
|
] }) }) : null,
|
|
4309
|
-
allowOverrides && (has("spacing") || has("size")) ? /* @__PURE__ */
|
|
4310
|
-
has("spacing") ? /* @__PURE__ */
|
|
4311
|
-
/* @__PURE__ */
|
|
4312
|
-
/* @__PURE__ */
|
|
4765
|
+
allowOverrides && (has("spacing") || has("size")) ? /* @__PURE__ */ jsx22(Section, { title: "Spacing & size", defaultOpen: false, children: /* @__PURE__ */ jsxs21("div", { className: "bpe-prop-grid", children: [
|
|
4766
|
+
has("spacing") ? /* @__PURE__ */ jsxs21(Fragment4, { children: [
|
|
4767
|
+
/* @__PURE__ */ jsx22(Field, { label: "Margin", stacked: true, children: /* @__PURE__ */ jsx22(SpacingGrid, { value: s.margin, onChange: (v) => setStyle({ margin: v }) }) }),
|
|
4768
|
+
/* @__PURE__ */ jsx22(Field, { label: "Padding", stacked: true, children: /* @__PURE__ */ jsx22(SpacingGrid, { value: s.padding, onChange: (v) => setStyle({ padding: v }) }) })
|
|
4313
4769
|
] }) : null,
|
|
4314
|
-
has("size") ? /* @__PURE__ */
|
|
4315
|
-
/* @__PURE__ */
|
|
4316
|
-
/* @__PURE__ */
|
|
4770
|
+
has("size") ? /* @__PURE__ */ jsxs21(Fragment4, { children: [
|
|
4771
|
+
/* @__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" }) }),
|
|
4772
|
+
/* @__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
4773
|
] }) : null
|
|
4318
4774
|
] }) }) : 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__ */
|
|
4775
|
+
allowOverrides && (has("background") || has("border")) ? /* @__PURE__ */ jsx22(Section, { title: "Background & border", defaultOpen: false, children: /* @__PURE__ */ jsxs21("div", { className: "bpe-prop-grid", children: [
|
|
4776
|
+
has("background") ? /* @__PURE__ */ jsx22(Field, { label: "Background", children: /* @__PURE__ */ jsx22(ColorField, { value: s.background, onChange: (v) => setStyle({ background: v }), theme, placeholder: "None" }) }) : null,
|
|
4777
|
+
has("border") ? /* @__PURE__ */ jsxs21(Fragment4, { children: [
|
|
4778
|
+
/* @__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" }) }),
|
|
4779
|
+
/* @__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" }) }),
|
|
4780
|
+
/* @__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" }] }) }),
|
|
4781
|
+
/* @__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" }) }),
|
|
4782
|
+
/* @__PURE__ */ jsx22(Field, { label: "Sides", children: /* @__PURE__ */ jsx22("div", { style: { display: "flex", gap: 4 }, children: SIDES.map((side) => {
|
|
4327
4783
|
const sides = s.border?.sides ?? [...SIDES];
|
|
4328
4784
|
const on = sides.includes(side);
|
|
4329
|
-
return /* @__PURE__ */
|
|
4785
|
+
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
4786
|
}) }) })
|
|
4331
4787
|
] }) : null,
|
|
4332
|
-
/* @__PURE__ */
|
|
4788
|
+
/* @__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
4789
|
] }) }) : null,
|
|
4334
|
-
has("flow") ? /* @__PURE__ */
|
|
4335
|
-
/* @__PURE__ */
|
|
4336
|
-
/* @__PURE__ */
|
|
4337
|
-
/* @__PURE__ */
|
|
4338
|
-
/* @__PURE__ */
|
|
4339
|
-
/* @__PURE__ */
|
|
4340
|
-
/* @__PURE__ */
|
|
4790
|
+
has("flow") ? /* @__PURE__ */ jsx22(Section, { title: "Page flow", defaultOpen: false, icon: "file-output", children: /* @__PURE__ */ jsxs21("div", { className: "bpe-prop-grid", children: [
|
|
4791
|
+
/* @__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 }) }) }),
|
|
4792
|
+
/* @__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 }) }) }),
|
|
4793
|
+
/* @__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" }] }) }),
|
|
4794
|
+
/* @__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" }] }) }),
|
|
4795
|
+
/* @__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" }) }),
|
|
4796
|
+
/* @__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
4797
|
] }) }) : null
|
|
4342
4798
|
] });
|
|
4343
4799
|
}
|
|
4344
4800
|
|
|
4345
4801
|
// src/inspector/NodeInspector.tsx
|
|
4346
|
-
import { Fragment as Fragment5, jsx as
|
|
4802
|
+
import { Fragment as Fragment5, jsx as jsx23, jsxs as jsxs22 } from "react/jsx-runtime";
|
|
4347
4803
|
function scopeVarsFor(entry, session) {
|
|
4348
4804
|
const chain = ancestors2(session.store.index(), entry.node.id);
|
|
4349
|
-
const root =
|
|
4805
|
+
const root = createSchemaScope7(session.dataSources);
|
|
4350
4806
|
let vars = {};
|
|
4351
4807
|
let scope = root;
|
|
4352
4808
|
for (const e of chain) {
|
|
@@ -4376,8 +4832,8 @@ function NodeInspector({ entry, actions }) {
|
|
|
4376
4832
|
const def = session.registry.get(node.type);
|
|
4377
4833
|
const theme = session.activeTheme;
|
|
4378
4834
|
const textStyles = Object.keys(session.store.template.styles.text);
|
|
4379
|
-
const scopeVars =
|
|
4380
|
-
const props =
|
|
4835
|
+
const scopeVars = useMemo8(() => scopeVarsFor(entry, session), [entry, session]);
|
|
4836
|
+
const props = useMemo8(() => ({ ...def ? typeof def.defaultProps === "function" ? def.defaultProps() : def.defaultProps : {}, ...node.props }), [def, node.props]);
|
|
4381
4837
|
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
4838
|
const ctx = {
|
|
4383
4839
|
dataSources: session.dataSources,
|
|
@@ -4391,53 +4847,53 @@ function NodeInspector({ entry, actions }) {
|
|
|
4391
4847
|
};
|
|
4392
4848
|
const label = nodeLabel(session, node);
|
|
4393
4849
|
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__ */
|
|
4850
|
+
return /* @__PURE__ */ jsxs22(Fragment5, { children: [
|
|
4851
|
+
/* @__PURE__ */ jsxs22("div", { className: "bpe-inspector__header", children: [
|
|
4852
|
+
/* @__PURE__ */ jsx23("span", { className: "bpe-inspector__icon", children: /* @__PURE__ */ jsx23(Icon, { name: def?.icon ?? "box" }) }),
|
|
4853
|
+
/* @__PURE__ */ jsxs22("div", { style: { minWidth: 0 }, children: [
|
|
4854
|
+
/* @__PURE__ */ jsx23("div", { className: "bpe-inspector__title", title: label, children: label }),
|
|
4855
|
+
/* @__PURE__ */ jsxs22("div", { className: "bpe-inspector__subtitle", children: [
|
|
4400
4856
|
def?.name ?? node.type,
|
|
4401
4857
|
def?.license && def.license !== "community" ? ` \xB7 ${def.license}` : ""
|
|
4402
4858
|
] })
|
|
4403
4859
|
] }),
|
|
4404
|
-
/* @__PURE__ */
|
|
4405
|
-
/* @__PURE__ */
|
|
4406
|
-
node.type !== "column" ? /* @__PURE__ */
|
|
4860
|
+
/* @__PURE__ */ jsxs22("div", { className: "bpe-inspector__actions", children: [
|
|
4861
|
+
/* @__PURE__ */ jsx23(Button, { size: "sm", variant: "ghost", icon: node.locked ? "lock" : "unlock", tooltip: node.locked ? "Unlock" : "Lock", onClick: () => actions.toggleLock() }),
|
|
4862
|
+
node.type !== "column" ? /* @__PURE__ */ jsx23(Button, { size: "sm", variant: "ghost", icon: "trash", tooltip: "Delete", onClick: () => actions.deleteSelection(), disabled: !!node.locked }) : null
|
|
4407
4863
|
] })
|
|
4408
4864
|
] }),
|
|
4409
|
-
/* @__PURE__ */
|
|
4410
|
-
issues.length ? /* @__PURE__ */
|
|
4411
|
-
/* @__PURE__ */
|
|
4412
|
-
/* @__PURE__ */
|
|
4865
|
+
/* @__PURE__ */ jsxs22("div", { className: "bpe-inspector__body", children: [
|
|
4866
|
+
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: [
|
|
4867
|
+
/* @__PURE__ */ jsx23(Icon, { name: i.severity === "error" ? "alert-circle" : i.severity === "warning" ? "alert-triangle" : "info" }),
|
|
4868
|
+
/* @__PURE__ */ jsx23("span", { children: i.message })
|
|
4413
4869
|
] }, k)) }) : null,
|
|
4414
|
-
!def ? /* @__PURE__ */
|
|
4415
|
-
/* @__PURE__ */
|
|
4416
|
-
/* @__PURE__ */
|
|
4870
|
+
!def ? /* @__PURE__ */ jsxs22("div", { className: "bpe-empty", children: [
|
|
4871
|
+
/* @__PURE__ */ jsx23(Icon, { name: "alert-triangle" }),
|
|
4872
|
+
/* @__PURE__ */ jsxs22("div", { className: "bpe-empty__title", children: [
|
|
4417
4873
|
"Unknown block \u201C",
|
|
4418
4874
|
node.type,
|
|
4419
4875
|
"\u201D"
|
|
4420
4876
|
] }),
|
|
4421
4877
|
"This block is not registered in this editor. Its settings are preserved."
|
|
4422
4878
|
] }) : null,
|
|
4423
|
-
node.type === "row" ? /* @__PURE__ */
|
|
4424
|
-
/* @__PURE__ */
|
|
4425
|
-
/* @__PURE__ */
|
|
4879
|
+
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: [
|
|
4880
|
+
/* @__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)) }),
|
|
4881
|
+
/* @__PURE__ */ jsx23("span", { className: "bpe-layout-item__name", children: l.label })
|
|
4426
4882
|
] }, l.id)) }) }) }) }) : null,
|
|
4427
|
-
node.type === "column" ? /* @__PURE__ */
|
|
4428
|
-
/* @__PURE__ */
|
|
4883
|
+
node.type === "column" ? /* @__PURE__ */ jsx23(Section, { title: "Column", children: /* @__PURE__ */ jsxs22("div", { className: "bpe-prop-grid", children: [
|
|
4884
|
+
/* @__PURE__ */ jsx23(Field, { label: "Width", hint: "Twelfths of the row. Change the row layout to resize columns.", children: /* @__PURE__ */ jsxs22(Badge, { children: [
|
|
4429
4885
|
String(node.props.span),
|
|
4430
4886
|
" / 12"
|
|
4431
4887
|
] }) }),
|
|
4432
|
-
/* @__PURE__ */
|
|
4888
|
+
/* @__PURE__ */ jsx23(Field, { label: "Row", children: /* @__PURE__ */ jsx23(Button, { size: "sm", onClick: () => actions.selectParent(), children: "Select row" }) })
|
|
4433
4889
|
] }) }) : null,
|
|
4434
4890
|
(def?.inspector ?? []).map((section, si) => {
|
|
4435
4891
|
if (section.when && !section.when(props)) return null;
|
|
4436
4892
|
const fields = section.fields.filter((f) => !f.when || f.when(props));
|
|
4437
4893
|
if (!fields.length) return null;
|
|
4438
|
-
return /* @__PURE__ */
|
|
4894
|
+
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
4895
|
}),
|
|
4440
|
-
def ? /* @__PURE__ */
|
|
4896
|
+
def ? /* @__PURE__ */ jsx23(
|
|
4441
4897
|
StyleGroups,
|
|
4442
4898
|
{
|
|
4443
4899
|
node,
|
|
@@ -4449,22 +4905,22 @@ function NodeInspector({ entry, actions }) {
|
|
|
4449
4905
|
setFlow: (patch) => session.store.dispatch({ type: "setFlow", id: node.id, patch }, { label: "Edit page flow" })
|
|
4450
4906
|
}
|
|
4451
4907
|
) : null,
|
|
4452
|
-
node.type !== "column" ? /* @__PURE__ */
|
|
4453
|
-
/* @__PURE__ */
|
|
4454
|
-
/* @__PURE__ */
|
|
4455
|
-
/* @__PURE__ */
|
|
4456
|
-
/* @__PURE__ */
|
|
4457
|
-
/* @__PURE__ */
|
|
4458
|
-
node.type !== "section" && node.type !== "column" ? /* @__PURE__ */
|
|
4908
|
+
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,
|
|
4909
|
+
/* @__PURE__ */ jsx23(Section, { title: "Advanced", defaultOpen: false, icon: "settings", children: /* @__PURE__ */ jsxs22("div", { className: "bpe-prop-grid", children: [
|
|
4910
|
+
/* @__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 }) }),
|
|
4911
|
+
/* @__PURE__ */ jsx23(Field, { label: "Locked", children: /* @__PURE__ */ jsx23(Switch, { checked: !!node.locked, onChange: (v) => session.store.dispatch({ type: "setLocked", id: node.id, locked: v }) }) }),
|
|
4912
|
+
/* @__PURE__ */ jsx23(Field, { label: "ID", children: /* @__PURE__ */ jsx23("code", { className: "bpe-mono bpe-muted", style: { fontSize: 10 }, children: node.id }) }),
|
|
4913
|
+
/* @__PURE__ */ jsx23(Field, { label: "Type", children: /* @__PURE__ */ jsx23("code", { className: "bpe-mono bpe-muted", style: { fontSize: 10 }, children: node.type }) }),
|
|
4914
|
+
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
4915
|
] }) })
|
|
4460
4916
|
] })
|
|
4461
4917
|
] });
|
|
4462
4918
|
}
|
|
4463
4919
|
|
|
4464
4920
|
// src/inspector/DocumentInspector.tsx
|
|
4465
|
-
import { useState as
|
|
4921
|
+
import { useState as useState18 } from "react";
|
|
4466
4922
|
import { PAGE_SIZES_MM, textStyleLabel as textStyleLabel3 } from "@broadpaper/core";
|
|
4467
|
-
import { Fragment as Fragment6, jsx as
|
|
4923
|
+
import { Fragment as Fragment6, jsx as jsx24, jsxs as jsxs23 } from "react/jsx-runtime";
|
|
4468
4924
|
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
4925
|
function DocumentInspector() {
|
|
4470
4926
|
const session = useSession();
|
|
@@ -4474,67 +4930,67 @@ function DocumentInspector() {
|
|
|
4474
4930
|
const page = template.page;
|
|
4475
4931
|
const setPage = (patch, label = "Page settings") => session.store.dispatch({ type: "setPage", patch }, { label, mergeKey: "page" });
|
|
4476
4932
|
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__ */
|
|
4933
|
+
const [editingStyle, setEditingStyle] = useState18(null);
|
|
4934
|
+
return /* @__PURE__ */ jsxs23(Fragment6, { children: [
|
|
4935
|
+
/* @__PURE__ */ jsxs23("div", { className: "bpe-inspector__header", children: [
|
|
4936
|
+
/* @__PURE__ */ jsx24("span", { className: "bpe-inspector__icon", children: /* @__PURE__ */ jsx24(Icon, { name: "file-text" }) }),
|
|
4937
|
+
/* @__PURE__ */ jsxs23("div", { style: { minWidth: 0 }, children: [
|
|
4938
|
+
/* @__PURE__ */ jsx24("div", { className: "bpe-inspector__title", children: "Document" }),
|
|
4939
|
+
/* @__PURE__ */ jsx24("div", { className: "bpe-inspector__subtitle", children: "Page, styles and settings" })
|
|
4484
4940
|
] })
|
|
4485
4941
|
] }),
|
|
4486
|
-
/* @__PURE__ */
|
|
4487
|
-
features.pageSettings ? /* @__PURE__ */
|
|
4488
|
-
/* @__PURE__ */
|
|
4489
|
-
sizeName === "custom" && typeof page.size === "object" ? /* @__PURE__ */
|
|
4490
|
-
/* @__PURE__ */
|
|
4491
|
-
/* @__PURE__ */
|
|
4942
|
+
/* @__PURE__ */ jsxs23("div", { className: "bpe-inspector__body", children: [
|
|
4943
|
+
features.pageSettings ? /* @__PURE__ */ jsx24(Section, { title: "Page", icon: "layout", children: /* @__PURE__ */ jsxs23("div", { className: "bpe-prop-grid", children: [
|
|
4944
|
+
/* @__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 }) }),
|
|
4945
|
+
sizeName === "custom" && typeof page.size === "object" ? /* @__PURE__ */ jsxs23(Fragment6, { children: [
|
|
4946
|
+
/* @__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" }) }),
|
|
4947
|
+
/* @__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
4948
|
] }) : null,
|
|
4493
|
-
/* @__PURE__ */
|
|
4494
|
-
/* @__PURE__ */
|
|
4495
|
-
/* @__PURE__ */
|
|
4496
|
-
/* @__PURE__ */
|
|
4497
|
-
/* @__PURE__ */
|
|
4949
|
+
/* @__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" }] }) }),
|
|
4950
|
+
/* @__PURE__ */ jsx24(Field, { label: "Margins", stacked: true, children: /* @__PURE__ */ jsxs23("div", { style: { width: "100%" }, children: [
|
|
4951
|
+
/* @__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)) }),
|
|
4952
|
+
/* @__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)) }),
|
|
4953
|
+
/* @__PURE__ */ jsx24("div", { className: "bpe-field__hint", children: "Millimetres" })
|
|
4498
4954
|
] }) }),
|
|
4499
|
-
/* @__PURE__ */
|
|
4955
|
+
/* @__PURE__ */ jsx24(Field, { label: "Background", children: /* @__PURE__ */ jsx24(ColorField, { value: page.background, onChange: (v) => setPage({ background: v }), theme, placeholder: "White" }) })
|
|
4500
4956
|
] }) }) : null,
|
|
4501
|
-
features.headerFooter ? /* @__PURE__ */
|
|
4502
|
-
/* @__PURE__ */
|
|
4503
|
-
/* @__PURE__ */
|
|
4504
|
-
/* @__PURE__ */
|
|
4505
|
-
/* @__PURE__ */
|
|
4506
|
-
/* @__PURE__ */
|
|
4957
|
+
features.headerFooter ? /* @__PURE__ */ jsx24(Section, { title: "Header & footer", icon: "panel-top", children: /* @__PURE__ */ jsxs23("div", { className: "bpe-prop-grid", children: [
|
|
4958
|
+
/* @__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" }) }),
|
|
4959
|
+
/* @__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" }) }),
|
|
4960
|
+
/* @__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 }) }) }),
|
|
4961
|
+
/* @__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 }) }) }),
|
|
4962
|
+
/* @__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
4963
|
] }) }) : null,
|
|
4508
|
-
features.documentStyles ? /* @__PURE__ */
|
|
4509
|
-
Object.entries(template.styles.text).map(([id, s]) => /* @__PURE__ */
|
|
4510
|
-
/* @__PURE__ */
|
|
4511
|
-
/* @__PURE__ */
|
|
4964
|
+
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: [
|
|
4965
|
+
Object.entries(template.styles.text).map(([id, s]) => /* @__PURE__ */ jsxs23("button", { type: "button", className: "bpe-doc-styles__item", onClick: () => setEditingStyle(id), children: [
|
|
4966
|
+
/* @__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) }),
|
|
4967
|
+
/* @__PURE__ */ jsxs23("span", { className: "bpe-doc-styles__id", children: [
|
|
4512
4968
|
id,
|
|
4513
4969
|
" \xB7 ",
|
|
4514
4970
|
s.fontSize ?? "\u2013",
|
|
4515
4971
|
"px"
|
|
4516
4972
|
] }),
|
|
4517
|
-
/* @__PURE__ */
|
|
4973
|
+
/* @__PURE__ */ jsx24(Icon, { name: "chevron-right", size: 12 })
|
|
4518
4974
|
] }, id)),
|
|
4519
|
-
/* @__PURE__ */
|
|
4975
|
+
/* @__PURE__ */ jsx24("div", { className: "bpe-field__hint", style: { padding: "4px 12px 8px" }, children: "Changing a style updates every block that uses it." })
|
|
4520
4976
|
] }) }) : 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__ */
|
|
4977
|
+
features.documentStyles ? /* @__PURE__ */ jsx24(Section, { title: "Tables & cards", icon: "table", defaultOpen: false, children: /* @__PURE__ */ jsxs23("div", { className: "bpe-prop-grid", children: [
|
|
4978
|
+
/* @__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" }] }) }),
|
|
4979
|
+
/* @__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 }) }),
|
|
4980
|
+
/* @__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 }) }),
|
|
4981
|
+
/* @__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" }) }),
|
|
4982
|
+
/* @__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" }) }),
|
|
4983
|
+
/* @__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" }) }),
|
|
4984
|
+
/* @__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" }) }),
|
|
4985
|
+
/* @__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 } } }) }) }),
|
|
4986
|
+
/* @__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" }) }),
|
|
4987
|
+
/* @__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
4988
|
] }) }) : null,
|
|
4533
|
-
/* @__PURE__ */
|
|
4534
|
-
/* @__PURE__ */
|
|
4535
|
-
/* @__PURE__ */
|
|
4536
|
-
/* @__PURE__ */
|
|
4537
|
-
/* @__PURE__ */
|
|
4989
|
+
/* @__PURE__ */ jsx24(Section, { title: "Settings", icon: "settings", defaultOpen: false, children: /* @__PURE__ */ jsxs23("div", { className: "bpe-prop-grid", children: [
|
|
4990
|
+
/* @__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" }) }),
|
|
4991
|
+
/* @__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" }) }),
|
|
4992
|
+
/* @__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" }) }),
|
|
4993
|
+
/* @__PURE__ */ jsx24(Field, { label: "Template ID", children: /* @__PURE__ */ jsx24("code", { className: "bpe-mono bpe-muted", style: { fontSize: 10 }, children: template.id }) })
|
|
4538
4994
|
] }) })
|
|
4539
4995
|
] })
|
|
4540
4996
|
] });
|
|
@@ -4542,69 +4998,69 @@ function DocumentInspector() {
|
|
|
4542
4998
|
function TextStyleEditor({ id, style, onChange, onBack }) {
|
|
4543
4999
|
const session = useSession();
|
|
4544
5000
|
const theme = session.activeTheme;
|
|
4545
|
-
return /* @__PURE__ */
|
|
4546
|
-
/* @__PURE__ */
|
|
4547
|
-
/* @__PURE__ */
|
|
4548
|
-
/* @__PURE__ */
|
|
5001
|
+
return /* @__PURE__ */ jsxs23("div", { className: "bpe-prop-grid", children: [
|
|
5002
|
+
/* @__PURE__ */ jsxs23("div", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [
|
|
5003
|
+
/* @__PURE__ */ jsx24(Button, { size: "sm", variant: "ghost", icon: "chevron-right", style: { transform: "rotate(180deg)" }, onClick: onBack, tooltip: "Back" }),
|
|
5004
|
+
/* @__PURE__ */ jsx24("strong", { children: id })
|
|
4549
5005
|
] }),
|
|
4550
|
-
/* @__PURE__ */
|
|
4551
|
-
/* @__PURE__ */
|
|
4552
|
-
/* @__PURE__ */
|
|
4553
|
-
/* @__PURE__ */
|
|
4554
|
-
/* @__PURE__ */
|
|
4555
|
-
/* @__PURE__ */
|
|
4556
|
-
/* @__PURE__ */
|
|
4557
|
-
/* @__PURE__ */
|
|
4558
|
-
/* @__PURE__ */
|
|
4559
|
-
/* @__PURE__ */
|
|
5006
|
+
/* @__PURE__ */ jsx24(Field, { label: "Font", children: /* @__PURE__ */ jsx24(Select, { value: style.fontFamily ?? "", onChange: (v) => onChange({ fontFamily: v || void 0 }), placeholder: "Theme body font", options: [{ value: "$typography.headingFont", label: "Theme heading font" }, { value: "$typography.bodyFont", label: "Theme body font" }, ...(theme.fonts ?? []).map((f) => ({ value: f.family, label: f.family }))] }) }),
|
|
5007
|
+
/* @__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" }) }),
|
|
5008
|
+
/* @__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) })) }) }),
|
|
5009
|
+
/* @__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 }) }),
|
|
5010
|
+
/* @__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" }) }),
|
|
5011
|
+
/* @__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" }] }) }),
|
|
5012
|
+
/* @__PURE__ */ jsx24(Field, { label: "Colour", children: /* @__PURE__ */ jsx24(ColorField, { value: style.color, onChange: (v) => onChange({ color: v }), theme, allowTransparent: false, placeholder: "Theme text" }) }),
|
|
5013
|
+
/* @__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" }) }),
|
|
5014
|
+
/* @__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" }) }),
|
|
5015
|
+
/* @__PURE__ */ jsx24(Field, { label: "Keep with next", children: /* @__PURE__ */ jsx24(Switch, { checked: !!style.keepWithNext, onChange: (v) => onChange({ keepWithNext: v || void 0 }) }) })
|
|
4560
5016
|
] });
|
|
4561
5017
|
}
|
|
4562
5018
|
|
|
4563
5019
|
// src/inspector/Inspector.tsx
|
|
4564
|
-
import { jsx as
|
|
5020
|
+
import { jsx as jsx25, jsxs as jsxs24 } from "react/jsx-runtime";
|
|
4565
5021
|
function Inspector({ actions }) {
|
|
4566
5022
|
const session = useSession();
|
|
4567
5023
|
const selection = useEditorState((s) => s.selection);
|
|
4568
5024
|
const mode = useEditorState((s) => s.mode);
|
|
4569
5025
|
const entry = useNode(selection[0] ?? null);
|
|
4570
5026
|
if (mode === "preview") {
|
|
4571
|
-
return /* @__PURE__ */
|
|
4572
|
-
/* @__PURE__ */
|
|
4573
|
-
/* @__PURE__ */
|
|
5027
|
+
return /* @__PURE__ */ jsx25("aside", { className: "bpe-inspector", "aria-label": "Properties", children: /* @__PURE__ */ jsxs24("div", { className: "bpe-empty", style: { marginTop: 40 }, children: [
|
|
5028
|
+
/* @__PURE__ */ jsx25(Icon, { name: "eye" }),
|
|
5029
|
+
/* @__PURE__ */ jsx25("div", { className: "bpe-empty__title", children: "Preview mode" }),
|
|
4574
5030
|
"Properties are hidden while previewing. Switch back to Design to edit.",
|
|
4575
|
-
/* @__PURE__ */
|
|
5031
|
+
/* @__PURE__ */ jsx25(Button, { size: "sm", onClick: () => session.setMode("design"), style: { marginTop: 8 }, children: "Back to design" })
|
|
4576
5032
|
] }) });
|
|
4577
5033
|
}
|
|
4578
5034
|
if (selection.length > 1) {
|
|
4579
|
-
return /* @__PURE__ */
|
|
4580
|
-
/* @__PURE__ */
|
|
4581
|
-
/* @__PURE__ */
|
|
4582
|
-
/* @__PURE__ */
|
|
4583
|
-
/* @__PURE__ */
|
|
5035
|
+
return /* @__PURE__ */ jsxs24("aside", { className: "bpe-inspector", "aria-label": "Properties", children: [
|
|
5036
|
+
/* @__PURE__ */ jsxs24("div", { className: "bpe-inspector__header", children: [
|
|
5037
|
+
/* @__PURE__ */ jsx25("span", { className: "bpe-inspector__icon", children: /* @__PURE__ */ jsx25(Icon, { name: "square-stack" }) }),
|
|
5038
|
+
/* @__PURE__ */ jsxs24("div", { children: [
|
|
5039
|
+
/* @__PURE__ */ jsxs24("div", { className: "bpe-inspector__title", children: [
|
|
4584
5040
|
selection.length,
|
|
4585
5041
|
" blocks selected"
|
|
4586
5042
|
] }),
|
|
4587
|
-
/* @__PURE__ */
|
|
5043
|
+
/* @__PURE__ */ jsx25("div", { className: "bpe-inspector__subtitle", children: "Actions apply to all" })
|
|
4588
5044
|
] })
|
|
4589
5045
|
] }),
|
|
4590
|
-
/* @__PURE__ */
|
|
4591
|
-
/* @__PURE__ */
|
|
4592
|
-
/* @__PURE__ */
|
|
4593
|
-
/* @__PURE__ */
|
|
4594
|
-
/* @__PURE__ */
|
|
5046
|
+
/* @__PURE__ */ jsxs24("div", { className: "bpe-prop-grid", children: [
|
|
5047
|
+
/* @__PURE__ */ jsx25(Button, { icon: "copy", onClick: () => actions.duplicateSelection(), children: "Duplicate all" }),
|
|
5048
|
+
/* @__PURE__ */ jsx25(Button, { icon: "panel-top", onClick: () => actions.wrapSelectionIn("card"), children: "Wrap in card" }),
|
|
5049
|
+
/* @__PURE__ */ jsx25(Button, { icon: "link", onClick: () => actions.wrapSelectionIn("keepTogether"), children: "Keep together" }),
|
|
5050
|
+
/* @__PURE__ */ jsx25(Button, { icon: "trash", variant: "danger", onClick: () => actions.deleteSelection(), children: "Delete all" })
|
|
4595
5051
|
] })
|
|
4596
5052
|
] });
|
|
4597
5053
|
}
|
|
4598
|
-
return /* @__PURE__ */
|
|
5054
|
+
return /* @__PURE__ */ jsx25("aside", { className: "bpe-inspector", "aria-label": "Properties", children: entry ? /* @__PURE__ */ jsx25(NodeInspector, { entry, actions }, entry.node.id) : /* @__PURE__ */ jsx25(DocumentInspector, {}) });
|
|
4599
5055
|
}
|
|
4600
5056
|
|
|
4601
5057
|
// src/ui/PanelResizer.tsx
|
|
4602
|
-
import { useRef as
|
|
4603
|
-
import { jsx as
|
|
5058
|
+
import { useRef as useRef9 } from "react";
|
|
5059
|
+
import { jsx as jsx26 } from "react/jsx-runtime";
|
|
4604
5060
|
function PanelResizer({ side, width, min, max, onChange, label }) {
|
|
4605
|
-
const start =
|
|
5061
|
+
const start = useRef9(null);
|
|
4606
5062
|
const clamp = (w) => Math.round(Math.min(max, Math.max(min, w)));
|
|
4607
|
-
return /* @__PURE__ */
|
|
5063
|
+
return /* @__PURE__ */ jsx26(
|
|
4608
5064
|
"div",
|
|
4609
5065
|
{
|
|
4610
5066
|
className: `bpe-resizer bpe-resizer--${side}`,
|
|
@@ -4642,7 +5098,7 @@ function PanelResizer({ side, width, min, max, onChange, label }) {
|
|
|
4642
5098
|
|
|
4643
5099
|
// src/ui/Boundary.tsx
|
|
4644
5100
|
import { Component } from "react";
|
|
4645
|
-
import { jsx as
|
|
5101
|
+
import { jsx as jsx27, jsxs as jsxs25 } from "react/jsx-runtime";
|
|
4646
5102
|
var Boundary = class extends Component {
|
|
4647
5103
|
state = { error: null };
|
|
4648
5104
|
static getDerivedStateFromError(error) {
|
|
@@ -4654,48 +5110,48 @@ var Boundary = class extends Component {
|
|
|
4654
5110
|
render() {
|
|
4655
5111
|
const { error } = this.state;
|
|
4656
5112
|
if (!error) return this.props.children;
|
|
4657
|
-
return /* @__PURE__ */
|
|
4658
|
-
/* @__PURE__ */
|
|
4659
|
-
/* @__PURE__ */
|
|
4660
|
-
/* @__PURE__ */
|
|
5113
|
+
return /* @__PURE__ */ jsxs25("div", { className: "bpe-boundary", role: "alert", style: this.props.slot ? { gridArea: this.props.slot } : void 0, children: [
|
|
5114
|
+
/* @__PURE__ */ jsx27(Icon, { name: "alert-triangle", size: 16 }),
|
|
5115
|
+
/* @__PURE__ */ jsxs25("div", { className: "bpe-boundary__body", children: [
|
|
5116
|
+
/* @__PURE__ */ jsxs25("div", { className: "bpe-boundary__title", children: [
|
|
4661
5117
|
this.props.area,
|
|
4662
5118
|
" stopped"
|
|
4663
5119
|
] }),
|
|
4664
|
-
/* @__PURE__ */
|
|
5120
|
+
/* @__PURE__ */ jsxs25("div", { className: "bpe-boundary__detail", children: [
|
|
4665
5121
|
error.message || "Something went wrong.",
|
|
4666
5122
|
" The rest of the designer, and your document, are unaffected."
|
|
4667
5123
|
] })
|
|
4668
5124
|
] }),
|
|
4669
|
-
/* @__PURE__ */
|
|
5125
|
+
/* @__PURE__ */ jsx27(Button, { size: "sm", icon: "rotate-ccw", onClick: () => this.setState({ error: null }), children: "Try again" })
|
|
4670
5126
|
] });
|
|
4671
5127
|
}
|
|
4672
5128
|
};
|
|
4673
5129
|
|
|
4674
5130
|
// src/ReportDesigner.tsx
|
|
4675
|
-
import { Fragment as Fragment7, jsx as
|
|
5131
|
+
import { Fragment as Fragment7, jsx as jsx28, jsxs as jsxs26 } from "react/jsx-runtime";
|
|
4676
5132
|
var NARROW_BELOW = 820;
|
|
4677
5133
|
function ReportDesignerRoot({ session }) {
|
|
4678
|
-
const actions =
|
|
4679
|
-
return /* @__PURE__ */
|
|
5134
|
+
const actions = useMemo9(() => createActions(session), [session]);
|
|
5135
|
+
return /* @__PURE__ */ jsx28(SessionContext.Provider, { value: session, children: /* @__PURE__ */ jsx28(Shell, { actions }) });
|
|
4680
5136
|
}
|
|
4681
5137
|
function Shell({ actions }) {
|
|
4682
5138
|
const options = useOptions();
|
|
4683
|
-
const [portal, setPortal] =
|
|
5139
|
+
const [portal, setPortal] = useState19(null);
|
|
4684
5140
|
const session = useSession();
|
|
4685
5141
|
const leftOpen = useUi((u) => u.leftOpen);
|
|
4686
5142
|
const rightOpen = useUi((u) => u.rightOpen);
|
|
4687
5143
|
const leftWidth = useUi((u) => u.leftWidth);
|
|
4688
5144
|
const rightWidth = useUi((u) => u.rightWidth);
|
|
4689
5145
|
const narrow = useUi((u) => u.narrow);
|
|
4690
|
-
const rootRef =
|
|
5146
|
+
const rootRef = useRef10(null);
|
|
4691
5147
|
const width = useElementWidth(rootRef);
|
|
4692
5148
|
const isNarrow = width > 0 && width < NARROW_BELOW;
|
|
4693
|
-
|
|
5149
|
+
useEffect8(() => {
|
|
4694
5150
|
if (isNarrow === narrow) return;
|
|
4695
5151
|
session.ui.patch(isNarrow ? { narrow: true, leftOpen: false, rightOpen: false } : { narrow: false });
|
|
4696
5152
|
}, [isNarrow, narrow, session]);
|
|
4697
5153
|
const appearance = options.appearance === "system" ? typeof matchMedia === "function" && matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light" : options.appearance ?? "light";
|
|
4698
|
-
|
|
5154
|
+
useEffect8(() => {
|
|
4699
5155
|
const onKey = (e) => {
|
|
4700
5156
|
const mod = e.metaKey || e.ctrlKey;
|
|
4701
5157
|
const target = e.target;
|
|
@@ -4725,18 +5181,18 @@ function Shell({ actions }) {
|
|
|
4725
5181
|
options.className
|
|
4726
5182
|
].filter(Boolean).join(" ");
|
|
4727
5183
|
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__ */
|
|
5184
|
+
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: [
|
|
5185
|
+
/* @__PURE__ */ jsx28(Boundary, { area: "The toolbar", slot: "toolbar", children: /* @__PURE__ */ jsx28(Toolbar, { actions }) }),
|
|
5186
|
+
leftOpen ? /* @__PURE__ */ jsxs26(Fragment7, { children: [
|
|
5187
|
+
/* @__PURE__ */ jsx28(Boundary, { area: "The insert panel", slot: "left", children: /* @__PURE__ */ jsx28(LeftPanel, { actions }) }),
|
|
5188
|
+
!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
|
|
5189
|
+
] }) : /* @__PURE__ */ jsx28("div", {}),
|
|
5190
|
+
/* @__PURE__ */ jsx28(Boundary, { area: "The canvas", slot: "canvas", children: /* @__PURE__ */ jsx28(Canvas, { actions }) }),
|
|
5191
|
+
rightOpen ? /* @__PURE__ */ jsxs26(Fragment7, { children: [
|
|
5192
|
+
!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,
|
|
5193
|
+
/* @__PURE__ */ jsx28(Boundary, { area: "The properties panel", slot: "right", children: /* @__PURE__ */ jsx28(Inspector, { actions }) })
|
|
5194
|
+
] }) : /* @__PURE__ */ jsx28("div", {}),
|
|
5195
|
+
scrim ? /* @__PURE__ */ jsx28(
|
|
4740
5196
|
"button",
|
|
4741
5197
|
{
|
|
4742
5198
|
type: "button",
|
|
@@ -4745,7 +5201,7 @@ function Shell({ actions }) {
|
|
|
4745
5201
|
onClick: () => session.ui.patch({ leftOpen: false, rightOpen: false })
|
|
4746
5202
|
}
|
|
4747
5203
|
) : null,
|
|
4748
|
-
/* @__PURE__ */
|
|
5204
|
+
/* @__PURE__ */ jsx28("div", { className: "bpe-portal bpe-scope", "data-appearance": appearance, ref: setPortal })
|
|
4749
5205
|
] }) });
|
|
4750
5206
|
}
|
|
4751
5207
|
|
|
@@ -4940,7 +5396,11 @@ var EDITOR_CSS = `/* BroadPaper editor design tokens.
|
|
|
4940
5396
|
.bpe-input--invalid { border-color: var(--bpe-danger) !important; }
|
|
4941
5397
|
.bpe-textarea { height: auto; min-height: 56px; padding: 6px 8px; resize: vertical; line-height: 1.45; }
|
|
4942
5398
|
.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
|
-
|
|
5399
|
+
/* \`width: 100%\`, because a flex container sizes to its content and this one holds
|
|
5400
|
+
an input. Without it every field with a unit or a suffix \u2014 a width in \`%\`, a
|
|
5401
|
+
size in \`px\` \u2014 shrank to about twenty characters and left the rest of the row
|
|
5402
|
+
blank, while the plain inputs beside it filled. */
|
|
5403
|
+
.bpe-input-group { position: relative; display: flex; align-items: center; width: 100%; }
|
|
4944
5404
|
.bpe-input-group .bpe-input { padding-right: 28px; }
|
|
4945
5405
|
.bpe-input-group__suffix { position: absolute; right: 8px; font-size: var(--bpe-text-xs); color: var(--bpe-fg-faint); pointer-events: none; }
|
|
4946
5406
|
.bpe-input-group__btn { position: absolute; right: 2px; top: 2px; }
|
|
@@ -5288,14 +5748,49 @@ var EDITOR_CSS = `/* BroadPaper editor design tokens.
|
|
|
5288
5748
|
.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
5749
|
.bpe-binding__btn:hover { border-color: var(--bpe-border-strong); }
|
|
5290
5750
|
.bpe-binding__btn--empty { color: var(--bpe-fg-faint); }
|
|
5751
|
+
/* A value the document works out. Shown, named after the fields it reads, and not
|
|
5752
|
+
editable where there is no expression editor to edit it with. */
|
|
5753
|
+
.bpe-binding__btn--locked { background: var(--bpe-bg-subtle); color: var(--bpe-fg-muted); cursor: default; }
|
|
5754
|
+
.bpe-binding__btn--locked:hover { border-color: var(--bpe-border); }
|
|
5291
5755
|
.bpe-binding__btn svg { width: 13px; height: 13px; flex-shrink: 0; color: var(--bpe-fg-muted); }
|
|
5292
5756
|
.bpe-expr { font-family: var(--bpe-font-mono); font-size: 11px; }
|
|
5293
5757
|
.bpe-picker { width: 300px; max-height: 380px; display: flex; flex-direction: column; padding: 0; overflow: hidden; }
|
|
5758
|
+
/* Wider when it is holding the calculation builder, which is three controls on a
|
|
5759
|
+
row plus a field name. At 300px every one of them truncated \u2014 "a total",
|
|
5760
|
+
"Total of", "In\u2026" \u2014 so the panel read as an interface that had been broken
|
|
5761
|
+
rather than one that was cramped. The field list on the other tab is a single
|
|
5762
|
+
column and does not need it, but one width for the panel is better than a
|
|
5763
|
+
popover that changes size when you change tab. */
|
|
5764
|
+
.bpe-picker--wide { width: 420px; max-height: 460px; }
|
|
5294
5765
|
.bpe-picker__search { padding: 8px; border-bottom: 1px solid var(--bpe-border); }
|
|
5295
5766
|
.bpe-picker__list { flex: 1; overflow-y: auto; padding: 4px; }
|
|
5296
5767
|
.bpe-picker__foot { display: flex; gap: 6px; align-items: center; padding: 8px; border-top: 1px solid var(--bpe-border); }
|
|
5297
5768
|
.bpe-picker__foot .bpe-input { flex: 1; }
|
|
5298
5769
|
.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); }
|
|
5770
|
+
/* The value panel's two answers: a field, or a value worked out. Shorter than the
|
|
5771
|
+
inspector's own tabs because these carry a word rather than a word and an icon. */
|
|
5772
|
+
.bpe-picker__tabs { display: flex; gap: 2px; padding: 6px 6px 0; border-bottom: 1px solid var(--bpe-border); }
|
|
5773
|
+
.bpe-picker__tabs .bpe-tab { height: 28px; }
|
|
5774
|
+
|
|
5775
|
+
/* The calculation builder. A column of rows, each reading as part of a sentence,
|
|
5776
|
+
and every control allowed to shrink \u2014 the properties panel is resizable, so
|
|
5777
|
+
anything in it can be squeezed and \`min-width: 0\` is what keeps the remove
|
|
5778
|
+
button inside the panel rather than out through its \`overflow: hidden\`. */
|
|
5779
|
+
.bpe-calc { display: flex; flex-direction: column; gap: 10px; padding: 10px; min-width: 0; }
|
|
5780
|
+
/* A step is a labelled block, not a line of controls.
|
|
5781
|
+
Three selects and a field name across one row is what made this unreadable:
|
|
5782
|
+
every control shrank to its truncation point and the sentence the builder is
|
|
5783
|
+
supposed to read as was unrecoverable from the abbreviations. */
|
|
5784
|
+
.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); }
|
|
5785
|
+
.bpe-calc__row { display: flex; align-items: center; gap: 6px; min-width: 0; }
|
|
5786
|
+
.bpe-grow { flex: 1 1 auto; }
|
|
5787
|
+
.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); }
|
|
5788
|
+
.bpe-calc__row .bpe-select { flex: 1 1 auto; min-width: 0; }
|
|
5789
|
+
.bpe-calc__term { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
|
|
5790
|
+
.bpe-calc__term .bpe-binding { width: 100%; }
|
|
5791
|
+
.bpe-calc__term .bpe-binding__btn { flex: 1 1 auto; min-width: 0; }
|
|
5792
|
+
.bpe-calc__term .bpe-input { width: 100%; }
|
|
5793
|
+
.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
5794
|
.bpe-cond { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
|
|
5300
5795
|
.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
5796
|
.bpe-cond__row { display: grid; grid-template-columns: minmax(0, 1fr); gap: 4px; min-width: 0; }
|
|
@@ -5307,6 +5802,17 @@ var EDITOR_CSS = `/* BroadPaper editor design tokens.
|
|
|
5307
5802
|
.bpe-list-editor__head .bpe-truncate { flex: 1 1 0; min-width: 0; }
|
|
5308
5803
|
.bpe-list-editor__item { min-width: 0; }
|
|
5309
5804
|
.bpe-list-editor__body { padding: 6px 8px 8px; border-top: 1px solid var(--bpe-border); display: flex; flex-direction: column; gap: 6px; }
|
|
5805
|
+
/*
|
|
5806
|
+
* Inside a list item, every field is stacked.
|
|
5807
|
+
*
|
|
5808
|
+
* The two-column field \u2014 a 96px label and the control beside it \u2014 is right in the
|
|
5809
|
+
* panel and wrong one level in: the item is already indented and bordered, so the
|
|
5810
|
+
* label column eats a third of what is left and the control ends in the middle of
|
|
5811
|
+
* the row with the rest blank. Stacked gives the control the whole width, and it
|
|
5812
|
+
* matches the fields in there that were already stacked because they needed it.
|
|
5813
|
+
*/
|
|
5814
|
+
.bpe-list-editor__body .bpe-field { grid-template-columns: 1fr; gap: 4px; }
|
|
5815
|
+
.bpe-list-editor__body .bpe-field__label { white-space: normal; overflow: visible; }
|
|
5310
5816
|
.bpe-list-editor__drag { cursor: grab; color: var(--bpe-fg-faint); display: inline-flex; }
|
|
5311
5817
|
.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
5818
|
.bpe-issue:hover { background: var(--bpe-bg-muted); }
|