@bendyline/squisq-react 2.2.0 → 2.3.1

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.
@@ -0,0 +1,330 @@
1
+ import {
2
+ useAutoSurface
3
+ } from "./chunk-TT6ENR6T.js";
4
+ import {
5
+ MarkdownRenderer
6
+ } from "./chunk-D7KN4CRG.js";
7
+
8
+ // src/jsonView/useJsonViewTokens.ts
9
+ import { useMemo } from "react";
10
+ import { buildJsonFormTokens, resolveJsonFormTheme } from "@bendyline/squisq/jsonForm";
11
+ function useJsonViewTokens(theme, surface) {
12
+ const auto = useAutoSurface(surface === "auto");
13
+ const effectiveSurface = surface === "auto" ? auto : surface ?? void 0;
14
+ return useMemo(() => {
15
+ const style = buildJsonFormTokens(theme, effectiveSurface, {
16
+ prefix: "--squisq-json"
17
+ });
18
+ return { style, theme: resolveJsonFormTheme(theme, effectiveSurface) };
19
+ }, [theme, effectiveSurface]);
20
+ }
21
+
22
+ // src/jsonView/RenderNode.tsx
23
+ import {
24
+ chooseControl,
25
+ resolveFlag,
26
+ resolveRef
27
+ } from "@bendyline/squisq/jsonForm";
28
+
29
+ // src/jsonView/viewers.tsx
30
+ import { Fragment, useMemo as useMemo2 } from "react";
31
+ import {
32
+ arrayItemKind
33
+ } from "@bendyline/squisq/jsonForm";
34
+ import { parseMarkdown } from "@bendyline/squisq/markdown";
35
+ import { Fragment as Fragment2, jsx, jsxs } from "react/jsx-runtime";
36
+ function TextViewer({ value }) {
37
+ if (value === void 0 || value === null || value === "") {
38
+ return /* @__PURE__ */ jsx("span", { className: "squisq-jv-empty", children: "\u2014" });
39
+ }
40
+ return /* @__PURE__ */ jsx("span", { className: "squisq-jv-value", children: String(value) });
41
+ }
42
+ function MultilineViewer({ value }) {
43
+ if (value === void 0 || value === null || value === "") {
44
+ return /* @__PURE__ */ jsx("span", { className: "squisq-jv-empty", children: "\u2014" });
45
+ }
46
+ return /* @__PURE__ */ jsx("span", { className: "squisq-jv-value squisq-jv-value--multiline", children: String(value) });
47
+ }
48
+ function RichTextViewer({ value }) {
49
+ const nodes = useMemo2(() => {
50
+ if (typeof value !== "string" || value === "") return null;
51
+ try {
52
+ const doc = parseMarkdown(value);
53
+ return doc.children;
54
+ } catch {
55
+ return null;
56
+ }
57
+ }, [value]);
58
+ if (!nodes) return /* @__PURE__ */ jsx("span", { className: "squisq-jv-empty", children: "\u2014" });
59
+ return /* @__PURE__ */ jsx("div", { className: "squisq-jv-richtext", children: /* @__PURE__ */ jsx(MarkdownRenderer, { nodes }) });
60
+ }
61
+ function NumberViewer({ value }) {
62
+ if (value === void 0 || value === null) {
63
+ return /* @__PURE__ */ jsx("span", { className: "squisq-jv-empty", children: "\u2014" });
64
+ }
65
+ return /* @__PURE__ */ jsx("span", { className: "squisq-jv-value", children: String(value) });
66
+ }
67
+ function BooleanViewer({ value }) {
68
+ const on = Boolean(value);
69
+ return /* @__PURE__ */ jsx("span", { className: `squisq-jv-toggle squisq-jv-toggle--${on ? "on" : "off"}`, children: on ? "On" : "Off" });
70
+ }
71
+ function EnumViewer({ value, schema }) {
72
+ if (value === void 0 || value === null || value === "") {
73
+ return /* @__PURE__ */ jsx("span", { className: "squisq-jv-empty", children: "\u2014" });
74
+ }
75
+ const labels = schema.squisq?.enumLabels;
76
+ const display = labels && typeof value === "string" ? labels[value] ?? value : String(value);
77
+ return /* @__PURE__ */ jsx("span", { className: "squisq-jv-value", children: display });
78
+ }
79
+ function ColorViewer({ value }) {
80
+ if (typeof value !== "string" || value === "") {
81
+ return /* @__PURE__ */ jsx("span", { className: "squisq-jv-empty", children: "\u2014" });
82
+ }
83
+ return /* @__PURE__ */ jsxs("span", { className: "squisq-jv-color", children: [
84
+ /* @__PURE__ */ jsx("span", { className: "squisq-jv-color__swatch", style: { background: value }, "aria-hidden": true }),
85
+ /* @__PURE__ */ jsx("span", { className: "squisq-jv-color__hex", children: value })
86
+ ] });
87
+ }
88
+ function DateViewer({ value, schema }) {
89
+ if (typeof value !== "string" || value === "") {
90
+ return /* @__PURE__ */ jsx("span", { className: "squisq-jv-empty", children: "\u2014" });
91
+ }
92
+ const fmt = schema.format;
93
+ let display = value;
94
+ try {
95
+ const date = new Date(value);
96
+ if (!Number.isNaN(date.getTime())) {
97
+ if (fmt === "time") {
98
+ display = date.toLocaleTimeString();
99
+ } else if (fmt === "date") {
100
+ display = date.toLocaleDateString();
101
+ } else {
102
+ display = date.toLocaleString();
103
+ }
104
+ }
105
+ } catch {
106
+ }
107
+ return /* @__PURE__ */ jsx("span", { className: "squisq-jv-value", children: display });
108
+ }
109
+ function ChipBinViewer({ value, schema }) {
110
+ if (!Array.isArray(value) || value.length === 0) {
111
+ return /* @__PURE__ */ jsx("span", { className: "squisq-jv-empty", children: "\u2014" });
112
+ }
113
+ const itemSchema = Array.isArray(schema.items) ? schema.items[0] : schema.items;
114
+ const labels = itemSchema?.squisq?.enumLabels;
115
+ return /* @__PURE__ */ jsx("div", { className: "squisq-jv-chip-bin", children: value.map((item, i) => {
116
+ const label = labels && typeof item === "string" ? labels[item] ?? String(item) : String(item);
117
+ return /* @__PURE__ */ jsx("span", { className: "squisq-jv-chip", children: label }, i);
118
+ }) });
119
+ }
120
+ function CardStackViewer(props) {
121
+ const { value, schema, rootSchema, rootData, pointer, density } = props;
122
+ if (!Array.isArray(value) || value.length === 0) {
123
+ return /* @__PURE__ */ jsx("span", { className: "squisq-jv-empty", children: "No items" });
124
+ }
125
+ const itemSchema = (Array.isArray(schema.items) ? schema.items[0] : schema.items) ?? {};
126
+ const itemLabel = itemSchema.squisq?.itemLabel;
127
+ return /* @__PURE__ */ jsx("div", { className: "squisq-jv-card-stack", children: value.map((item, i) => {
128
+ const title = resolveItemTitle(itemLabel, item, i);
129
+ return /* @__PURE__ */ jsxs("div", { className: "squisq-jv-card", children: [
130
+ title ? /* @__PURE__ */ jsx("h4", { className: "squisq-jv-card__title", children: title }) : null,
131
+ /* @__PURE__ */ jsx(
132
+ RenderNode,
133
+ {
134
+ value: item,
135
+ schema: itemSchema,
136
+ rootSchema,
137
+ rootData,
138
+ pointer: `${pointer}/${i}`,
139
+ density,
140
+ suppressTopGroupTitle: true
141
+ }
142
+ )
143
+ ] }, i);
144
+ }) });
145
+ }
146
+ function resolveItemTitle(spec, item, index) {
147
+ if (!spec) return `Item ${index + 1}`;
148
+ if (typeof spec === "string") return spec;
149
+ if (item && typeof item === "object") {
150
+ const v = item[spec.fromField];
151
+ if (typeof v === "string" && v !== "") return v;
152
+ if (typeof v === "number") return String(v);
153
+ }
154
+ return `Item ${index + 1}`;
155
+ }
156
+ function GroupViewer(props) {
157
+ const { value, schema, rootSchema, rootData, pointer, density, suppressTitle } = props;
158
+ const title = !suppressTitle ? schema.squisq?.label ?? schema.title : void 0;
159
+ const help = schema.squisq?.help ?? schema.description;
160
+ const obj = (value && typeof value === "object" ? value : {}) ?? {};
161
+ const propEntries = Object.entries(schema.properties ?? {});
162
+ return /* @__PURE__ */ jsxs("section", { className: "squisq-jv-group", children: [
163
+ title ? /* @__PURE__ */ jsx("h3", { className: "squisq-jv-group__title", children: title }) : null,
164
+ help ? /* @__PURE__ */ jsx("p", { className: "squisq-jv-group__help", children: help }) : null,
165
+ propEntries.map(([key, propSchema]) => /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(
166
+ RowOrSection,
167
+ {
168
+ label: propSchema.squisq?.label ?? propSchema.title ?? key,
169
+ help: propSchema.squisq?.help ?? propSchema.description,
170
+ kindHint: propSchema,
171
+ children: /* @__PURE__ */ jsx(
172
+ RenderNode,
173
+ {
174
+ value: obj[key],
175
+ schema: propSchema,
176
+ rootSchema,
177
+ rootData,
178
+ pointer: `${pointer}/${key}`,
179
+ density,
180
+ suppressTopGroupTitle: true
181
+ }
182
+ )
183
+ }
184
+ ) }, key))
185
+ ] });
186
+ }
187
+ function RowOrSection({
188
+ label,
189
+ help,
190
+ kindHint,
191
+ children
192
+ }) {
193
+ const composite = isCompositeKind(kindHint);
194
+ if (composite) {
195
+ return /* @__PURE__ */ jsx(Fragment2, { children });
196
+ }
197
+ return /* @__PURE__ */ jsxs("div", { className: "squisq-jv-row", children: [
198
+ /* @__PURE__ */ jsx("div", { className: "squisq-jv-label", title: help, children: label }),
199
+ /* @__PURE__ */ jsx("div", { children })
200
+ ] });
201
+ }
202
+ function isCompositeKind(schema) {
203
+ const t = schema.type;
204
+ const arrayLike = t === "array" || Array.isArray(t) && t.includes("array");
205
+ if (arrayLike) {
206
+ return arrayItemKind(schema) === "object";
207
+ }
208
+ const objectLike = t === "object" || Array.isArray(t) && t.includes("object");
209
+ if (objectLike) return true;
210
+ if (schema.oneOf || schema.anyOf) return true;
211
+ if (schema.format === "markdown" || schema.squisq?.control === "richtext") return true;
212
+ return false;
213
+ }
214
+ function TabsViewer(props) {
215
+ const { value, schema, rootSchema, rootData, pointer, density } = props;
216
+ const branches = (schema.oneOf ?? schema.anyOf ?? []).slice();
217
+ const matchedIndex = pickMatchingBranch(branches, value);
218
+ const branch = branches[matchedIndex];
219
+ if (!branch) {
220
+ return /* @__PURE__ */ jsx(TextViewer, { ...props });
221
+ }
222
+ return /* @__PURE__ */ jsxs("div", { className: "squisq-jv-tabs", children: [
223
+ /* @__PURE__ */ jsx("span", { className: "squisq-jv-tabs__discriminator", children: branch.squisq?.label ?? branch.title ?? `Option ${matchedIndex + 1}` }),
224
+ /* @__PURE__ */ jsx(
225
+ RenderNode,
226
+ {
227
+ value,
228
+ schema: branch,
229
+ rootSchema,
230
+ rootData,
231
+ pointer,
232
+ density
233
+ }
234
+ )
235
+ ] });
236
+ }
237
+ function pickMatchingBranch(branches, value) {
238
+ for (let i = 0; i < branches.length; i++) {
239
+ if (matchesShape(branches[i], value)) return i;
240
+ }
241
+ return 0;
242
+ }
243
+ function matchesShape(schema, value) {
244
+ const t = schema.type;
245
+ const target = Array.isArray(t) ? t.find((x) => x !== "null") : t;
246
+ if (!target) return true;
247
+ switch (target) {
248
+ case "string":
249
+ return typeof value === "string";
250
+ case "number":
251
+ case "integer":
252
+ return typeof value === "number";
253
+ case "boolean":
254
+ return typeof value === "boolean";
255
+ case "null":
256
+ return value === null;
257
+ case "array":
258
+ return Array.isArray(value);
259
+ case "object":
260
+ return value !== null && typeof value === "object" && !Array.isArray(value);
261
+ default:
262
+ return false;
263
+ }
264
+ }
265
+ var VIEWERS = {
266
+ text: TextViewer,
267
+ multiline: MultilineViewer,
268
+ richtext: RichTextViewer,
269
+ color: ColorViewer,
270
+ date: DateViewer,
271
+ time: DateViewer,
272
+ datetime: DateViewer,
273
+ slider: NumberViewer,
274
+ stepper: NumberViewer,
275
+ segmented: EnumViewer,
276
+ radio: EnumViewer,
277
+ combobox: EnumViewer,
278
+ toggle: BooleanViewer,
279
+ checkbox: BooleanViewer,
280
+ card: GroupViewer,
281
+ group: GroupViewer,
282
+ "card-stack": CardStackViewer,
283
+ "chip-bin": ChipBinViewer,
284
+ tabs: TabsViewer
285
+ };
286
+
287
+ // src/jsonView/RenderNode.tsx
288
+ import { jsx as jsx2 } from "react/jsx-runtime";
289
+ function RenderNode(props) {
290
+ const resolved = resolveRef(props.schema, props.rootSchema) ?? props.schema;
291
+ if (resolveFlag(resolved.squisq?.hidden, props.rootData)) return null;
292
+ const kind = chooseControl(resolved);
293
+ const Viewer = VIEWERS[kind];
294
+ const viewerProps = {
295
+ value: props.value,
296
+ schema: resolved,
297
+ rootSchema: props.rootSchema,
298
+ rootData: props.rootData,
299
+ pointer: props.pointer,
300
+ density: props.density
301
+ };
302
+ if (kind === "group" || kind === "card") {
303
+ const Group = Viewer;
304
+ return /* @__PURE__ */ jsx2(Group, { ...viewerProps, suppressTitle: props.suppressTopGroupTitle });
305
+ }
306
+ return /* @__PURE__ */ jsx2(Viewer, { ...viewerProps });
307
+ }
308
+
309
+ // src/jsonView/JsonView.tsx
310
+ import { jsx as jsx3 } from "react/jsx-runtime";
311
+ function JsonView(props) {
312
+ const { schema, value, theme, surface, density = "comfortable", className } = props;
313
+ const { style } = useJsonViewTokens(theme, surface);
314
+ const cls = "squisq-json-view" + (density === "compact" ? " squisq-json-view--compact" : "") + (className ? ` ${className}` : "");
315
+ return /* @__PURE__ */ jsx3("div", { className: cls, style, children: /* @__PURE__ */ jsx3(
316
+ RenderNode,
317
+ {
318
+ value,
319
+ schema,
320
+ rootSchema: schema,
321
+ rootData: value,
322
+ pointer: "",
323
+ density
324
+ }
325
+ ) });
326
+ }
327
+
328
+ export {
329
+ JsonView
330
+ };