@wipco/sdui-editor 0.15.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import React, { lazy, Suspense, useState, useCallback, useEffect, useMemo, useRef, useLayoutEffect, useDeferredValue, useReducer, useId } from 'react';
2
- import { defaultRegistry, createRegistry, Tooltip, SidebarNav, SegmentedControl, Radio, Breadcrumb, Avatar, Alert, Icon, IconButton, Spinner, Button, Card, Input, Badge, EmptyState, Table, SduiDialog, Field, Select, validateSpec, SduiRenderer, Checkbox, Dialog, Textarea, Switch } from '@wipco/sdui';
2
+ import { defaultRegistry, createRegistry, Tooltip, SidebarNav, SegmentedControl, Radio, Breadcrumb, Avatar, Alert, Icon, IconButton, Spinner, Button, Card, Tabs, Input, Badge, EmptyState, Table, SduiDialog, Field, Select, validateSpec, SduiRenderer, Checkbox, Switch, Dialog, Textarea } from '@wipco/sdui';
3
3
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
4
4
  import { EditorView, basicSetup } from 'codemirror';
5
5
  import { EditorState } from '@codemirror/state';
@@ -7,6 +7,7 @@ import { keymap } from '@codemirror/view';
7
7
  import { indentWithTab } from '@codemirror/commands';
8
8
  import { json } from '@codemirror/lang-json';
9
9
  import { getComponentMeta, slotNamesOf, paletteGroups } from '@wipco/sdui/meta';
10
+ import { AutocompleteField } from '@wipco/sdui/widgets/AutocompleteField';
10
11
 
11
12
  // src/SduiEditor.tsx
12
13
 
@@ -77,6 +78,10 @@ var PAGE_ID_PATTERN = /^([A-Za-z0-9_-]+|\{[A-Za-z0-9_]+\})(\/([A-Za-z0-9_-]+|\{[
77
78
  function isValidPageId(id) {
78
79
  return PAGE_ID_PATTERN.test(id) && id.length <= 200;
79
80
  }
81
+ var DIALOG_PREFIX = "dialogs/";
82
+ function isDialogSpecId(id) {
83
+ return id.startsWith(DIALOG_PREFIX);
84
+ }
80
85
  function titleOf(spec) {
81
86
  if (spec && typeof spec === "object" && typeof spec.title === "string") {
82
87
  return spec.title;
@@ -384,11 +389,47 @@ var LIST = {
384
389
  }
385
390
  ]
386
391
  };
392
+ var DIALOG = {
393
+ version: 1,
394
+ title: "\uB2E4\uC774\uC5BC\uB85C\uADF8",
395
+ body: [
396
+ {
397
+ component: "Show",
398
+ props: { when: { $state: "open" } },
399
+ children: [
400
+ {
401
+ component: "Dialog",
402
+ props: {
403
+ title: "\uB2E4\uC774\uC5BC\uB85C\uADF8 \uC81C\uBAA9",
404
+ size: "md",
405
+ onClose: { action: "close", key: "open" }
406
+ },
407
+ slots: {
408
+ footer: [
409
+ {
410
+ component: "Button",
411
+ props: { onClick: { action: "close", key: "open" } },
412
+ children: ["\uCDE8\uC18C"]
413
+ },
414
+ {
415
+ component: "Button",
416
+ props: { variant: "primary", onClick: { action: "close", key: "open" } },
417
+ children: ["\uD655\uC778"]
418
+ }
419
+ ]
420
+ },
421
+ children: [{ component: "Text", props: { preset: "body", value: "\uB0B4\uC6A9\uC744 \uAD6C\uC131\uD558\uC138\uC694." } }]
422
+ }
423
+ ]
424
+ }
425
+ ]
426
+ };
387
427
  var TEMPLATES = [
388
428
  { key: "blank", label: "\uBE48 \uD398\uC774\uC9C0", description: "\uCE74\uB4DC \uD558\uB098\uB85C \uC2DC\uC791", spec: BLANK },
389
429
  { key: "dashboard", label: "\uB300\uC2DC\uBCF4\uB4DC", description: "StatCard 4\uAC1C + \uCE74\uB4DC", spec: DASHBOARD },
390
430
  { key: "form", label: "\uC785\uB825 \uD3FC", description: "\uAC80\uC99D + \uC81C\uCD9C \uACE8\uACA9", spec: FORM },
391
- { key: "list", label: "\uBAA9\uB85D", description: "\uD14C\uC774\uBE14 + \uC0C1\uD0DC \uBC30\uC9C0", spec: LIST }
431
+ { key: "list", label: "\uBAA9\uB85D", description: "\uD14C\uC774\uBE14 + \uC0C1\uD0DC \uBC30\uC9C0", spec: LIST },
432
+ { key: "dialog", label: "\uB2E4\uC774\uC5BC\uB85C\uADF8 \uC870\uAC01", description: "Show(open) + Dialog \uACE8\uACA9 \u2014 dialogs/ \uC2A4\uD399\uC6A9", spec: DIALOG }
392
433
  ];
393
434
 
394
435
  // src/util.ts
@@ -409,11 +450,18 @@ function PageListView({ store, onEdit, onPreview }) {
409
450
  const [listFailed, setListFailed] = useState(false);
410
451
  const [error, setError] = useState(null);
411
452
  const [query, setQuery] = useState("");
453
+ const [tab, setTab] = useState("pages");
412
454
  const [createOpen, setCreateOpen] = useState(false);
413
455
  const [newId, setNewId] = useState("");
414
456
  const [template, setTemplate] = useState("blank");
415
457
  const [createError, setCreateError] = useState(null);
416
458
  const [creating, setCreating] = useState(false);
459
+ const openCreate = () => {
460
+ setCreateOpen(true);
461
+ setCreateError(null);
462
+ setNewId(tab === "dialogs" ? DIALOG_PREFIX : "");
463
+ setTemplate(tab === "dialogs" ? "dialog" : "blank");
464
+ };
417
465
  const load = useCallback(() => {
418
466
  setListFailed(false);
419
467
  store.list().then(setPages).catch((err) => {
@@ -422,12 +470,17 @@ function PageListView({ store, onEdit, onPreview }) {
422
470
  });
423
471
  }, [store]);
424
472
  useEffect(load, [load]);
473
+ const counts = useMemo(() => {
474
+ const dialogs = pages?.filter((p) => isDialogSpecId(p.id)).length ?? 0;
475
+ return { dialogs, pages: (pages?.length ?? 0) - dialogs };
476
+ }, [pages]);
425
477
  const filtered = useMemo(() => {
426
478
  if (!pages) return null;
479
+ const scoped = pages.filter((p) => isDialogSpecId(p.id) === (tab === "dialogs"));
427
480
  const q = query.trim().toLowerCase();
428
- if (!q) return pages;
429
- return pages.filter((p) => p.id.toLowerCase().includes(q) || (p.title ?? "").toLowerCase().includes(q));
430
- }, [pages, query]);
481
+ if (!q) return scoped;
482
+ return scoped.filter((p) => p.id.toLowerCase().includes(q) || (p.title ?? "").toLowerCase().includes(q));
483
+ }, [pages, query, tab]);
431
484
  const createPage = async () => {
432
485
  if (pages === null) {
433
486
  setCreateError("\uD398\uC774\uC9C0 \uBAA9\uB85D\uC744 \uBD88\uB7EC\uC624\uC9C0 \uBABB\uD574 \uC911\uBCF5 \uC5EC\uBD80\uB97C \uD655\uC778\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uBAA9\uB85D\uC744 \uB2E4\uC2DC \uBD88\uB7EC\uC628 \uB4A4 \uC2DC\uB3C4\uD558\uC138\uC694.");
@@ -498,10 +551,7 @@ function PageListView({ store, onEdit, onPreview }) {
498
551
  /* @__PURE__ */ jsx("h1", { className: "wp-page-head__title", children: "\uD398\uC774\uC9C0" }),
499
552
  /* @__PURE__ */ jsx("div", { className: "wp-page-head__sub", children: "SDUI \uD398\uC774\uC9C0 \uC2A4\uD399\uC744 \uCD94\uAC00\xB7\uC218\uC815\uD569\uB2C8\uB2E4" })
500
553
  ] }),
501
- /* @__PURE__ */ jsx("div", { className: "wp-page-head__actions", children: /* @__PURE__ */ jsx(Button, { variant: "primary", icon: "plus", onClick: () => {
502
- setCreateOpen(true);
503
- setCreateError(null);
504
- }, children: "\uC0C8 \uD398\uC774\uC9C0" }) })
554
+ /* @__PURE__ */ jsx("div", { className: "wp-page-head__actions", children: /* @__PURE__ */ jsx(Button, { variant: "primary", icon: "plus", onClick: openCreate, children: tab === "dialogs" ? "\uC0C8 \uB2E4\uC774\uC5BC\uB85C\uADF8" : "\uC0C8 \uD398\uC774\uC9C0" }) })
505
555
  ] }),
506
556
  error ? /* @__PURE__ */ jsx(
507
557
  Alert,
@@ -515,6 +565,17 @@ function PageListView({ store, onEdit, onPreview }) {
515
565
  caps.limitations.length > 0 ? /* @__PURE__ */ jsx(Alert, { tone: "info", title: `${caps.label} \uC800\uC7A5\uC18C\uB97C \uD3B8\uC9D1 \uC911\uC785\uB2C8\uB2E4`, children: /* @__PURE__ */ jsx("ul", { className: "sdui-editor-limitations", children: caps.limitations.map((limitation) => /* @__PURE__ */ jsx("li", { children: limitation }, limitation)) }) }) : null,
516
566
  /* @__PURE__ */ jsxs(Card, { padding: "none", children: [
517
567
  /* @__PURE__ */ jsxs("div", { className: "sdui-editor-list-toolbar", children: [
568
+ /* @__PURE__ */ jsx(
569
+ Tabs,
570
+ {
571
+ items: [
572
+ { value: "pages", icon: "layout-dashboard", label: "\uD654\uBA74", count: counts.pages },
573
+ { value: "dialogs", icon: "external-link", label: "\uB2E4\uC774\uC5BC\uB85C\uADF8", count: counts.dialogs }
574
+ ],
575
+ value: tab,
576
+ onChange: (next) => setTab(next)
577
+ }
578
+ ),
518
579
  /* @__PURE__ */ jsx(Input, { icon: "search", placeholder: "id \uB610\uB294 \uC81C\uBAA9 \uAC80\uC0C9", value: query, onChange: (e) => setQuery(e.target.value) }),
519
580
  /* @__PURE__ */ jsxs(Badge, { tone: "brand", children: [
520
581
  filtered?.length ?? 0,
@@ -554,9 +615,9 @@ function PageListView({ store, onEdit, onPreview }) {
554
615
  empty: /* @__PURE__ */ jsx(
555
616
  EmptyState,
556
617
  {
557
- title: query ? "\uAC80\uC0C9 \uACB0\uACFC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4" : "\uD398\uC774\uC9C0\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4",
558
- actions: query ? void 0 : /* @__PURE__ */ jsx(Button, { variant: "primary", icon: "plus", onClick: () => setCreateOpen(true), children: "\uCCAB \uD398\uC774\uC9C0 \uB9CC\uB4E4\uAE30" }),
559
- children: query ? "\uB2E4\uB978 \uAC80\uC0C9\uC5B4\uB85C \uC2DC\uB3C4\uD574 \uBCF4\uC138\uC694." : "\uC0C8 \uD398\uC774\uC9C0\uB97C \uB9CC\uB4E4\uC5B4 \uC2DC\uC791\uD558\uC138\uC694."
618
+ title: query ? "\uAC80\uC0C9 \uACB0\uACFC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4" : tab === "dialogs" ? "\uB2E4\uC774\uC5BC\uB85C\uADF8 \uC870\uAC01\uC774 \uC5C6\uC2B5\uB2C8\uB2E4" : "\uD398\uC774\uC9C0\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4",
619
+ actions: query ? void 0 : /* @__PURE__ */ jsx(Button, { variant: "primary", icon: "plus", onClick: openCreate, children: tab === "dialogs" ? "\uCCAB \uB2E4\uC774\uC5BC\uB85C\uADF8 \uB9CC\uB4E4\uAE30" : "\uCCAB \uD398\uC774\uC9C0 \uB9CC\uB4E4\uAE30" }),
620
+ children: query ? "\uB2E4\uB978 \uAC80\uC0C9\uC5B4\uB85C \uC2DC\uB3C4\uD574 \uBCF4\uC138\uC694." : tab === "dialogs" ? `${DIALOG_PREFIX} \uC811\uB450 id\uB85C \uB9CC\uB4E0 \uC2A4\uD399\uC774 \uC774 \uD0ED\uC5D0 \uBAA8\uC785\uB2C8\uB2E4 \u2014 Fragment\uAC00 \uB07C\uC6CC \uC4F0\uB294 \uACF5\uC6A9 \uC870\uAC01\uC785\uB2C8\uB2E4.` : "\uC0C8 \uD398\uC774\uC9C0\uB97C \uB9CC\uB4E4\uC5B4 \uC2DC\uC791\uD558\uC138\uC694."
560
621
  }
561
622
  )
562
623
  }
@@ -565,8 +626,8 @@ function PageListView({ store, onEdit, onPreview }) {
565
626
  createOpen ? /* @__PURE__ */ jsxs(
566
627
  SduiDialog,
567
628
  {
568
- title: "\uC0C8 \uD398\uC774\uC9C0",
569
- subtitle: "id\uB294 URL \uACBD\uB85C\uAC00 \uB429\uB2C8\uB2E4",
629
+ title: tab === "dialogs" ? "\uC0C8 \uB2E4\uC774\uC5BC\uB85C\uADF8 \uC870\uAC01" : "\uC0C8 \uD398\uC774\uC9C0",
630
+ subtitle: tab === "dialogs" ? `${DIALOG_PREFIX} \uC811\uB450\uAC00 \uB2E4\uC774\uC5BC\uB85C\uADF8 \uD0ED\uC758 \uAE30\uC900\uC785\uB2C8\uB2E4` : "id\uB294 URL \uACBD\uB85C\uAC00 \uB429\uB2C8\uB2E4",
570
631
  icon: "plus",
571
632
  onClose: () => setCreateOpen(false),
572
633
  footer: /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -576,23 +637,31 @@ function PageListView({ store, onEdit, onPreview }) {
576
637
  children: [
577
638
  pages === null ? /* @__PURE__ */ jsx(Alert, { tone: "warning", title: "\uC9C0\uAE08\uC740 \uC0C8 \uD398\uC774\uC9C0\uB97C \uB9CC\uB4E4 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4", children: "\uD398\uC774\uC9C0 \uBAA9\uB85D\uC744 \uBD88\uB7EC\uC624\uC9C0 \uBABB\uD574 \uC5B4\uB5A4 id\uAC00 \uC774\uBBF8 \uC4F0\uC774\uB294\uC9C0 \uD655\uC778\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uBAA9\uB85D\uC744 \uBA3C\uC800 \uB2E4\uC2DC \uBD88\uB7EC\uC624\uC138\uC694." }) : null,
578
639
  createError ? /* @__PURE__ */ jsx(Alert, { tone: "danger", children: createError }) : null,
579
- /* @__PURE__ */ jsx(Field, { label: "\uD398\uC774\uC9C0 id", required: true, hint: "\uC608: home, sales/orders", children: /* @__PURE__ */ jsx(
580
- Input,
640
+ /* @__PURE__ */ jsx(
641
+ Field,
581
642
  {
582
- mono: true,
583
- value: newId,
584
- autoFocus: true,
585
- disabled: pages === null,
586
- placeholder: "sales/orders",
587
- onChange: (e) => setNewId(e.target.value),
588
- onKeyDown: (e) => {
589
- if (e.key === "Enter") {
590
- e.preventDefault();
591
- if (pages !== null) void createPage();
643
+ label: "\uD398\uC774\uC9C0 id",
644
+ required: true,
645
+ hint: tab === "dialogs" ? `\uC608: ${DIALOG_PREFIX}asset-edit` : "\uC608: home, sales/orders",
646
+ children: /* @__PURE__ */ jsx(
647
+ Input,
648
+ {
649
+ mono: true,
650
+ value: newId,
651
+ autoFocus: true,
652
+ disabled: pages === null,
653
+ placeholder: tab === "dialogs" ? `${DIALOG_PREFIX}asset-edit` : "sales/orders",
654
+ onChange: (e) => setNewId(e.target.value),
655
+ onKeyDown: (e) => {
656
+ if (e.key === "Enter") {
657
+ e.preventDefault();
658
+ if (pages !== null) void createPage();
659
+ }
660
+ }
592
661
  }
593
- }
662
+ )
594
663
  }
595
- ) }),
664
+ ),
596
665
  /* @__PURE__ */ jsx(Field, { label: "\uD15C\uD50C\uB9BF", children: /* @__PURE__ */ jsx(
597
666
  Select,
598
667
  {
@@ -848,10 +917,122 @@ var editorRegistry = createRegistry({
848
917
  () => import('@wipco/sdui/widgets/TreeView').then((m) => ({ default: m.TreeView }))
849
918
  )
850
919
  });
920
+
921
+ // src/preview/seed.ts
922
+ function isPlainObject(value) {
923
+ return typeof value === "object" && value !== null && !Array.isArray(value);
924
+ }
925
+ function hasDialogInside(node) {
926
+ if (!isPlainObject(node)) return false;
927
+ if (node.component === "Dialog") return true;
928
+ const children = Array.isArray(node.children) ? node.children : [];
929
+ for (const child of children) if (hasDialogInside(child)) return true;
930
+ if (isPlainObject(node.slots)) {
931
+ for (const list of Object.values(node.slots)) {
932
+ if (!Array.isArray(list)) continue;
933
+ for (const child of list) if (hasDialogInside(child)) return true;
934
+ }
935
+ }
936
+ return false;
937
+ }
938
+ function dialogOpenSeeds(spec) {
939
+ const seeds = {};
940
+ if (!isPlainObject(spec) || !Array.isArray(spec.body)) return seeds;
941
+ for (const node of spec.body) {
942
+ if (!isPlainObject(node) || node.component !== "Show") continue;
943
+ const when = isPlainObject(node.props) ? node.props.when : void 0;
944
+ if (!isPlainObject(when) || typeof when.$state !== "string" || when.$state === "") continue;
945
+ if (Object.keys(when).some((key) => key !== "$state" && key !== "default")) continue;
946
+ if (!hasDialogInside(node)) continue;
947
+ seeds[when.$state] = true;
948
+ }
949
+ return seeds;
950
+ }
951
+ function seedSignature(seeds) {
952
+ return JSON.stringify(
953
+ Object.keys(seeds).sort().map((key) => [key, seeds[key]])
954
+ );
955
+ }
956
+ var INITIAL_PREVIEW_SETUP = {
957
+ autoOpen: true,
958
+ manual: [],
959
+ argsOn: false,
960
+ argsText: "{\n}",
961
+ dataArmed: false,
962
+ entityLabels: {}
963
+ };
964
+ function parseSeedValue(text) {
965
+ try {
966
+ return JSON.parse(text);
967
+ } catch {
968
+ return text;
969
+ }
970
+ }
971
+ function seedsOf(setup, autoSeeds) {
972
+ const seeds = setup.autoOpen ? { ...autoSeeds } : {};
973
+ for (const row of setup.manual) {
974
+ const key = row.key.trim();
975
+ if (!key) continue;
976
+ seeds[key] = parseSeedValue(row.text);
977
+ }
978
+ return seeds;
979
+ }
980
+ function argsWrapperPage(specId, argsText) {
981
+ let parsed;
982
+ try {
983
+ parsed = JSON.parse(argsText);
984
+ } catch {
985
+ return null;
986
+ }
987
+ if (!isPlainObject(parsed)) return null;
988
+ const page = {
989
+ version: 1,
990
+ body: [{ component: "Fragment", props: { id: specId, args: parsed } }]
991
+ };
992
+ return { page, args: parsed };
993
+ }
994
+ var STATE_WRITING_ACTIONS = /* @__PURE__ */ new Set([
995
+ "setState",
996
+ "open",
997
+ "close",
998
+ "arrayAppend",
999
+ "arrayRemove",
1000
+ "arrayMove",
1001
+ "arrayUpdate"
1002
+ ]);
1003
+ function collectStateKeys(spec, limit = 200) {
1004
+ const counts = /* @__PURE__ */ new Map();
1005
+ const touch = (key) => counts.set(key, (counts.get(key) ?? 0) + 1);
1006
+ const walk = (value) => {
1007
+ if (Array.isArray(value)) {
1008
+ for (const item of value) walk(item);
1009
+ return;
1010
+ }
1011
+ if (!isPlainObject(value)) return;
1012
+ if (typeof value.$state === "string" && value.$state) touch(value.$state);
1013
+ if (typeof value.action === "string" && STATE_WRITING_ACTIONS.has(value.action) && typeof value.key === "string" && value.key) {
1014
+ touch(value.key);
1015
+ }
1016
+ for (const [name, sub] of Object.entries(value)) {
1017
+ if (name === "$state") continue;
1018
+ walk(sub);
1019
+ }
1020
+ };
1021
+ walk(spec);
1022
+ return [...counts.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).slice(0, limit).map(([key]) => key);
1023
+ }
1024
+ function patternParams(specId) {
1025
+ const params = [];
1026
+ for (const segment of specId.split("/")) {
1027
+ const match = /^\{([A-Za-z0-9_]+)\}$/.exec(segment);
1028
+ if (match) params.push(match[1]);
1029
+ }
1030
+ return params;
1031
+ }
851
1032
  var ROOT_ID = "$root";
852
1033
  var DEFAULT_SLOT = "$children";
853
1034
  var TEXT_CHILD = "#text";
854
- function isPlainObject(v) {
1035
+ function isPlainObject2(v) {
855
1036
  return typeof v === "object" && v !== null && !Array.isArray(v);
856
1037
  }
857
1038
  function mint(seq) {
@@ -861,11 +1042,11 @@ function childFromSpec(spec, seq) {
861
1042
  if (typeof spec === "string") {
862
1043
  return { kind: "text", id: mint(seq), text: spec };
863
1044
  }
864
- if (!isPlainObject(spec) || typeof spec.component !== "string") {
1045
+ if (!isPlainObject2(spec) || typeof spec.component !== "string") {
865
1046
  return { kind: "text", id: mint(seq), text: JSON.stringify(spec) ?? "" };
866
1047
  }
867
1048
  const slots = {};
868
- if (isPlainObject(spec.slots)) {
1049
+ if (isPlainObject2(spec.slots)) {
869
1050
  for (const [name, list] of Object.entries(spec.slots)) {
870
1051
  if (!Array.isArray(list) || list.length === 0) continue;
871
1052
  slots[name] = list.map((c) => childFromSpec(c, seq));
@@ -878,14 +1059,14 @@ function childFromSpec(spec, seq) {
878
1059
  kind: "node",
879
1060
  id: mint(seq),
880
1061
  component: spec.component,
881
- props: isPlainObject(spec.props) ? { ...spec.props } : {},
1062
+ props: isPlainObject2(spec.props) ? { ...spec.props } : {},
882
1063
  slots
883
1064
  };
884
1065
  return typeof spec.key === "string" ? { ...node, key: spec.key } : node;
885
1066
  }
886
1067
  function fromPage(page) {
887
1068
  const seq = { next: 1 };
888
- if (!isPlainObject(page)) {
1069
+ if (!isPlainObject2(page)) {
889
1070
  return { meta: { version: 1 }, body: [], nextId: seq.next };
890
1071
  }
891
1072
  const { body, ...rest } = page;
@@ -1841,125 +2022,381 @@ var SPEC_PAIR_FREQ = {
1841
2022
  "Show>LiveClock": 1,
1842
2023
  "Strip>Row": 1
1843
2024
  };
1844
- var HINT_PROPS = ["title", "label", "header", "text", "value", "placeholder", "name"];
1845
- function isPlainObject2(v) {
1846
- return typeof v === "object" && v !== null && !Array.isArray(v);
1847
- }
1848
- function summarize(value) {
1849
- if (typeof value === "string") return value.trim() || void 0;
2025
+
2026
+ // src/builder/canvas/project.ts
2027
+ var EDIT_MARK = "$EditMark";
2028
+ var EDIT_FOREACH = "$EditForEach";
2029
+ var MARK_ATTR = "data-sdui-id";
2030
+ var LOGICAL = /* @__PURE__ */ new Set(["Show", "Can", "ForEach"]);
2031
+ function bindingLabel(value) {
2032
+ if (value === void 0 || value === null) return void 0;
2033
+ if (typeof value === "string") return value || void 0;
1850
2034
  if (typeof value === "number" || typeof value === "boolean") return String(value);
1851
- if (isPlainObject2(value)) {
1852
- if (typeof value.$state === "string") return `{${value.$state}}`;
1853
- if (value.$item === true) return "{\uD589}";
1854
- if (typeof value.$item === "string") return `{\uD589.${value.$item}}`;
1855
- if (value.$index === true) return "{\uBC88\uD638}";
1856
- if (isPlainObject2(value.$calc) && typeof value.$calc.op === "string") return `{\uACC4\uC0B0:${value.$calc.op}}`;
2035
+ if (Array.isArray(value)) return void 0;
2036
+ const bag = value;
2037
+ if (typeof bag.$state === "string") return `{${bag.$state}}`;
2038
+ if (bag.$item === true) return "{\uD589}";
2039
+ if (typeof bag.$item === "string") return `{\uD589.${bag.$item}}`;
2040
+ if (bag.$index === true) return "{\uBC88\uD638}";
2041
+ const calc = bag.$calc;
2042
+ if (calc !== null && typeof calc === "object" && !Array.isArray(calc)) {
2043
+ const op = calc.op;
2044
+ if (typeof op === "string") return `{\uACC4\uC0B0:${op}}`;
1857
2045
  }
1858
2046
  return void 0;
1859
2047
  }
1860
- function describeChild(child, catalog) {
1861
- if (child.kind === "text") {
1862
- const text = child.text.trim();
1863
- return { label: "\uD14D\uC2A4\uD2B8", hint: text || "(\uBE48 \uBB38\uC790\uC5F4)", known: true };
1864
- }
1865
- const meta = getComponentMeta(child.component, catalog);
1866
- let hint;
1867
- for (const name of HINT_PROPS) {
1868
- if (!(name in child.props)) continue;
1869
- hint = summarize(child.props[name]);
1870
- if (hint) break;
1871
- }
1872
- if (!hint) {
1873
- const first = child.slots[DEFAULT_SLOT]?.[0];
1874
- if (first?.kind === "text") hint = first.text.trim() || void 0;
2048
+ function chromeHintOf(node) {
2049
+ switch (node.component) {
2050
+ case "Show":
2051
+ return bindingLabel(node.props.when);
2052
+ case "Can": {
2053
+ const entity = typeof node.props.entity === "string" ? node.props.entity : "?";
2054
+ const action = typeof node.props.action === "string" ? node.props.action : "read";
2055
+ return `${entity} \xB7 ${action}`;
2056
+ }
2057
+ case "ForEach":
2058
+ return bindingLabel(node.props.source);
2059
+ default:
2060
+ return void 0;
1875
2061
  }
1876
- return {
1877
- label: meta?.label ?? child.component,
1878
- ...hint ? { hint } : {},
1879
- ...meta ? { icon: meta.icon } : {},
1880
- known: meta !== void 0
1881
- };
1882
2062
  }
1883
- function breadcrumbOf(doc, id, catalog) {
1884
- const trail = [...ancestorIdsOf(doc, id), id];
1885
- const out = [];
1886
- for (const step of trail) {
1887
- const child = getChild(doc, step);
1888
- if (child) out.push({ id: step, label: describeChild(child, catalog).label });
1889
- }
1890
- return out;
2063
+ function chromeOf(node) {
2064
+ if (LOGICAL.has(node.component)) return "logic";
2065
+ if (node.component === "Dialog") return "modal";
2066
+ return void 0;
1891
2067
  }
1892
- function slotsOf(node, catalog) {
1893
- const meta = getComponentMeta(node.component, catalog);
1894
- const out = [];
1895
- const seenNames = /* @__PURE__ */ new Set();
1896
- const push = (slot, d) => {
1897
- if (seenNames.has(slot)) return;
1898
- seenNames.add(slot);
1899
- out.push({
1900
- slot,
1901
- count: node.slots[slot]?.length ?? 0,
1902
- seen: slotUsage(node.component, slot),
1903
- ...d
1904
- });
2068
+ function mark(child, parent, slot, index) {
2069
+ const props = {
2070
+ nid: child.id,
2071
+ parent,
2072
+ slot,
2073
+ index
1905
2074
  };
1906
- const acceptsDefault = meta ? meta.children.accepts : true;
1907
- if (acceptsDefault || (node.slots[DEFAULT_SLOT]?.length ?? 0) > 0) {
1908
- push(DEFAULT_SLOT, {
1909
- label: meta?.children.label ?? "\uC790\uC2DD",
1910
- known: meta !== void 0,
1911
- accepts: acceptsDefault
1912
- });
1913
- }
1914
- if (meta) {
1915
- for (const name of slotNamesOf(meta)) {
1916
- const declared = (meta.slots ?? []).find((s) => s.name === name);
1917
- const asProp = meta.props.find((p) => p.name === name && p.slot === true);
1918
- push(name, {
1919
- label: declared?.label ?? asProp?.label ?? name,
1920
- ...declared?.description ?? asProp?.description ? { description: declared?.description ?? asProp?.description } : {},
1921
- known: true,
1922
- accepts: true
1923
- });
1924
- }
1925
- }
1926
- for (const name of Object.keys(node.slots)) {
1927
- push(name, { label: name, known: false, accepts: true });
2075
+ let inner;
2076
+ if (child.kind === "text") {
2077
+ props.text = true;
2078
+ inner = child.text;
2079
+ } else {
2080
+ props.name = child.component;
2081
+ const chrome = chromeOf(child);
2082
+ if (chrome) props.chrome = chrome;
2083
+ const hint = chromeHintOf(child);
2084
+ if (hint) props.hint = hint;
2085
+ inner = toCanvasNode(child);
1928
2086
  }
1929
- return out.sort((a, b) => {
1930
- if (a.slot === DEFAULT_SLOT) return -1;
1931
- if (b.slot === DEFAULT_SLOT) return 1;
1932
- return b.seen - a.seen || a.slot.localeCompare(b.slot);
1933
- });
2087
+ return { component: EDIT_MARK, key: child.id, props, children: [inner] };
1934
2088
  }
1935
- var SLOT_USAGE = (() => {
1936
- const totals = /* @__PURE__ */ new Map();
1937
- for (const [key, count] of Object.entries(SPEC_PAIR_FREQ)) {
1938
- const arrow = key.indexOf(">");
1939
- const hash = key.indexOf("#");
1940
- if (hash < 0 || hash > arrow) continue;
1941
- const head = key.slice(0, arrow);
1942
- totals.set(head, (totals.get(head) ?? 0) + count);
2089
+ function markList(list, parent, slot) {
2090
+ if (!list || list.length === 0) return void 0;
2091
+ return list.map((child, index) => mark(child, parent, slot, index));
2092
+ }
2093
+ function toCanvasNode(node) {
2094
+ const out = {
2095
+ // ForEach만 편집용 구현으로 돌린다(위 주석 참고). 나머지는 이름 그대로 —
2096
+ // 레지스트리 쪽에서 갈아끼우므로 여기서 손댈 일이 없다.
2097
+ component: node.component === "ForEach" ? EDIT_FOREACH : node.component
2098
+ };
2099
+ if (Object.keys(node.props).length > 0) out.props = { ...node.props };
2100
+ let slots;
2101
+ for (const [name, list] of Object.entries(node.slots)) {
2102
+ if (name === DEFAULT_SLOT) continue;
2103
+ const marked = markList(list, node.id, name);
2104
+ if (marked) (slots ??= {})[name] = marked;
1943
2105
  }
1944
- return totals;
1945
- })();
1946
- function slotUsage(component, slot) {
1947
- if (slot === DEFAULT_SLOT) return 0;
1948
- return SLOT_USAGE.get(`${component}#${slot}`) ?? 0;
2106
+ if (slots) out.slots = slots;
2107
+ const own = markList(node.slots[DEFAULT_SLOT], node.id, DEFAULT_SLOT);
2108
+ if (own) out.children = own;
2109
+ return out;
1949
2110
  }
1950
- function flattenTree(doc, options = {}) {
1951
- const { expanded, showSlots = true, catalog } = options;
1952
- const isOpen = (key) => expanded === void 0 || expanded.has(key);
1953
- const rows = [];
1954
- const emitList = (list, parent, slot, depth) => {
1955
- list.forEach((child, index) => {
1956
- if (child.kind === "text") {
1957
- const d2 = describeChild(child, catalog);
1958
- rows.push({
1959
- kind: "text",
1960
- key: child.id,
1961
- id: child.id,
1962
- depth,
2111
+ function toCanvasPage(doc) {
2112
+ const { urlState: _urlState, subscribe: _subscribe, ...meta } = doc.meta;
2113
+ return {
2114
+ ...meta,
2115
+ body: doc.body.map((child, index) => mark(child, ROOT_ID, DEFAULT_SLOT, index))
2116
+ };
2117
+ }
2118
+ var CHROME_LABEL = {
2119
+ Show: "\uC870\uAC74\uBD80",
2120
+ Can: "\uAD8C\uD55C",
2121
+ ForEach: "\uBC18\uBCF5",
2122
+ Dialog: "\uBAA8\uB2EC"
2123
+ };
2124
+ function EditMark({ nid, parent, slot, index, name, text, chrome, hint, children }) {
2125
+ const common = {
2126
+ "data-sdui-id": nid,
2127
+ "data-sdui-parent": parent,
2128
+ "data-sdui-slot": slot,
2129
+ "data-sdui-index": index,
2130
+ ...name ? { "data-sdui-name": name } : {},
2131
+ ...text ? { "data-sdui-text": "true" } : {}
2132
+ };
2133
+ if (chrome && name) {
2134
+ return /* @__PURE__ */ jsxs("div", { className: `sdui-editor-cv-chrome sdui-editor-cv-chrome--${chrome}`, ...common, children: [
2135
+ /* @__PURE__ */ jsxs("span", { className: "sdui-editor-cv-chrome__tag", children: [
2136
+ CHROME_LABEL[name] ?? name,
2137
+ hint ? /* @__PURE__ */ jsx("em", { className: "sdui-editor-cv-chrome__hint", children: hint }) : null
2138
+ ] }),
2139
+ children
2140
+ ] });
2141
+ }
2142
+ return /* @__PURE__ */ jsx("span", { className: "sdui-editor-cv-mark", ...common, children });
2143
+ }
2144
+ function EditShow({ children }) {
2145
+ return /* @__PURE__ */ jsx(Fragment, { children });
2146
+ }
2147
+ function EditCan({
2148
+ fallback,
2149
+ children
2150
+ }) {
2151
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
2152
+ children,
2153
+ fallback ? /* @__PURE__ */ jsxs("div", { className: "sdui-editor-cv-branch", children: [
2154
+ /* @__PURE__ */ jsx("span", { className: "sdui-editor-cv-branch__tag", children: "\uCC28\uB2E8 \uC2DC" }),
2155
+ fallback
2156
+ ] }) : null
2157
+ ] });
2158
+ }
2159
+ function EditForEach({
2160
+ source,
2161
+ empty,
2162
+ children
2163
+ }) {
2164
+ const rows = Array.isArray(source) ? source.length : null;
2165
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
2166
+ /* @__PURE__ */ jsxs("div", { className: "sdui-editor-cv-branch", children: [
2167
+ /* @__PURE__ */ jsxs("span", { className: "sdui-editor-cv-branch__tag", children: [
2168
+ "\uD589 1\uAC1C \uBBF8\uB9AC\uBCF4\uAE30",
2169
+ rows === null ? "" : ` \xB7 \uC9C0\uAE08 ${rows}\uAC74`
2170
+ ] }),
2171
+ children
2172
+ ] }),
2173
+ empty ? /* @__PURE__ */ jsxs("div", { className: "sdui-editor-cv-branch", children: [
2174
+ /* @__PURE__ */ jsx("span", { className: "sdui-editor-cv-branch__tag", children: "\uBE44\uC5C8\uC744 \uB54C" }),
2175
+ empty
2176
+ ] }) : null
2177
+ ] });
2178
+ }
2179
+ function createEditRegistry(base = defaultRegistry) {
2180
+ return {
2181
+ ...base,
2182
+ Show: EditShow,
2183
+ Can: EditCan,
2184
+ Dialog,
2185
+ [EDIT_MARK]: EditMark,
2186
+ [EDIT_FOREACH]: EditForEach
2187
+ };
2188
+ }
2189
+ var PLACEHOLDER_SVG = [
2190
+ '<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64">',
2191
+ '<rect width="64" height="64" fill="#e2e8f0"/>',
2192
+ '<path d="M0 0h16v16H0zM32 0h16v16H32zM16 16h16v16H16zM48 16h16v16H48z',
2193
+ 'M0 32h16v16H0zM32 32h16v16H32zM16 48h16v16H16zM48 48h16v16H48z" fill="#cbd5e1"/>',
2194
+ '<circle cx="24" cy="24" r="6" fill="#94a3b8"/>',
2195
+ '<path d="M8 52l16-18 10 11 8-8 14 15z" fill="#94a3b8"/>',
2196
+ "</svg>"
2197
+ ].join("");
2198
+ var CANVAS_ASSET_PLACEHOLDER = `data:image/svg+xml,${encodeURIComponent(PLACEHOLDER_SVG)}`;
2199
+ function isInertUrl(url) {
2200
+ const value = url.trim().toLowerCase();
2201
+ return value.startsWith("#") || value.startsWith("data:") || value.startsWith("blob:");
2202
+ }
2203
+ function resolveCanvasAsset(url, ctx) {
2204
+ if (isInertUrl(url)) return url;
2205
+ return ctx.kind === "attribute" ? CANVAS_ASSET_PLACEHOLDER : null;
2206
+ }
2207
+ function createCanvasAdapter(onBlocked) {
2208
+ return {
2209
+ navigate: (to) => onBlocked(`navigate \u2192 ${to}`),
2210
+ // 요청은 보내지 않되 성공(null)으로 응답한다 — onSuccess 체인이 어떻게 흐르는지는
2211
+ // 보여 주면서 서버는 건드리지 않는다.
2212
+ fetch: async (path, init) => {
2213
+ onBlocked(`${init.method} ${path}`);
2214
+ return null;
2215
+ },
2216
+ // download는 응답을 파일로 저장한다. 실패로 끊어 빈 파일이 내려가는 것까지 막는다.
2217
+ rawFetch: async (path) => {
2218
+ onBlocked(`download ${path}`);
2219
+ throw new Error("\uCE94\uBC84\uC2A4\uC5D0\uC11C\uB294 \uC2E4\uD589\uB418\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4");
2220
+ },
2221
+ invalidate: (prefix) => onBlocked(`invalidate ${prefix}`),
2222
+ refresh: () => onBlocked("refresh"),
2223
+ // 편집 중 상태가 호스트 앱의 URL을 덮어쓰지 않게 한다(syncUrlState={false}와 이중 방어).
2224
+ replaceQuery: () => {
2225
+ },
2226
+ // 스펙 최상위 `subscribe`가 여는 실시간 채널. **지금 이걸 비워 두면 안전한 것은 우연이다** —
2227
+ // `defaultAdapter`에 아직 구현이 없어서일 뿐이고, 구현이 생기는 순간 캔버스가 조용히
2228
+ // EventSource를 연다. 실제로 vehicle/logs 스펙이 최상위 subscribe를 갖고 있다.
2229
+ // 해제 함수를 돌려주는 계약이라 no-op도 함수를 반환해야 한다.
2230
+ subscribe: (channels) => {
2231
+ onBlocked(`subscribe ${channels.join(", ")}`);
2232
+ return () => {
2233
+ };
2234
+ },
2235
+ // 위의 다섯과 성질이 다르다. 저 다섯은 어댑터가 **호출을 소유**하므로 비워 두면
2236
+ // 아무 일도 안 일어나지만, `<img src>`와 `background-image: url(…)`은 요청을 내는
2237
+ // 주체가 브라우저라 어댑터가 그 자리에 없다. 실제로 캔버스 클릭 차단과 위 다섯 개를
2238
+ // 다 두고도 아바타·라이트박스·style 프롭으로 요청 5건이 세션 쿠키를 달고 나갔다.
2239
+ resolveAsset: resolveCanvasAsset,
2240
+ // 위의 여섯과 또 성질이 다르다. 저것들은 **이 어댑터를 거쳐 나가는** 경로라 여기서
2241
+ // 끊을 수 있지만, 소비 앱의 목록 위젯처럼 **자기 데이터 클라이언트로 직접** 조회하는
2242
+ // 컴포넌트는 어댑터를 아예 지나가지 않는다(ERP의 RemoteSearchResults가 그렇다 —
2243
+ // TanStack Query 모듈 싱글턴이라 끼어들 자리가 없다). 그런 컴포넌트가 스스로
2244
+ // 물러설 수 있게 "여기는 진짜 화면이 아니다"만 알려 준다.
2245
+ //
2246
+ // 값 교체가 아니라 **선언**이라는 점이 중요하다. `fetch`처럼 빈 응답을 돌려주는 것으로
2247
+ // 대신하려 했다가 버렸다 — 그 빈 응답이 소비 앱의 공유 캐시에 실려, 편집기를 닫고
2248
+ // 진짜 페이지를 열면 오염된 목록이 나온다. 요청은 **시작되기 전에** 막혀야 한다.
2249
+ sandbox: true
2250
+ };
2251
+ }
2252
+
2253
+ // src/preview/preview-adapter.ts
2254
+ function createDataPreviewAdapter(previewFetch, onBlocked) {
2255
+ const sealed = createCanvasAdapter(onBlocked);
2256
+ return {
2257
+ ...sealed,
2258
+ fetch: async (path, init) => {
2259
+ if (init.method !== "GET") {
2260
+ onBlocked(`${init.method} ${path}`);
2261
+ return null;
2262
+ }
2263
+ return previewFetch(path, { method: "GET", signal: init.signal });
2264
+ },
2265
+ resolveAsset: (url, ctx) => {
2266
+ const value = url.trim();
2267
+ if (value.startsWith("/") && !value.startsWith("//")) return url;
2268
+ return sealed.resolveAsset ? sealed.resolveAsset(url, ctx) : null;
2269
+ }
2270
+ };
2271
+ }
2272
+ function resolvePreviewAdapter({
2273
+ armed,
2274
+ previewFetch,
2275
+ onBlocked,
2276
+ useSpec
2277
+ }) {
2278
+ const base = armed && previewFetch ? createDataPreviewAdapter(previewFetch, onBlocked) : createCanvasAdapter(onBlocked);
2279
+ return useSpec ? { ...base, useSpec } : base;
2280
+ }
2281
+ var HINT_PROPS = ["title", "label", "header", "text", "value", "placeholder", "name"];
2282
+ function isPlainObject3(v) {
2283
+ return typeof v === "object" && v !== null && !Array.isArray(v);
2284
+ }
2285
+ function summarize(value) {
2286
+ if (typeof value === "string") return value.trim() || void 0;
2287
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
2288
+ if (isPlainObject3(value)) {
2289
+ if (typeof value.$state === "string") return `{${value.$state}}`;
2290
+ if (value.$item === true) return "{\uD589}";
2291
+ if (typeof value.$item === "string") return `{\uD589.${value.$item}}`;
2292
+ if (value.$index === true) return "{\uBC88\uD638}";
2293
+ if (isPlainObject3(value.$calc) && typeof value.$calc.op === "string") return `{\uACC4\uC0B0:${value.$calc.op}}`;
2294
+ }
2295
+ return void 0;
2296
+ }
2297
+ function describeChild(child, catalog) {
2298
+ if (child.kind === "text") {
2299
+ const text = child.text.trim();
2300
+ return { label: "\uD14D\uC2A4\uD2B8", hint: text || "(\uBE48 \uBB38\uC790\uC5F4)", known: true };
2301
+ }
2302
+ const meta = getComponentMeta(child.component, catalog);
2303
+ let hint;
2304
+ for (const name of HINT_PROPS) {
2305
+ if (!(name in child.props)) continue;
2306
+ hint = summarize(child.props[name]);
2307
+ if (hint) break;
2308
+ }
2309
+ if (!hint) {
2310
+ const first = child.slots[DEFAULT_SLOT]?.[0];
2311
+ if (first?.kind === "text") hint = first.text.trim() || void 0;
2312
+ }
2313
+ return {
2314
+ label: meta?.label ?? child.component,
2315
+ ...hint ? { hint } : {},
2316
+ ...meta ? { icon: meta.icon } : {},
2317
+ known: meta !== void 0
2318
+ };
2319
+ }
2320
+ function breadcrumbOf(doc, id, catalog) {
2321
+ const trail = [...ancestorIdsOf(doc, id), id];
2322
+ const out = [];
2323
+ for (const step of trail) {
2324
+ const child = getChild(doc, step);
2325
+ if (child) out.push({ id: step, label: describeChild(child, catalog).label });
2326
+ }
2327
+ return out;
2328
+ }
2329
+ function slotsOf(node, catalog) {
2330
+ const meta = getComponentMeta(node.component, catalog);
2331
+ const out = [];
2332
+ const seenNames = /* @__PURE__ */ new Set();
2333
+ const push = (slot, d) => {
2334
+ if (seenNames.has(slot)) return;
2335
+ seenNames.add(slot);
2336
+ out.push({
2337
+ slot,
2338
+ count: node.slots[slot]?.length ?? 0,
2339
+ seen: slotUsage(node.component, slot),
2340
+ ...d
2341
+ });
2342
+ };
2343
+ const acceptsDefault = meta ? meta.children.accepts : true;
2344
+ if (acceptsDefault || (node.slots[DEFAULT_SLOT]?.length ?? 0) > 0) {
2345
+ push(DEFAULT_SLOT, {
2346
+ label: meta?.children.label ?? "\uC790\uC2DD",
2347
+ known: meta !== void 0,
2348
+ accepts: acceptsDefault
2349
+ });
2350
+ }
2351
+ if (meta) {
2352
+ for (const name of slotNamesOf(meta)) {
2353
+ const declared = (meta.slots ?? []).find((s) => s.name === name);
2354
+ const asProp = meta.props.find((p) => p.name === name && p.slot === true);
2355
+ push(name, {
2356
+ label: declared?.label ?? asProp?.label ?? name,
2357
+ ...declared?.description ?? asProp?.description ? { description: declared?.description ?? asProp?.description } : {},
2358
+ known: true,
2359
+ accepts: true
2360
+ });
2361
+ }
2362
+ }
2363
+ for (const name of Object.keys(node.slots)) {
2364
+ push(name, { label: name, known: false, accepts: true });
2365
+ }
2366
+ return out.sort((a, b) => {
2367
+ if (a.slot === DEFAULT_SLOT) return -1;
2368
+ if (b.slot === DEFAULT_SLOT) return 1;
2369
+ return b.seen - a.seen || a.slot.localeCompare(b.slot);
2370
+ });
2371
+ }
2372
+ var SLOT_USAGE = (() => {
2373
+ const totals = /* @__PURE__ */ new Map();
2374
+ for (const [key, count] of Object.entries(SPEC_PAIR_FREQ)) {
2375
+ const arrow = key.indexOf(">");
2376
+ const hash = key.indexOf("#");
2377
+ if (hash < 0 || hash > arrow) continue;
2378
+ const head = key.slice(0, arrow);
2379
+ totals.set(head, (totals.get(head) ?? 0) + count);
2380
+ }
2381
+ return totals;
2382
+ })();
2383
+ function slotUsage(component, slot) {
2384
+ if (slot === DEFAULT_SLOT) return 0;
2385
+ return SLOT_USAGE.get(`${component}#${slot}`) ?? 0;
2386
+ }
2387
+ function flattenTree(doc, options = {}) {
2388
+ const { expanded, showSlots = true, catalog } = options;
2389
+ const isOpen = (key) => expanded === void 0 || expanded.has(key);
2390
+ const rows = [];
2391
+ const emitList = (list, parent, slot, depth) => {
2392
+ list.forEach((child, index) => {
2393
+ if (child.kind === "text") {
2394
+ const d2 = describeChild(child, catalog);
2395
+ rows.push({
2396
+ kind: "text",
2397
+ key: child.id,
2398
+ id: child.id,
2399
+ depth,
1963
2400
  parent,
1964
2401
  slot,
1965
2402
  index,
@@ -2058,7 +2495,7 @@ function dropTargetsAround(doc, hoveredId, child, catalog) {
2058
2495
  }
2059
2496
  return out;
2060
2497
  }
2061
- var STATE_WRITING_ACTIONS = /* @__PURE__ */ new Set([
2498
+ var STATE_WRITING_ACTIONS2 = /* @__PURE__ */ new Set([
2062
2499
  "setState",
2063
2500
  "open",
2064
2501
  "close",
@@ -2091,12 +2528,12 @@ function buildStateKeyIndex(doc) {
2091
2528
  for (const item of value) walkValue(item, node);
2092
2529
  return;
2093
2530
  }
2094
- if (!isPlainObject2(value)) return;
2531
+ if (!isPlainObject3(value)) return;
2095
2532
  if (typeof value.$state === "string" && value.$state) {
2096
2533
  const draft = touch(value.$state, "default" in value ? "default" : "read", node);
2097
2534
  draft.reads++;
2098
2535
  }
2099
- if (typeof value.action === "string" && STATE_WRITING_ACTIONS.has(value.action)) {
2536
+ if (typeof value.action === "string" && STATE_WRITING_ACTIONS2.has(value.action)) {
2100
2537
  if (typeof value.key === "string" && value.key) {
2101
2538
  const draft = touch(value.key, "setState", node);
2102
2539
  draft.writes++;
@@ -2114,7 +2551,7 @@ function buildStateKeyIndex(doc) {
2114
2551
  const fields = /* @__PURE__ */ new Set();
2115
2552
  for (const source of ["initial", "rules", "labels", "initialFromUrl"]) {
2116
2553
  const bag = child.props[source];
2117
- if (!isPlainObject2(bag)) continue;
2554
+ if (!isPlainObject3(bag)) continue;
2118
2555
  for (const field of Object.keys(bag)) fields.add(field);
2119
2556
  }
2120
2557
  for (const field of fields) touch(`${id}.${field}`, "form", child.id);
@@ -2157,297 +2594,70 @@ function buildKeyTree(entries) {
2157
2594
  }
2158
2595
  }
2159
2596
  const finish = (bucket, declaredAbove) => {
2160
- const entry = entries.get(bucket.key);
2161
- const declaredHere = declaredAbove || (entry?.declared ?? false);
2162
- return {
2163
- segment: bucket.segment,
2164
- key: bucket.key,
2165
- ...entry ? { entry } : {},
2166
- derived: declaredAbove,
2167
- children: [...bucket.children.values()].map((child) => finish(child, declaredHere)).sort((a, b) => a.segment.localeCompare(b.segment, "ko"))
2168
- };
2169
- };
2170
- return [...roots.values()].map((bucket) => finish(bucket, false)).sort((a, b) => a.segment.localeCompare(b.segment, "ko"));
2171
- }
2172
- function stateKeyStatus(index, key) {
2173
- const entry = index.entries.get(key);
2174
- if (entry?.declared) return "declared";
2175
- const segments = key.split(".");
2176
- for (let cut = segments.length - 1; cut >= 1; cut--) {
2177
- const head = segments.slice(0, cut).join(".");
2178
- if (index.entries.get(head)?.declared) return "derived";
2179
- }
2180
- return entry ? "read" : "unknown";
2181
- }
2182
- function suggestStateKeys(index, query = "", limit = 20) {
2183
- const needle = query.trim().toLowerCase();
2184
- const matched = [...index.entries.values()].filter(
2185
- (entry) => needle === "" || entry.key.toLowerCase().includes(needle)
2186
- );
2187
- return matched.sort((a, b) => {
2188
- if (a.declared !== b.declared) return a.declared ? -1 : 1;
2189
- if (needle) {
2190
- const ap = a.key.toLowerCase().startsWith(needle);
2191
- const bp = b.key.toLowerCase().startsWith(needle);
2192
- if (ap !== bp) return ap ? -1 : 1;
2193
- }
2194
- return b.reads + b.writes - (a.reads + a.writes) || a.key.localeCompare(b.key);
2195
- }).slice(0, limit);
2196
- }
2197
-
2198
- // src/builder/canvas/dnd.ts
2199
- var DRAG_MIME = "application/x-wipco-sdui-builder";
2200
- var payload = null;
2201
- function beginDrag(transfer, next) {
2202
- payload = next;
2203
- if (!transfer) return;
2204
- transfer.setData(DRAG_MIME, next.component);
2205
- transfer.setData("text/plain", next.label);
2206
- transfer.effectAllowed = next.kind === "insert" ? "copy" : "move";
2207
- }
2208
- function currentDrag() {
2209
- return payload;
2210
- }
2211
- function endDrag() {
2212
- payload = null;
2213
- }
2214
- function isBuilderDrag(transfer) {
2215
- if (payload !== null) return true;
2216
- return transfer ? Array.from(transfer.types).includes(DRAG_MIME) : false;
2217
- }
2218
- function insertDragOf(spec, label) {
2219
- return {
2220
- kind: "insert",
2221
- component: typeof spec === "string" ? TEXT_CHILD : spec.component,
2222
- spec,
2223
- label
2224
- };
2225
- }
2226
-
2227
- // src/builder/canvas/project.ts
2228
- var EDIT_MARK = "$EditMark";
2229
- var EDIT_FOREACH = "$EditForEach";
2230
- var MARK_ATTR = "data-sdui-id";
2231
- var LOGICAL = /* @__PURE__ */ new Set(["Show", "Can", "ForEach"]);
2232
- function bindingLabel(value) {
2233
- if (value === void 0 || value === null) return void 0;
2234
- if (typeof value === "string") return value || void 0;
2235
- if (typeof value === "number" || typeof value === "boolean") return String(value);
2236
- if (Array.isArray(value)) return void 0;
2237
- const bag = value;
2238
- if (typeof bag.$state === "string") return `{${bag.$state}}`;
2239
- if (bag.$item === true) return "{\uD589}";
2240
- if (typeof bag.$item === "string") return `{\uD589.${bag.$item}}`;
2241
- if (bag.$index === true) return "{\uBC88\uD638}";
2242
- const calc = bag.$calc;
2243
- if (calc !== null && typeof calc === "object" && !Array.isArray(calc)) {
2244
- const op = calc.op;
2245
- if (typeof op === "string") return `{\uACC4\uC0B0:${op}}`;
2246
- }
2247
- return void 0;
2248
- }
2249
- function chromeHintOf(node) {
2250
- switch (node.component) {
2251
- case "Show":
2252
- return bindingLabel(node.props.when);
2253
- case "Can": {
2254
- const entity = typeof node.props.entity === "string" ? node.props.entity : "?";
2255
- const action = typeof node.props.action === "string" ? node.props.action : "read";
2256
- return `${entity} \xB7 ${action}`;
2257
- }
2258
- case "ForEach":
2259
- return bindingLabel(node.props.source);
2260
- default:
2261
- return void 0;
2262
- }
2263
- }
2264
- function chromeOf(node) {
2265
- if (LOGICAL.has(node.component)) return "logic";
2266
- if (node.component === "Dialog") return "modal";
2267
- return void 0;
2268
- }
2269
- function mark(child, parent, slot, index) {
2270
- const props = {
2271
- nid: child.id,
2272
- parent,
2273
- slot,
2274
- index
2275
- };
2276
- let inner;
2277
- if (child.kind === "text") {
2278
- props.text = true;
2279
- inner = child.text;
2280
- } else {
2281
- props.name = child.component;
2282
- const chrome = chromeOf(child);
2283
- if (chrome) props.chrome = chrome;
2284
- const hint = chromeHintOf(child);
2285
- if (hint) props.hint = hint;
2286
- inner = toCanvasNode(child);
2287
- }
2288
- return { component: EDIT_MARK, key: child.id, props, children: [inner] };
2289
- }
2290
- function markList(list, parent, slot) {
2291
- if (!list || list.length === 0) return void 0;
2292
- return list.map((child, index) => mark(child, parent, slot, index));
2293
- }
2294
- function toCanvasNode(node) {
2295
- const out = {
2296
- // ForEach만 편집용 구현으로 돌린다(위 주석 참고). 나머지는 이름 그대로 —
2297
- // 레지스트리 쪽에서 갈아끼우므로 여기서 손댈 일이 없다.
2298
- component: node.component === "ForEach" ? EDIT_FOREACH : node.component
2299
- };
2300
- if (Object.keys(node.props).length > 0) out.props = { ...node.props };
2301
- let slots;
2302
- for (const [name, list] of Object.entries(node.slots)) {
2303
- if (name === DEFAULT_SLOT) continue;
2304
- const marked = markList(list, node.id, name);
2305
- if (marked) (slots ??= {})[name] = marked;
2306
- }
2307
- if (slots) out.slots = slots;
2308
- const own = markList(node.slots[DEFAULT_SLOT], node.id, DEFAULT_SLOT);
2309
- if (own) out.children = own;
2310
- return out;
2311
- }
2312
- function toCanvasPage(doc) {
2313
- const { urlState: _urlState, subscribe: _subscribe, ...meta } = doc.meta;
2314
- return {
2315
- ...meta,
2316
- body: doc.body.map((child, index) => mark(child, ROOT_ID, DEFAULT_SLOT, index))
2597
+ const entry = entries.get(bucket.key);
2598
+ const declaredHere = declaredAbove || (entry?.declared ?? false);
2599
+ return {
2600
+ segment: bucket.segment,
2601
+ key: bucket.key,
2602
+ ...entry ? { entry } : {},
2603
+ derived: declaredAbove,
2604
+ children: [...bucket.children.values()].map((child) => finish(child, declaredHere)).sort((a, b) => a.segment.localeCompare(b.segment, "ko"))
2605
+ };
2317
2606
  };
2607
+ return [...roots.values()].map((bucket) => finish(bucket, false)).sort((a, b) => a.segment.localeCompare(b.segment, "ko"));
2318
2608
  }
2319
- var CHROME_LABEL = {
2320
- Show: "\uC870\uAC74\uBD80",
2321
- Can: "\uAD8C\uD55C",
2322
- ForEach: "\uBC18\uBCF5",
2323
- Dialog: "\uBAA8\uB2EC"
2324
- };
2325
- function EditMark({ nid, parent, slot, index, name, text, chrome, hint, children }) {
2326
- const common = {
2327
- "data-sdui-id": nid,
2328
- "data-sdui-parent": parent,
2329
- "data-sdui-slot": slot,
2330
- "data-sdui-index": index,
2331
- ...name ? { "data-sdui-name": name } : {},
2332
- ...text ? { "data-sdui-text": "true" } : {}
2333
- };
2334
- if (chrome && name) {
2335
- return /* @__PURE__ */ jsxs("div", { className: `sdui-editor-cv-chrome sdui-editor-cv-chrome--${chrome}`, ...common, children: [
2336
- /* @__PURE__ */ jsxs("span", { className: "sdui-editor-cv-chrome__tag", children: [
2337
- CHROME_LABEL[name] ?? name,
2338
- hint ? /* @__PURE__ */ jsx("em", { className: "sdui-editor-cv-chrome__hint", children: hint }) : null
2339
- ] }),
2340
- children
2341
- ] });
2609
+ function stateKeyStatus(index, key) {
2610
+ const entry = index.entries.get(key);
2611
+ if (entry?.declared) return "declared";
2612
+ const segments = key.split(".");
2613
+ for (let cut = segments.length - 1; cut >= 1; cut--) {
2614
+ const head = segments.slice(0, cut).join(".");
2615
+ if (index.entries.get(head)?.declared) return "derived";
2342
2616
  }
2343
- return /* @__PURE__ */ jsx("span", { className: "sdui-editor-cv-mark", ...common, children });
2344
- }
2345
- function EditShow({ children }) {
2346
- return /* @__PURE__ */ jsx(Fragment, { children });
2617
+ return entry ? "read" : "unknown";
2347
2618
  }
2348
- function EditCan({
2349
- fallback,
2350
- children
2351
- }) {
2352
- return /* @__PURE__ */ jsxs(Fragment, { children: [
2353
- children,
2354
- fallback ? /* @__PURE__ */ jsxs("div", { className: "sdui-editor-cv-branch", children: [
2355
- /* @__PURE__ */ jsx("span", { className: "sdui-editor-cv-branch__tag", children: "\uCC28\uB2E8 \uC2DC" }),
2356
- fallback
2357
- ] }) : null
2358
- ] });
2619
+ function suggestStateKeys(index, query = "", limit = 20) {
2620
+ const needle = query.trim().toLowerCase();
2621
+ const matched = [...index.entries.values()].filter(
2622
+ (entry) => needle === "" || entry.key.toLowerCase().includes(needle)
2623
+ );
2624
+ return matched.sort((a, b) => {
2625
+ if (a.declared !== b.declared) return a.declared ? -1 : 1;
2626
+ if (needle) {
2627
+ const ap = a.key.toLowerCase().startsWith(needle);
2628
+ const bp = b.key.toLowerCase().startsWith(needle);
2629
+ if (ap !== bp) return ap ? -1 : 1;
2630
+ }
2631
+ return b.reads + b.writes - (a.reads + a.writes) || a.key.localeCompare(b.key);
2632
+ }).slice(0, limit);
2359
2633
  }
2360
- function EditForEach({
2361
- source,
2362
- empty,
2363
- children
2364
- }) {
2365
- const rows = Array.isArray(source) ? source.length : null;
2366
- return /* @__PURE__ */ jsxs(Fragment, { children: [
2367
- /* @__PURE__ */ jsxs("div", { className: "sdui-editor-cv-branch", children: [
2368
- /* @__PURE__ */ jsxs("span", { className: "sdui-editor-cv-branch__tag", children: [
2369
- "\uD589 1\uAC1C \uBBF8\uB9AC\uBCF4\uAE30",
2370
- rows === null ? "" : ` \xB7 \uC9C0\uAE08 ${rows}\uAC74`
2371
- ] }),
2372
- children
2373
- ] }),
2374
- empty ? /* @__PURE__ */ jsxs("div", { className: "sdui-editor-cv-branch", children: [
2375
- /* @__PURE__ */ jsx("span", { className: "sdui-editor-cv-branch__tag", children: "\uBE44\uC5C8\uC744 \uB54C" }),
2376
- empty
2377
- ] }) : null
2378
- ] });
2634
+
2635
+ // src/builder/canvas/dnd.ts
2636
+ var DRAG_MIME = "application/x-wipco-sdui-builder";
2637
+ var payload = null;
2638
+ function beginDrag(transfer, next) {
2639
+ payload = next;
2640
+ if (!transfer) return;
2641
+ transfer.setData(DRAG_MIME, next.component);
2642
+ transfer.setData("text/plain", next.label);
2643
+ transfer.effectAllowed = next.kind === "insert" ? "copy" : "move";
2379
2644
  }
2380
- function createEditRegistry(base = defaultRegistry) {
2381
- return {
2382
- ...base,
2383
- Show: EditShow,
2384
- Can: EditCan,
2385
- Dialog,
2386
- [EDIT_MARK]: EditMark,
2387
- [EDIT_FOREACH]: EditForEach
2388
- };
2645
+ function currentDrag() {
2646
+ return payload;
2389
2647
  }
2390
- var PLACEHOLDER_SVG = [
2391
- '<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64">',
2392
- '<rect width="64" height="64" fill="#e2e8f0"/>',
2393
- '<path d="M0 0h16v16H0zM32 0h16v16H32zM16 16h16v16H16zM48 16h16v16H48z',
2394
- 'M0 32h16v16H0zM32 32h16v16H32zM16 48h16v16H16zM48 48h16v16H48z" fill="#cbd5e1"/>',
2395
- '<circle cx="24" cy="24" r="6" fill="#94a3b8"/>',
2396
- '<path d="M8 52l16-18 10 11 8-8 14 15z" fill="#94a3b8"/>',
2397
- "</svg>"
2398
- ].join("");
2399
- var CANVAS_ASSET_PLACEHOLDER = `data:image/svg+xml,${encodeURIComponent(PLACEHOLDER_SVG)}`;
2400
- function isInertUrl(url) {
2401
- const value = url.trim().toLowerCase();
2402
- return value.startsWith("#") || value.startsWith("data:") || value.startsWith("blob:");
2648
+ function endDrag() {
2649
+ payload = null;
2403
2650
  }
2404
- function resolveCanvasAsset(url, ctx) {
2405
- if (isInertUrl(url)) return url;
2406
- return ctx.kind === "attribute" ? CANVAS_ASSET_PLACEHOLDER : null;
2651
+ function isBuilderDrag(transfer) {
2652
+ if (payload !== null) return true;
2653
+ return transfer ? Array.from(transfer.types).includes(DRAG_MIME) : false;
2407
2654
  }
2408
- function createCanvasAdapter(onBlocked) {
2655
+ function insertDragOf(spec, label) {
2409
2656
  return {
2410
- navigate: (to) => onBlocked(`navigate \u2192 ${to}`),
2411
- // 요청은 보내지 않되 성공(null)으로 응답한다 — onSuccess 체인이 어떻게 흐르는지는
2412
- // 보여 주면서 서버는 건드리지 않는다.
2413
- fetch: async (path, init) => {
2414
- onBlocked(`${init.method} ${path}`);
2415
- return null;
2416
- },
2417
- // download는 응답을 파일로 저장한다. 실패로 끊어 빈 파일이 내려가는 것까지 막는다.
2418
- rawFetch: async (path) => {
2419
- onBlocked(`download ${path}`);
2420
- throw new Error("\uCE94\uBC84\uC2A4\uC5D0\uC11C\uB294 \uC2E4\uD589\uB418\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4");
2421
- },
2422
- invalidate: (prefix) => onBlocked(`invalidate ${prefix}`),
2423
- refresh: () => onBlocked("refresh"),
2424
- // 편집 중 상태가 호스트 앱의 URL을 덮어쓰지 않게 한다(syncUrlState={false}와 이중 방어).
2425
- replaceQuery: () => {
2426
- },
2427
- // 스펙 최상위 `subscribe`가 여는 실시간 채널. **지금 이걸 비워 두면 안전한 것은 우연이다** —
2428
- // `defaultAdapter`에 아직 구현이 없어서일 뿐이고, 구현이 생기는 순간 캔버스가 조용히
2429
- // EventSource를 연다. 실제로 vehicle/logs 스펙이 최상위 subscribe를 갖고 있다.
2430
- // 해제 함수를 돌려주는 계약이라 no-op도 함수를 반환해야 한다.
2431
- subscribe: (channels) => {
2432
- onBlocked(`subscribe ${channels.join(", ")}`);
2433
- return () => {
2434
- };
2435
- },
2436
- // 위의 다섯과 성질이 다르다. 저 다섯은 어댑터가 **호출을 소유**하므로 비워 두면
2437
- // 아무 일도 안 일어나지만, `<img src>`와 `background-image: url(…)`은 요청을 내는
2438
- // 주체가 브라우저라 어댑터가 그 자리에 없다. 실제로 캔버스 클릭 차단과 위 다섯 개를
2439
- // 다 두고도 아바타·라이트박스·style 프롭으로 요청 5건이 세션 쿠키를 달고 나갔다.
2440
- resolveAsset: resolveCanvasAsset,
2441
- // 위의 여섯과 또 성질이 다르다. 저것들은 **이 어댑터를 거쳐 나가는** 경로라 여기서
2442
- // 끊을 수 있지만, 소비 앱의 목록 위젯처럼 **자기 데이터 클라이언트로 직접** 조회하는
2443
- // 컴포넌트는 어댑터를 아예 지나가지 않는다(ERP의 RemoteSearchResults가 그렇다 —
2444
- // TanStack Query 모듈 싱글턴이라 끼어들 자리가 없다). 그런 컴포넌트가 스스로
2445
- // 물러설 수 있게 "여기는 진짜 화면이 아니다"만 알려 준다.
2446
- //
2447
- // 값 교체가 아니라 **선언**이라는 점이 중요하다. `fetch`처럼 빈 응답을 돌려주는 것으로
2448
- // 대신하려 했다가 버렸다 — 그 빈 응답이 소비 앱의 공유 캐시에 실려, 편집기를 닫고
2449
- // 진짜 페이지를 열면 오염된 목록이 나온다. 요청은 **시작되기 전에** 막혀야 한다.
2450
- sandbox: true
2657
+ kind: "insert",
2658
+ component: typeof spec === "string" ? TEXT_CHILD : spec.component,
2659
+ spec,
2660
+ label
2451
2661
  };
2452
2662
  }
2453
2663
 
@@ -3155,6 +3365,10 @@ function Canvas({
3155
3365
  */
3156
3366
  registry = editorRegistry,
3157
3367
  can,
3368
+ useSpec,
3369
+ previewFetch,
3370
+ dataArmed,
3371
+ initialState,
3158
3372
  className
3159
3373
  }) {
3160
3374
  ensureBuilderStyles();
@@ -3184,8 +3398,13 @@ function Canvas({
3184
3398
  const page = useMemo(() => toCanvasPage(doc), [doc]);
3185
3399
  const editRegistry = useMemo(() => createEditRegistry(registry), [registry]);
3186
3400
  const adapter = useMemo(
3187
- () => createCanvasAdapter((what) => announce(`${what} \u2014 \uCE94\uBC84\uC2A4\uC5D0\uC11C\uB294 \uC2E4\uD589\uB418\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4`)),
3188
- [announce]
3401
+ () => resolvePreviewAdapter({
3402
+ armed: dataArmed === true,
3403
+ previewFetch,
3404
+ onBlocked: (what) => announce(`${what} \u2014 \uCE94\uBC84\uC2A4\uC5D0\uC11C\uB294 \uC2E4\uD589\uB418\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4`),
3405
+ useSpec
3406
+ }),
3407
+ [announce, dataArmed, previewFetch, useSpec]
3189
3408
  );
3190
3409
  useLayoutEffect(() => {
3191
3410
  setVersion((v) => v + 1);
@@ -3455,9 +3674,11 @@ function Canvas({
3455
3674
  page,
3456
3675
  registry: editRegistry,
3457
3676
  adapter,
3677
+ initialState,
3458
3678
  syncUrlState: false,
3459
3679
  can: can ?? "allow-all"
3460
- }
3680
+ },
3681
+ initialState ? seedSignature(initialState) : void 0
3461
3682
  ),
3462
3683
  notice ? /* @__PURE__ */ jsx("div", { className: "sdui-editor-cv-notice", children: notice }) : null
3463
3684
  ] }),
@@ -3700,16 +3921,16 @@ function Palette({ doc, selection, dispatch, catalog, registry, className }) {
3700
3921
  ] })
3701
3922
  ] });
3702
3923
  }
3703
- function isPlainObject3(v) {
3924
+ function isPlainObject4(v) {
3704
3925
  return typeof v === "object" && v !== null && !Array.isArray(v);
3705
3926
  }
3706
3927
  function bindingOf(value) {
3707
- if (!isPlainObject3(value)) return null;
3928
+ if (!isPlainObject4(value)) return null;
3708
3929
  if (typeof value.$state === "string") return { kind: "state", label: "\uC0C1\uD0DC", detail: value.$state };
3709
3930
  if (value.$item === true) return { kind: "item", label: "\uD589", detail: "\uC804\uCCB4" };
3710
3931
  if (typeof value.$item === "string") return { kind: "item", label: "\uD589", detail: value.$item };
3711
3932
  if (value.$index === true) return { kind: "index", label: "\uBC88\uD638" };
3712
- if (isPlainObject3(value.$calc) && typeof value.$calc.op === "string") {
3933
+ if (isPlainObject4(value.$calc) && typeof value.$calc.op === "string") {
3713
3934
  return { kind: "calc", label: "\uACC4\uC0B0", detail: String(value.$calc.op) };
3714
3935
  }
3715
3936
  return null;
@@ -3889,7 +4110,7 @@ function asArray(value) {
3889
4110
  return Array.isArray(value) ? value : null;
3890
4111
  }
3891
4112
  function asObject(value) {
3892
- return isPlainObject3(value) ? value : null;
4113
+ return isPlainObject4(value) ? value : null;
3893
4114
  }
3894
4115
  function patchObject(base, name, value) {
3895
4116
  const next = { ...base ?? {} };
@@ -4065,30 +4286,246 @@ function toSteps(value) {
4065
4286
  if (value === void 0) return [];
4066
4287
  return [value];
4067
4288
  }
4068
- function fromSteps(steps, wasArray) {
4069
- if (steps.length === 0) return void 0;
4070
- if (steps.length === 1 && !wasArray) return steps[0];
4071
- return steps;
4289
+ function fromSteps(steps, wasArray) {
4290
+ if (steps.length === 0) return void 0;
4291
+ if (steps.length === 1 && !wasArray) return steps[0];
4292
+ return steps;
4293
+ }
4294
+ function summarizeStep(step) {
4295
+ const obj = asObject(step);
4296
+ if (!obj) return toJsonText(step).slice(0, 60);
4297
+ const verb = typeof obj.action === "string" ? VERB_BY_NAME.get(obj.action) : void 0;
4298
+ const label = verb?.label ?? (typeof obj.action === "string" ? obj.action : "\uC54C \uC218 \uC5C6\uB294 \uB3D9\uC791");
4299
+ const detail = verb?.summaryField ? obj[verb.summaryField] : void 0;
4300
+ return typeof detail === "string" || typeof detail === "number" ? `${label} \xB7 ${detail}` : label;
4301
+ }
4302
+ function switchVerb(step, next) {
4303
+ const out = { action: next.action };
4304
+ for (const field of next.fields) {
4305
+ const carried = step?.[field.name];
4306
+ if (carried !== void 0) out[field.name] = carried;
4307
+ }
4308
+ return out;
4309
+ }
4310
+ function isActionShaped(value) {
4311
+ if (Array.isArray(value)) return value.every((v) => isPlainObject4(v) && typeof v.action === "string");
4312
+ return isPlainObject4(value) && typeof value.action === "string";
4313
+ }
4314
+ function ToolButton({
4315
+ icon,
4316
+ label,
4317
+ onClick,
4318
+ disabled,
4319
+ active: active2,
4320
+ text
4321
+ }) {
4322
+ return /* @__PURE__ */ jsxs(
4323
+ "button",
4324
+ {
4325
+ type: "button",
4326
+ className: `sdui-insp-tool${active2 ? " sdui-insp-tool--on" : ""}`,
4327
+ onClick,
4328
+ disabled,
4329
+ title: label,
4330
+ "aria-label": label,
4331
+ children: [
4332
+ /* @__PURE__ */ jsx(Icon, { name: icon, size: 14 }),
4333
+ text ? /* @__PURE__ */ jsx("span", { children: text }) : null
4334
+ ]
4335
+ }
4336
+ );
4337
+ }
4338
+ function textCommitValue(raw, meta) {
4339
+ return raw === "" && !meta?.required ? void 0 : raw;
4340
+ }
4341
+ function TextWidget({ value, onChange, meta, stateKeys }) {
4342
+ const listId = useId();
4343
+ const suggest = stateKeys && stateKeys.length > 0;
4344
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
4345
+ /* @__PURE__ */ jsx(
4346
+ Input,
4347
+ {
4348
+ size: "sm",
4349
+ value: typeof value === "string" ? value : formatScalar(value),
4350
+ placeholder: meta?.default !== void 0 ? `\uAE30\uBCF8\uAC12: ${formatScalar(meta.default)}` : void 0,
4351
+ list: suggest ? listId : void 0,
4352
+ onChange: (e) => onChange(textCommitValue(e.target.value, meta))
4353
+ }
4354
+ ),
4355
+ suggest ? /* @__PURE__ */ jsx("datalist", { id: listId, children: stateKeys.slice(0, 200).map((key) => /* @__PURE__ */ jsx("option", { value: key }, key)) }) : null
4356
+ ] });
4357
+ }
4358
+ function NumberWidget({ value, onChange, meta }) {
4359
+ const external = formatScalar(value);
4360
+ const [text, setText2] = useState(external);
4361
+ const mine = useRef(external);
4362
+ useEffect(() => {
4363
+ if (external !== mine.current) {
4364
+ mine.current = external;
4365
+ setText2(external);
4366
+ }
4367
+ }, [external]);
4368
+ return /* @__PURE__ */ jsx(
4369
+ Input,
4370
+ {
4371
+ size: "sm",
4372
+ inputMode: "decimal",
4373
+ value: text,
4374
+ placeholder: meta?.default !== void 0 ? `\uAE30\uBCF8\uAC12: ${formatScalar(meta.default)}` : void 0,
4375
+ onChange: (e) => {
4376
+ const next = e.target.value;
4377
+ setText2(next);
4378
+ const parsed = parseNumber(next);
4379
+ if (parsed === null) return;
4380
+ mine.current = formatScalar(parsed);
4381
+ onChange(parsed);
4382
+ }
4383
+ }
4384
+ );
4385
+ }
4386
+ function BoolWidget({ value, onChange, meta }) {
4387
+ const on = value === true;
4388
+ return /* @__PURE__ */ jsx(
4389
+ Switch,
4390
+ {
4391
+ size: "sm",
4392
+ checked: on,
4393
+ label: on ? "\uCF2C" : "\uB054",
4394
+ onChange: (e) => {
4395
+ if (e.target.checked) onChange(true);
4396
+ else onChange(meta?.default === true ? false : void 0);
4397
+ }
4398
+ }
4399
+ );
4072
4400
  }
4073
- function summarizeStep(step) {
4074
- const obj = asObject(step);
4075
- if (!obj) return toJsonText(step).slice(0, 60);
4076
- const verb = typeof obj.action === "string" ? VERB_BY_NAME.get(obj.action) : void 0;
4077
- const label = verb?.label ?? (typeof obj.action === "string" ? obj.action : "\uC54C \uC218 \uC5C6\uB294 \uB3D9\uC791");
4078
- const detail = verb?.summaryField ? obj[verb.summaryField] : void 0;
4079
- return typeof detail === "string" || typeof detail === "number" ? `${label} \xB7 ${detail}` : label;
4401
+ var CURRENT = "__current__";
4402
+ function EnumWidget({ value, onChange, meta }) {
4403
+ const options = meta?.options ?? [];
4404
+ const known = options.findIndex((o) => o.value === value);
4405
+ const outsider = value !== void 0 && known < 0;
4406
+ const choices = options.map((option, index) => ({
4407
+ value: String(index),
4408
+ label: option.legacy ? `${option.label} (\uC61B \uAC12)` : option.label
4409
+ }));
4410
+ if (outsider) choices.push({ value: CURRENT, label: `${formatScalar(value)} (\uD6C4\uBCF4 \uBC16)` });
4411
+ return /* @__PURE__ */ jsx(
4412
+ Select,
4413
+ {
4414
+ size: "sm",
4415
+ options: choices,
4416
+ placeholder: meta?.default !== void 0 ? `\uAE30\uBCF8\uAC12: ${labelOf(options, meta.default)}` : "(\uC5C6\uC74C)",
4417
+ value: outsider ? CURRENT : known >= 0 ? String(known) : "",
4418
+ onChange: (e) => {
4419
+ const raw = e.target.value;
4420
+ if (raw === "") onChange(void 0);
4421
+ else if (raw === CURRENT) onChange(value);
4422
+ else onChange(options[Number(raw)]?.value);
4423
+ }
4424
+ }
4425
+ );
4080
4426
  }
4081
- function switchVerb(step, next) {
4082
- const out = { action: next.action };
4083
- for (const field of next.fields) {
4084
- const carried = step?.[field.name];
4085
- if (carried !== void 0) out[field.name] = carried;
4086
- }
4087
- return out;
4427
+ function labelOf(options, value) {
4428
+ return options.find((o) => o.value === value)?.label ?? formatScalar(value);
4088
4429
  }
4089
- function isActionShaped(value) {
4090
- if (Array.isArray(value)) return value.every((v) => isPlainObject3(v) && typeof v.action === "string");
4091
- return isPlainObject3(value) && typeof value.action === "string";
4430
+ function JsonWidget({ value, onChange, rows = 4 }) {
4431
+ const external = toJsonText(value);
4432
+ const [text, setText2] = useState(external);
4433
+ const [error, setError] = useState(null);
4434
+ const mine = useRef(external);
4435
+ useEffect(() => {
4436
+ if (external !== mine.current) {
4437
+ mine.current = external;
4438
+ setText2(external);
4439
+ setError(null);
4440
+ }
4441
+ }, [external]);
4442
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
4443
+ /* @__PURE__ */ jsx(
4444
+ "textarea",
4445
+ {
4446
+ className: `sdui-insp-json${error ? " sdui-insp-json--bad" : ""}`,
4447
+ rows,
4448
+ spellCheck: false,
4449
+ value: text,
4450
+ onChange: (e) => {
4451
+ const next = e.target.value;
4452
+ setText2(next);
4453
+ const parsed = parseJson(next);
4454
+ if (!parsed.ok) {
4455
+ setError(parsed.error ?? "JSON\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4");
4456
+ return;
4457
+ }
4458
+ setError(null);
4459
+ mine.current = toJsonText(parsed.value);
4460
+ onChange(parsed.value);
4461
+ }
4462
+ }
4463
+ ),
4464
+ error ? /* @__PURE__ */ jsx("p", { className: "sdui-insp-err", children: error }) : null
4465
+ ] });
4466
+ }
4467
+ function BindingChip({
4468
+ info,
4469
+ onEdit,
4470
+ onRaw
4471
+ }) {
4472
+ return /* @__PURE__ */ jsxs("div", { className: "sdui-insp-chip", title: bindingText(info), children: [
4473
+ /* @__PURE__ */ jsx(Icon, { name: "link", size: 14 }),
4474
+ /* @__PURE__ */ jsx("span", { className: "sdui-insp-chip__kind", children: info.label }),
4475
+ /* @__PURE__ */ jsx("span", { className: "sdui-insp-chip__val", children: info.detail ?? "" }),
4476
+ /* @__PURE__ */ jsxs("span", { className: "sdui-insp-chip__tools", children: [
4477
+ onEdit ? /* @__PURE__ */ jsx(ToolButton, { icon: "pencil", label: "\uBC14\uC778\uB529 \uD3B8\uC9D1", onClick: onEdit }) : null,
4478
+ onRaw ? /* @__PURE__ */ jsx(ToolButton, { icon: "square-pen", label: "JSON\uC73C\uB85C \uD3B8\uC9D1", onClick: onRaw }) : null
4479
+ ] })
4480
+ ] });
4481
+ }
4482
+ function FragmentRefRow({
4483
+ refId,
4484
+ pageIds,
4485
+ onOpenSpec
4486
+ }) {
4487
+ let body;
4488
+ if (typeof refId === "string" && refId !== "") {
4489
+ const missing = pageIds !== void 0 && !pageIds.includes(refId);
4490
+ body = /* @__PURE__ */ jsxs(Fragment, { children: [
4491
+ /* @__PURE__ */ jsxs("div", { className: "sdui-insp-row__head", children: [
4492
+ /* @__PURE__ */ jsx("span", { className: "sdui-insp-row__label", children: "\uCC38\uC870 \uC2A4\uD399" }),
4493
+ /* @__PURE__ */ jsx("span", { className: "sdui-insp-row__name wp-mono-xs", children: refId }),
4494
+ missing ? /* @__PURE__ */ jsx("span", { className: "sdui-insp-flag sdui-insp-flag--req", children: "\uC5C6\uC74C" }) : null,
4495
+ /* @__PURE__ */ jsx("span", { className: "sdui-insp-row__tools", children: onOpenSpec ? /* @__PURE__ */ jsx(
4496
+ ToolButton,
4497
+ {
4498
+ icon: "external-link",
4499
+ label: `${refId} \uD3B8\uC9D1\uC73C\uB85C \uC5F4\uAE30`,
4500
+ text: "\uC5F4\uAE30",
4501
+ onClick: () => onOpenSpec(refId)
4502
+ }
4503
+ ) : null })
4504
+ ] }),
4505
+ missing ? /* @__PURE__ */ jsx("p", { className: "sdui-insp-row__warn", children: "\uC800\uC7A5\uC18C\uC5D0 \uC5C6\uB294 \uC2A4\uD399 id\uC785\uB2C8\uB2E4 \u2014 \uC774\uB300\uB85C \uC800\uC7A5\uD558\uBA74 \uC774 \uC790\uB9AC\uB294 \uD654\uBA74\uC5D0 \uADF8\uB824\uC9C0\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uC5F4\uAE30\uB97C \uB204\uB974\uBA74 \uD3B8\uC9D1 \uD654\uBA74\uC5D0\uC11C \uAC19\uC740 id\uB85C \uC0C8\uB85C \uB9CC\uB4E4 \uC218 \uC788\uC2B5\uB2C8\uB2E4." }) : /* @__PURE__ */ jsx("p", { className: "sdui-insp-row__desc", children: "\uC774 \uC870\uAC01\uC758 \uB0B4\uC6A9\uC740 \uC800 \uC2A4\uD399\uC758 body\uC785\uB2C8\uB2E4. \uD3B8\uC9D1\uC73C\uB85C \uC5F4\uC5B4\uB3C4 \uC9C0\uAE08 \uBB38\uC11C\uC758 \uBBF8\uC800\uC7A5 \uBCC0\uACBD\uC740 \uCD08\uC548\uC73C\uB85C \uB0A8\uC2B5\uB2C8\uB2E4." })
4506
+ ] });
4507
+ } else if (isPlainObject4(refId)) {
4508
+ body = /* @__PURE__ */ jsxs(Fragment, { children: [
4509
+ /* @__PURE__ */ jsxs("div", { className: "sdui-insp-row__head", children: [
4510
+ /* @__PURE__ */ jsx("span", { className: "sdui-insp-row__label", children: "\uCC38\uC870 \uC2A4\uD399" }),
4511
+ /* @__PURE__ */ jsx("span", { className: "sdui-insp-flag sdui-insp-flag--unknown", children: "\uB3D9\uC801 \uCC38\uC870" })
4512
+ ] }),
4513
+ /* @__PURE__ */ jsxs("p", { className: "sdui-insp-row__desc", children: [
4514
+ "id\uAC00 \uBC14\uC778\uB529(",
4515
+ Object.keys(refId)[0] ?? "$state",
4516
+ ")\uC73C\uB85C \uC815\uD574\uC9D1\uB2C8\uB2E4 \u2014 \uC2E4\uD589 \uC2DC\uC810 \uAC12\uC774\uB77C \uD3B8\uC9D1\uAE30\uC5D0\uC11C \uC5F4 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uAC12\uC740 \uC704 id \uD504\uB86D\uC5D0\uC11C \uD655\uC778\uD558\uC138\uC694."
4517
+ ] })
4518
+ ] });
4519
+ } else {
4520
+ body = /* @__PURE__ */ jsxs(Fragment, { children: [
4521
+ /* @__PURE__ */ jsxs("div", { className: "sdui-insp-row__head", children: [
4522
+ /* @__PURE__ */ jsx("span", { className: "sdui-insp-row__label", children: "\uCC38\uC870 \uC2A4\uD399" }),
4523
+ /* @__PURE__ */ jsx("span", { className: "sdui-insp-flag sdui-insp-flag--req", children: "\uD544\uC218" })
4524
+ ] }),
4525
+ /* @__PURE__ */ jsx("p", { className: "sdui-insp-row__warn", children: "id \uD504\uB86D\uC774 \uBE44\uC5B4 \uC788\uC2B5\uB2C8\uB2E4 \u2014 \uAC00\uC838\uC62C \uC2A4\uD399\uC758 id(\uC608: dialogs/asset-edit)\uB97C \uB123\uC5B4\uC57C \uB80C\uB354\uB429\uB2C8\uB2E4." })
4526
+ ] });
4527
+ }
4528
+ return /* @__PURE__ */ jsx("section", { className: "sdui-insp-group", children: /* @__PURE__ */ jsx("div", { className: "sdui-insp-group__body", children: /* @__PURE__ */ jsx("div", { className: "sdui-insp-row", children: body }) }) });
4092
4529
  }
4093
4530
  var CSS2 = `
4094
4531
  .sdui-insp {
@@ -4221,209 +4658,41 @@ var CSS2 = `
4221
4658
  border: var(--wp-border-w) solid var(--wp-border-subtle); border-radius: var(--wp-radius-md);
4222
4659
  background: var(--wp-surface-sunken);
4223
4660
  }
4224
- .sdui-insp-item__head {
4225
- display: flex; align-items: center; gap: var(--wp-space-1);
4226
- padding: var(--wp-space-1) var(--wp-space-2);
4227
- }
4228
- .sdui-insp-item__idx {
4229
- font-family: var(--wp-font-mono); font-size: var(--wp-size-2xs); color: var(--wp-text-subtle);
4230
- }
4231
- .sdui-insp-item__sum {
4232
- font-size: var(--wp-size-2xs); color: var(--wp-text-muted);
4233
- overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
4234
- }
4235
- .sdui-insp-item__tools { margin-left: auto; display: flex; gap: 0; flex: none; }
4236
- .sdui-insp-item__body {
4237
- display: flex; flex-direction: column; gap: var(--wp-space-2);
4238
- padding: var(--wp-space-2); padding-top: 0;
4239
- }
4240
- /* \uBA38\uB9AC \uC5C6\uC774 \uBAB8\uD1B5\uB9CC \uC788\uB294 \uC0C1\uC790(\uD544\uB4DC \uC815\uC758\uAC00 \uC788\uB294 object \uD504\uB86D) \u2014 \uC704\uCABD \uC5EC\uBC31\uC744 \uB418\uC0B4\uB9B0\uB2E4 */
4241
- .sdui-insp-item__body--solo { padding-top: var(--wp-space-2); }
4242
- .sdui-insp-struct__add { align-self: flex-start; }
4243
-
4244
- /* ---------- JSON \uC6D0\uC2DC \uD3B8\uC9D1 ---------- */
4245
- .sdui-insp-json {
4246
- width: 100%; min-height: calc(var(--wp-space-8) * 2); resize: vertical;
4247
- font-family: var(--wp-font-mono); font-size: var(--wp-size-2xs);
4248
- line-height: var(--wp-leading-snug);
4249
- padding: var(--wp-space-2);
4250
- border: var(--wp-border-w) solid var(--wp-border-default); border-radius: var(--wp-radius-md);
4251
- background: var(--wp-surface-card); color: var(--wp-text-default);
4252
- }
4253
- .sdui-insp-json:focus { outline: none; border-color: var(--wp-border-focus); }
4254
- .sdui-insp-json--bad { border-color: var(--wp-border-danger); }
4255
- .sdui-insp-err { font-size: var(--wp-size-2xs); color: var(--wp-text-danger); }
4256
- `;
4257
- function InspectorStyle() {
4258
- return /* @__PURE__ */ jsx("style", { href: "sdui-editor-inspector", precedence: "medium", children: CSS2 });
4259
- }
4260
- function ToolButton({
4261
- icon,
4262
- label,
4263
- onClick,
4264
- disabled,
4265
- active: active2,
4266
- text
4267
- }) {
4268
- return /* @__PURE__ */ jsxs(
4269
- "button",
4270
- {
4271
- type: "button",
4272
- className: `sdui-insp-tool${active2 ? " sdui-insp-tool--on" : ""}`,
4273
- onClick,
4274
- disabled,
4275
- title: label,
4276
- "aria-label": label,
4277
- children: [
4278
- /* @__PURE__ */ jsx(Icon, { name: icon, size: 14 }),
4279
- text ? /* @__PURE__ */ jsx("span", { children: text }) : null
4280
- ]
4281
- }
4282
- );
4283
- }
4284
- function textCommitValue(raw, meta) {
4285
- return raw === "" && !meta?.required ? void 0 : raw;
4286
- }
4287
- function TextWidget({ value, onChange, meta, stateKeys }) {
4288
- const listId = useId();
4289
- const suggest = stateKeys && stateKeys.length > 0;
4290
- return /* @__PURE__ */ jsxs(Fragment, { children: [
4291
- /* @__PURE__ */ jsx(
4292
- Input,
4293
- {
4294
- size: "sm",
4295
- value: typeof value === "string" ? value : formatScalar(value),
4296
- placeholder: meta?.default !== void 0 ? `\uAE30\uBCF8\uAC12: ${formatScalar(meta.default)}` : void 0,
4297
- list: suggest ? listId : void 0,
4298
- onChange: (e) => onChange(textCommitValue(e.target.value, meta))
4299
- }
4300
- ),
4301
- suggest ? /* @__PURE__ */ jsx("datalist", { id: listId, children: stateKeys.slice(0, 200).map((key) => /* @__PURE__ */ jsx("option", { value: key }, key)) }) : null
4302
- ] });
4303
- }
4304
- function NumberWidget({ value, onChange, meta }) {
4305
- const external = formatScalar(value);
4306
- const [text, setText2] = useState(external);
4307
- const mine = useRef(external);
4308
- useEffect(() => {
4309
- if (external !== mine.current) {
4310
- mine.current = external;
4311
- setText2(external);
4312
- }
4313
- }, [external]);
4314
- return /* @__PURE__ */ jsx(
4315
- Input,
4316
- {
4317
- size: "sm",
4318
- inputMode: "decimal",
4319
- value: text,
4320
- placeholder: meta?.default !== void 0 ? `\uAE30\uBCF8\uAC12: ${formatScalar(meta.default)}` : void 0,
4321
- onChange: (e) => {
4322
- const next = e.target.value;
4323
- setText2(next);
4324
- const parsed = parseNumber(next);
4325
- if (parsed === null) return;
4326
- mine.current = formatScalar(parsed);
4327
- onChange(parsed);
4328
- }
4329
- }
4330
- );
4331
- }
4332
- function BoolWidget({ value, onChange, meta }) {
4333
- const on = value === true;
4334
- return /* @__PURE__ */ jsx(
4335
- Switch,
4336
- {
4337
- size: "sm",
4338
- checked: on,
4339
- label: on ? "\uCF2C" : "\uB054",
4340
- onChange: (e) => {
4341
- if (e.target.checked) onChange(true);
4342
- else onChange(meta?.default === true ? false : void 0);
4343
- }
4344
- }
4345
- );
4661
+ .sdui-insp-item__head {
4662
+ display: flex; align-items: center; gap: var(--wp-space-1);
4663
+ padding: var(--wp-space-1) var(--wp-space-2);
4346
4664
  }
4347
- var CURRENT = "__current__";
4348
- function EnumWidget({ value, onChange, meta }) {
4349
- const options = meta?.options ?? [];
4350
- const known = options.findIndex((o) => o.value === value);
4351
- const outsider = value !== void 0 && known < 0;
4352
- const choices = options.map((option, index) => ({
4353
- value: String(index),
4354
- label: option.legacy ? `${option.label} (\uC61B \uAC12)` : option.label
4355
- }));
4356
- if (outsider) choices.push({ value: CURRENT, label: `${formatScalar(value)} (\uD6C4\uBCF4 \uBC16)` });
4357
- return /* @__PURE__ */ jsx(
4358
- Select,
4359
- {
4360
- size: "sm",
4361
- options: choices,
4362
- placeholder: meta?.default !== void 0 ? `\uAE30\uBCF8\uAC12: ${labelOf(options, meta.default)}` : "(\uC5C6\uC74C)",
4363
- value: outsider ? CURRENT : known >= 0 ? String(known) : "",
4364
- onChange: (e) => {
4365
- const raw = e.target.value;
4366
- if (raw === "") onChange(void 0);
4367
- else if (raw === CURRENT) onChange(value);
4368
- else onChange(options[Number(raw)]?.value);
4369
- }
4370
- }
4371
- );
4665
+ .sdui-insp-item__idx {
4666
+ font-family: var(--wp-font-mono); font-size: var(--wp-size-2xs); color: var(--wp-text-subtle);
4372
4667
  }
4373
- function labelOf(options, value) {
4374
- return options.find((o) => o.value === value)?.label ?? formatScalar(value);
4668
+ .sdui-insp-item__sum {
4669
+ font-size: var(--wp-size-2xs); color: var(--wp-text-muted);
4670
+ overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
4375
4671
  }
4376
- function JsonWidget({ value, onChange, rows = 4 }) {
4377
- const external = toJsonText(value);
4378
- const [text, setText2] = useState(external);
4379
- const [error, setError] = useState(null);
4380
- const mine = useRef(external);
4381
- useEffect(() => {
4382
- if (external !== mine.current) {
4383
- mine.current = external;
4384
- setText2(external);
4385
- setError(null);
4386
- }
4387
- }, [external]);
4388
- return /* @__PURE__ */ jsxs(Fragment, { children: [
4389
- /* @__PURE__ */ jsx(
4390
- "textarea",
4391
- {
4392
- className: `sdui-insp-json${error ? " sdui-insp-json--bad" : ""}`,
4393
- rows,
4394
- spellCheck: false,
4395
- value: text,
4396
- onChange: (e) => {
4397
- const next = e.target.value;
4398
- setText2(next);
4399
- const parsed = parseJson(next);
4400
- if (!parsed.ok) {
4401
- setError(parsed.error ?? "JSON\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4");
4402
- return;
4403
- }
4404
- setError(null);
4405
- mine.current = toJsonText(parsed.value);
4406
- onChange(parsed.value);
4407
- }
4408
- }
4409
- ),
4410
- error ? /* @__PURE__ */ jsx("p", { className: "sdui-insp-err", children: error }) : null
4411
- ] });
4672
+ .sdui-insp-item__tools { margin-left: auto; display: flex; gap: 0; flex: none; }
4673
+ .sdui-insp-item__body {
4674
+ display: flex; flex-direction: column; gap: var(--wp-space-2);
4675
+ padding: var(--wp-space-2); padding-top: 0;
4412
4676
  }
4413
- function BindingChip({
4414
- info,
4415
- onEdit,
4416
- onRaw
4417
- }) {
4418
- return /* @__PURE__ */ jsxs("div", { className: "sdui-insp-chip", title: bindingText(info), children: [
4419
- /* @__PURE__ */ jsx(Icon, { name: "link", size: 14 }),
4420
- /* @__PURE__ */ jsx("span", { className: "sdui-insp-chip__kind", children: info.label }),
4421
- /* @__PURE__ */ jsx("span", { className: "sdui-insp-chip__val", children: info.detail ?? "" }),
4422
- /* @__PURE__ */ jsxs("span", { className: "sdui-insp-chip__tools", children: [
4423
- onEdit ? /* @__PURE__ */ jsx(ToolButton, { icon: "pencil", label: "\uBC14\uC778\uB529 \uD3B8\uC9D1", onClick: onEdit }) : null,
4424
- onRaw ? /* @__PURE__ */ jsx(ToolButton, { icon: "square-pen", label: "JSON\uC73C\uB85C \uD3B8\uC9D1", onClick: onRaw }) : null
4425
- ] })
4426
- ] });
4677
+ /* \uBA38\uB9AC \uC5C6\uC774 \uBAB8\uD1B5\uB9CC \uC788\uB294 \uC0C1\uC790(\uD544\uB4DC \uC815\uC758\uAC00 \uC788\uB294 object \uD504\uB86D) \u2014 \uC704\uCABD \uC5EC\uBC31\uC744 \uB418\uC0B4\uB9B0\uB2E4 */
4678
+ .sdui-insp-item__body--solo { padding-top: var(--wp-space-2); }
4679
+ .sdui-insp-struct__add { align-self: flex-start; }
4680
+
4681
+ /* ---------- JSON \uC6D0\uC2DC \uD3B8\uC9D1 ---------- */
4682
+ .sdui-insp-json {
4683
+ width: 100%; min-height: calc(var(--wp-space-8) * 2); resize: vertical;
4684
+ font-family: var(--wp-font-mono); font-size: var(--wp-size-2xs);
4685
+ line-height: var(--wp-leading-snug);
4686
+ padding: var(--wp-space-2);
4687
+ border: var(--wp-border-w) solid var(--wp-border-default); border-radius: var(--wp-radius-md);
4688
+ background: var(--wp-surface-card); color: var(--wp-text-default);
4689
+ }
4690
+ .sdui-insp-json:focus { outline: none; border-color: var(--wp-border-focus); }
4691
+ .sdui-insp-json--bad { border-color: var(--wp-border-danger); }
4692
+ .sdui-insp-err { font-size: var(--wp-size-2xs); color: var(--wp-text-danger); }
4693
+ `;
4694
+ function InspectorStyle() {
4695
+ return /* @__PURE__ */ jsx("style", { href: "sdui-editor-inspector", precedence: "medium", children: CSS2 });
4427
4696
  }
4428
4697
  function ActionField({ value, onChange, stateKeys }) {
4429
4698
  const wasArray = Array.isArray(value);
@@ -4916,7 +5185,15 @@ function LiteralEditor({ meta, value, onChange, ctx, propName }) {
4916
5185
  const kind = meta.kind;
4917
5186
  switch (kind) {
4918
5187
  case "string":
4919
- return /* @__PURE__ */ jsx(TextWidget, { value, onChange, meta });
5188
+ return /* @__PURE__ */ jsx(
5189
+ TextWidget,
5190
+ {
5191
+ value,
5192
+ onChange,
5193
+ meta,
5194
+ stateKeys: propName ? ctx.textSuggestions?.[propName] : void 0
5195
+ }
5196
+ );
4920
5197
  case "number":
4921
5198
  return /* @__PURE__ */ jsx(NumberWidget, { value, onChange, meta });
4922
5199
  case "boolean":
@@ -5143,6 +5420,8 @@ function Inspector({
5143
5420
  onApply,
5144
5421
  onRequestBinding,
5145
5422
  allowKeyEdit = true,
5423
+ pageIds,
5424
+ onOpenSpec,
5146
5425
  className
5147
5426
  }) {
5148
5427
  const primary = selection.primary;
@@ -5151,9 +5430,17 @@ function Inspector({
5151
5430
  const index = buildStateKeyIndex(doc);
5152
5431
  return suggestStateKeys(index, "", 200).map((entry) => entry.key);
5153
5432
  }, [doc]);
5433
+ const fragmentIds = useMemo(
5434
+ () => child?.kind === "node" && child.component === "Fragment" && pageIds ? pageIds.filter((id) => id.startsWith(DIALOG_PREFIX)) : void 0,
5435
+ [child, pageIds]
5436
+ );
5154
5437
  const ctx = useMemo(
5155
- () => ({ stateKeys, ...onRequestBinding ? { onRequestBinding } : {} }),
5156
- [stateKeys, onRequestBinding]
5438
+ () => ({
5439
+ stateKeys,
5440
+ ...onRequestBinding ? { onRequestBinding } : {},
5441
+ ...fragmentIds && fragmentIds.length > 0 ? { textSuggestions: { id: fragmentIds } } : {}
5442
+ }),
5443
+ [stateKeys, onRequestBinding, fragmentIds]
5157
5444
  );
5158
5445
  const inspection = useMemo(
5159
5446
  () => child && child.kind === "node" ? inspectNode(child, catalog) : null,
@@ -5179,7 +5466,9 @@ function Inspector({
5179
5466
  ctx,
5180
5467
  onApply,
5181
5468
  onRequestBinding,
5182
- allowKeyEdit
5469
+ allowKeyEdit,
5470
+ pageIds,
5471
+ onOpenSpec
5183
5472
  }
5184
5473
  ) : null
5185
5474
  ] })
@@ -5196,7 +5485,9 @@ function NodePanel({
5196
5485
  ctx,
5197
5486
  onApply,
5198
5487
  onRequestBinding,
5199
- allowKeyEdit
5488
+ allowKeyEdit,
5489
+ pageIds,
5490
+ onOpenSpec
5200
5491
  }) {
5201
5492
  const open = useMemo(() => initialOpenGroups(inspection), [inspection]);
5202
5493
  const apply = (name, next, meta) => {
@@ -5243,6 +5534,7 @@ function NodePanel({
5243
5534
  },
5244
5535
  `${node.id}:${group.key}`
5245
5536
  )),
5537
+ node.component === "Fragment" ? /* @__PURE__ */ jsx(FragmentRefRow, { refId: node.props.id, pageIds, onOpenSpec }) : null,
5246
5538
  inspection.unknown.length > 0 ? /* @__PURE__ */ jsx(UnknownSection, { entries: inspection.unknown, ctx, onChange: apply }) : null,
5247
5539
  allowKeyEdit ? /* @__PURE__ */ jsx(KeyRow, { doc, node, onApply }) : null
5248
5540
  ] });
@@ -5624,10 +5916,10 @@ function indexChildren(doc) {
5624
5916
  function logicHint(node) {
5625
5917
  const condition = node.props.when ?? node.props.source;
5626
5918
  if (typeof condition === "string") return condition;
5627
- if (isPlainObject4(condition)) {
5919
+ if (isPlainObject5(condition)) {
5628
5920
  if (typeof condition.$state === "string") return `{${condition.$state}}`;
5629
5921
  if (typeof condition.$item === "string") return `{\uD589.${condition.$item}}`;
5630
- if (isPlainObject4(condition.$calc) && typeof condition.$calc.op === "string") {
5922
+ if (isPlainObject5(condition.$calc) && typeof condition.$calc.op === "string") {
5631
5923
  return `{\uACC4\uC0B0:${condition.$calc.op}}`;
5632
5924
  }
5633
5925
  }
@@ -5638,7 +5930,7 @@ function logicHint(node) {
5638
5930
  }
5639
5931
  return void 0;
5640
5932
  }
5641
- function isPlainObject4(v) {
5933
+ function isPlainObject5(v) {
5642
5934
  return typeof v === "object" && v !== null && !Array.isArray(v);
5643
5935
  }
5644
5936
  function childCount(child) {
@@ -5720,7 +6012,7 @@ function buildRows(doc, options) {
5720
6012
  }
5721
6013
 
5722
6014
  // src/builder/tree/search.ts
5723
- function isPlainObject5(v) {
6015
+ function isPlainObject6(v) {
5724
6016
  return typeof v === "object" && v !== null && !Array.isArray(v);
5725
6017
  }
5726
6018
  function collectBindings(value, out) {
@@ -5728,10 +6020,10 @@ function collectBindings(value, out) {
5728
6020
  for (const item of value) collectBindings(item, out);
5729
6021
  return;
5730
6022
  }
5731
- if (!isPlainObject5(value)) return;
6023
+ if (!isPlainObject6(value)) return;
5732
6024
  if (typeof value.$state === "string") out.push(value.$state);
5733
6025
  if (typeof value.$item === "string") out.push(value.$item);
5734
- if (isPlainObject5(value.$calc) && typeof value.$calc.op === "string") out.push(value.$calc.op);
6026
+ if (isPlainObject6(value.$calc) && typeof value.$calc.op === "string") out.push(value.$calc.op);
5735
6027
  if (typeof value.action === "string") out.push(value.action);
5736
6028
  if (typeof value.key === "string") out.push(value.key);
5737
6029
  if (typeof value.form === "string") out.push(value.form);
@@ -5746,7 +6038,7 @@ function collectLiterals(value, out) {
5746
6038
  for (const item of value) collectLiterals(item, out);
5747
6039
  return;
5748
6040
  }
5749
- if (!isPlainObject5(value)) return;
6041
+ if (!isPlainObject6(value)) return;
5750
6042
  for (const [name, sub] of Object.entries(value)) {
5751
6043
  if (name === "$state" || name === "$item" || name === "$index") continue;
5752
6044
  collectLiterals(sub, out);
@@ -7048,33 +7340,33 @@ function HistoryBar({
7048
7340
  }
7049
7341
 
7050
7342
  // src/builder/binding/values.ts
7051
- function isPlainObject6(v) {
7343
+ function isPlainObject7(v) {
7052
7344
  return typeof v === "object" && v !== null && !Array.isArray(v);
7053
7345
  }
7054
7346
  function classifyValue(value) {
7055
- if (!isPlainObject6(value)) return "literal";
7347
+ if (!isPlainObject7(value)) return "literal";
7056
7348
  if (typeof value.$state === "string") return "state";
7057
7349
  if ("$item" in value) return "item";
7058
7350
  if ("$index" in value) return "index";
7059
- if (isPlainObject6(value.$calc)) return "calc";
7351
+ if (isPlainObject7(value.$calc)) return "calc";
7060
7352
  return "literal";
7061
7353
  }
7062
7354
  function isBinding(value) {
7063
7355
  return classifyValue(value) !== "literal";
7064
7356
  }
7065
7357
  function asStateRef(value) {
7066
- if (classifyValue(value) !== "state" || !isPlainObject6(value)) return null;
7358
+ if (classifyValue(value) !== "state" || !isPlainObject7(value)) return null;
7067
7359
  const out = { $state: value.$state };
7068
7360
  if ("default" in value) out.default = value.default;
7069
7361
  return out;
7070
7362
  }
7071
7363
  function asItemRef(value) {
7072
- if (classifyValue(value) !== "item" || !isPlainObject6(value)) return null;
7364
+ if (classifyValue(value) !== "item" || !isPlainObject7(value)) return null;
7073
7365
  const raw = value.$item;
7074
7366
  return { $item: typeof raw === "string" ? raw : true };
7075
7367
  }
7076
7368
  function asCalc(value) {
7077
- if (classifyValue(value) !== "calc" || !isPlainObject6(value)) return null;
7369
+ if (classifyValue(value) !== "calc" || !isPlainObject7(value)) return null;
7078
7370
  const calc = value.$calc;
7079
7371
  const out = {
7080
7372
  op: typeof calc.op === "string" ? calc.op : "",
@@ -7349,7 +7641,7 @@ function writeActionList(list) {
7349
7641
  return all;
7350
7642
  }
7351
7643
  function isActionObject(value) {
7352
- return isPlainObject6(value) && typeof value.action === "string";
7644
+ return isPlainObject7(value) && typeof value.action === "string";
7353
7645
  }
7354
7646
  function actionNameOf(action) {
7355
7647
  return typeof action.action === "string" ? action.action : "";
@@ -7416,7 +7708,7 @@ function formatValue(value, depth = 0) {
7416
7708
  if (value.length === 0) return "[]";
7417
7709
  return `[${value.map((item) => formatValue(item, depth + 1)).join(", ")}]`;
7418
7710
  }
7419
- if (isPlainObject6(value)) {
7711
+ if (isPlainObject7(value)) {
7420
7712
  const keys = Object.keys(value);
7421
7713
  if (keys.length === 0) return "{}";
7422
7714
  return `{ ${keys.slice(0, 3).join(", ")}${keys.length > 3 ? ", \u2026" : ""} }`;
@@ -7816,7 +8108,7 @@ function LiteralInput({
7816
8108
  }
7817
8109
  }
7818
8110
  function effectiveKind(value, meta) {
7819
- if (Array.isArray(value) || isPlainObject6(value)) return "json";
8111
+ if (Array.isArray(value) || isPlainObject7(value)) return "json";
7820
8112
  if (typeof value === "string" && value.includes("\n")) return "multiline";
7821
8113
  switch (meta?.kind) {
7822
8114
  case "number":
@@ -8421,7 +8713,7 @@ function dialogKeysOf(doc, catalog) {
8421
8713
  return [...found.values()].sort((a, b) => a.value.localeCompare(b.value, "ko"));
8422
8714
  }
8423
8715
  function gateKeyOf(when) {
8424
- if (classifyValue(when) !== "state" || !isPlainObject6(when)) return null;
8716
+ if (classifyValue(when) !== "state" || !isPlainObject7(when)) return null;
8425
8717
  const key = when.$state;
8426
8718
  return typeof key === "string" && key ? key : null;
8427
8719
  }
@@ -8493,7 +8785,7 @@ function visitActions(value, visit) {
8493
8785
  for (const item of value) visitActions(item, visit);
8494
8786
  return;
8495
8787
  }
8496
- if (!isPlainObject6(value)) return;
8788
+ if (!isPlainObject7(value)) return;
8497
8789
  if (typeof value.action !== "string") return;
8498
8790
  visit(value);
8499
8791
  visitActions(value.onSuccess, visit);
@@ -8550,12 +8842,12 @@ function itemFieldsOf(doc, forEachId) {
8550
8842
  function sampleRowOf(source) {
8551
8843
  const pick = (v) => {
8552
8844
  if (!Array.isArray(v)) return null;
8553
- const first = v.find((row) => isPlainObject6(row));
8554
- return isPlainObject6(first) ? first : null;
8845
+ const first = v.find((row) => isPlainObject7(row));
8846
+ return isPlainObject7(first) ? first : null;
8555
8847
  };
8556
8848
  const direct = pick(source);
8557
8849
  if (direct) return direct;
8558
- if (classifyValue(source) === "state" && isPlainObject6(source)) {
8850
+ if (classifyValue(source) === "state" && isPlainObject7(source)) {
8559
8851
  return pick(source.default);
8560
8852
  }
8561
8853
  return null;
@@ -8566,7 +8858,7 @@ function collectItemFields(children, out) {
8566
8858
  for (const item of value) walkValue(item);
8567
8859
  return;
8568
8860
  }
8569
- if (!isPlainObject6(value)) return;
8861
+ if (!isPlainObject7(value)) return;
8570
8862
  const ref = asItemRef(value);
8571
8863
  if (ref && typeof ref.$item === "string" && ref.$item) out.add(ref.$item);
8572
8864
  for (const [name, sub] of Object.entries(value)) {
@@ -8575,7 +8867,7 @@ function collectItemFields(children, out) {
8575
8867
  }
8576
8868
  };
8577
8869
  const walkChild = (child) => {
8578
- if (!isPlainObject6(child) || child.kind !== "node") return;
8870
+ if (!isPlainObject7(child) || child.kind !== "node") return;
8579
8871
  const node = child;
8580
8872
  for (const value of Object.values(node.props)) walkValue(value);
8581
8873
  for (const list of Object.values(node.slots)) list.forEach(walkChild);
@@ -8774,7 +9066,7 @@ function ActionRow({
8774
9066
  value: action,
8775
9067
  "aria-label": "\uC561\uC158 JSON",
8776
9068
  onChange: (next) => {
8777
- if (isPlainObject6(next) && typeof next.action === "string") onChange(next);
9069
+ if (isPlainObject7(next) && typeof next.action === "string") onChange(next);
8778
9070
  }
8779
9071
  }
8780
9072
  )
@@ -8793,7 +9085,7 @@ function ActionRow({
8793
9085
  onChange: (next) => {
8794
9086
  const kept = { action: name };
8795
9087
  for (const field of meta.fields) if (field.name in action) kept[field.name] = action[field.name];
8796
- onChange(isPlainObject6(next) ? { ...kept, ...next } : kept);
9088
+ onChange(isPlainObject7(next) ? { ...kept, ...next } : kept);
8797
9089
  }
8798
9090
  }
8799
9091
  )
@@ -8984,17 +9276,17 @@ function IndexField({
8984
9276
  }
8985
9277
  );
8986
9278
  }
8987
- function isPlainObject7(value) {
9279
+ function isPlainObject8(value) {
8988
9280
  return typeof value === "object" && value !== null && !Array.isArray(value);
8989
9281
  }
8990
9282
  function canon(value) {
8991
9283
  if (Array.isArray(value)) return value.map(canon);
8992
- if (!isPlainObject7(value)) return value;
9284
+ if (!isPlainObject8(value)) return value;
8993
9285
  const out = {};
8994
9286
  for (const key of Object.keys(value).sort()) {
8995
9287
  const v = value[key];
8996
9288
  if (key === "children" && Array.isArray(v) && v.length === 0) continue;
8997
- if (key === "slots" && isPlainObject7(v)) {
9289
+ if (key === "slots" && isPlainObject8(v)) {
8998
9290
  const slots = {};
8999
9291
  for (const name of Object.keys(v).sort()) {
9000
9292
  const list = v[name];
@@ -9019,7 +9311,7 @@ function diffPaths(a, b, path, out) {
9019
9311
  a.forEach((item, i) => diffPaths(item, b[i], `${path}[${i}]`, out));
9020
9312
  return;
9021
9313
  }
9022
- if (isPlainObject7(a) && isPlainObject7(b)) {
9314
+ if (isPlainObject8(a) && isPlainObject8(b)) {
9023
9315
  for (const key of /* @__PURE__ */ new Set([...Object.keys(a), ...Object.keys(b)])) {
9024
9316
  if (out.length >= 6) return;
9025
9317
  if (!(key in b)) out.push(`${path}.${key} \u2014 \uBE4C\uB354\uC5D0\uC11C \uC0AC\uB77C\uC9D1\uB2C8\uB2E4`);
@@ -9034,7 +9326,7 @@ function sameSpecMeaning(a, b) {
9034
9326
  return JSON.stringify(canon(a)) === JSON.stringify(canon(b));
9035
9327
  }
9036
9328
  function builderFidelity(spec) {
9037
- if (!isPlainObject7(spec)) {
9329
+ if (!isPlainObject8(spec)) {
9038
9330
  return {
9039
9331
  ok: false,
9040
9332
  issues: [
@@ -9185,6 +9477,11 @@ function BuilderView({
9185
9477
  onOpenJson,
9186
9478
  onSave,
9187
9479
  pageIds,
9480
+ onOpenSpec,
9481
+ useSpec,
9482
+ previewFetch,
9483
+ dataArmed,
9484
+ canvasInitialState,
9188
9485
  registry,
9189
9486
  catalog,
9190
9487
  can,
@@ -9419,7 +9716,11 @@ function BuilderView({
9419
9716
  hover: state.hover,
9420
9717
  dispatch,
9421
9718
  registry: canvasRegistry,
9422
- can
9719
+ can,
9720
+ useSpec,
9721
+ previewFetch,
9722
+ dataArmed,
9723
+ initialState: canvasInitialState
9423
9724
  }
9424
9725
  ) }) : null,
9425
9726
  showRight ? /* @__PURE__ */ jsx("div", { className: "sdui-bv__right", children: /* @__PURE__ */ jsx(
@@ -9429,7 +9730,9 @@ function BuilderView({
9429
9730
  catalog: canvasCatalog,
9430
9731
  selection: state.selection,
9431
9732
  onApply: (change) => dispatch({ type: "apply", ...change }),
9432
- onRequestBinding: setBindingProp
9733
+ onRequestBinding: setBindingProp,
9734
+ pageIds,
9735
+ onOpenSpec
9433
9736
  }
9434
9737
  ) }) : null
9435
9738
  ] }),
@@ -9498,6 +9801,227 @@ function labelOf2(doc, ids, catalog) {
9498
9801
  const first = ids[0] ? getNode(doc, ids[0]) : null;
9499
9802
  return first ? describeChild(first, catalog).label : "\uB178\uB4DC";
9500
9803
  }
9804
+ function PreviewSeedPanel({
9805
+ specId,
9806
+ autoSeeds,
9807
+ stateKeys,
9808
+ value,
9809
+ onChange,
9810
+ previewEntitySearch,
9811
+ canFetch,
9812
+ onClose
9813
+ }) {
9814
+ const listId = useId();
9815
+ const autoKeys = Object.keys(autoSeeds);
9816
+ const params = patternParams(specId);
9817
+ const patch = (next) => onChange({ ...value, ...next });
9818
+ const patchRow = (index, row) => patch({ manual: value.manual.map((m, i) => i === index ? { ...m, ...row } : m) });
9819
+ const argsBroken = (() => {
9820
+ if (!value.argsOn) return false;
9821
+ try {
9822
+ const parsed = JSON.parse(value.argsText);
9823
+ return typeof parsed !== "object" || parsed === null || Array.isArray(parsed);
9824
+ } catch {
9825
+ return true;
9826
+ }
9827
+ })();
9828
+ const pickEntity = (param, picked, option) => {
9829
+ const key = `route.${param}`;
9830
+ const rest = value.manual.filter((row) => row.key !== key);
9831
+ const labels = { ...value.entityLabels };
9832
+ if (picked === null) {
9833
+ delete labels[param];
9834
+ patch({ manual: rest, entityLabels: labels });
9835
+ return;
9836
+ }
9837
+ labels[param] = typeof option?.label === "string" ? option.label : picked;
9838
+ patch({
9839
+ manual: [...rest, { key, text: JSON.stringify(picked) }],
9840
+ entityLabels: labels,
9841
+ dataArmed: true
9842
+ });
9843
+ };
9844
+ const entityValueOf = (param) => {
9845
+ const row = value.manual.find((m) => m.key === `route.${param}`);
9846
+ if (!row) return "";
9847
+ const parsed = parseSeedValue(row.text);
9848
+ return typeof parsed === "string" ? parsed : String(parsed ?? "");
9849
+ };
9850
+ return /* @__PURE__ */ jsxs("div", { className: "sdui-editor-seedpanel", children: [
9851
+ /* @__PURE__ */ jsxs("div", { className: "sdui-editor-seedpanel__head", children: [
9852
+ /* @__PURE__ */ jsx("span", { className: "wp-overline", children: "\uBBF8\uB9AC\uBCF4\uAE30 \uC2DC\uB529" }),
9853
+ /* @__PURE__ */ jsx("span", { className: "wp-spacer" }),
9854
+ /* @__PURE__ */ jsx(IconButton, { icon: "x", label: "\uD328\uB110 \uB2EB\uAE30", size: "sm", onClick: onClose })
9855
+ ] }),
9856
+ /* @__PURE__ */ jsx("p", { className: "sdui-editor-seedpanel__note", children: "\uC2DC\uB4DC\uB97C \uBC14\uAFB8\uBA74 \uBBF8\uB9AC\uBCF4\uAE30\uAC00 \uB2E4\uC2DC \uB9C8\uC6B4\uD2B8\uB418\uC5B4, \uBBF8\uB9AC\uBCF4\uAE30 \uC548\uC5D0\uC11C \uC785\uB825 \uC911\uC774\uB358 \uAC12\uC740 \uCD08\uAE30\uD654\uB429\uB2C8\uB2E4." }),
9857
+ autoKeys.length > 0 ? /* @__PURE__ */ jsxs("section", { className: "sdui-editor-seedpanel__sec", children: [
9858
+ /* @__PURE__ */ jsx(
9859
+ Switch,
9860
+ {
9861
+ size: "sm",
9862
+ checked: value.autoOpen,
9863
+ label: `\uB2E4\uC774\uC5BC\uB85C\uADF8 \uC5F4\uB9BC \uC2DC\uB529 (${autoKeys.map((k) => `${k}=true`).join(", ")})`,
9864
+ onChange: (e) => patch({ autoOpen: e.target.checked })
9865
+ }
9866
+ ),
9867
+ /* @__PURE__ */ jsxs("p", { className: "sdui-editor-seedpanel__desc", children: [
9868
+ "\uCD5C\uC0C1\uC704 ",
9869
+ /* @__PURE__ */ jsx("code", { children: "Show(when: $state)" }),
9870
+ " \uC544\uB798\uC758 \uB2E4\uC774\uC5BC\uB85C\uADF8\uB97C \uC5F4\uC5B4 \uB461\uB2C8\uB2E4. \uB044\uBA74 \uC2E4\uC81C \uCD08\uAE30 \uC0C1\uD0DC(\uB2EB\uD798)\uB85C \uBCF4\uC785\uB2C8\uB2E4."
9871
+ ] })
9872
+ ] }) : null,
9873
+ /* @__PURE__ */ jsxs("section", { className: "sdui-editor-seedpanel__sec", children: [
9874
+ /* @__PURE__ */ jsx("span", { className: "sdui-editor-seedpanel__label", children: "\uC0C1\uD0DC \uC2DC\uB4DC (\uD0A4 \u2192 \uAC12)" }),
9875
+ value.manual.map((row, index) => /* @__PURE__ */ jsxs("div", { className: "sdui-editor-seedpanel__row", children: [
9876
+ /* @__PURE__ */ jsx(
9877
+ Input,
9878
+ {
9879
+ size: "sm",
9880
+ mono: true,
9881
+ placeholder: "\uC0C1\uD0DC \uD0A4",
9882
+ value: row.key,
9883
+ list: stateKeys.length > 0 ? listId : void 0,
9884
+ onChange: (e) => patchRow(index, { key: e.target.value })
9885
+ }
9886
+ ),
9887
+ /* @__PURE__ */ jsx(
9888
+ Input,
9889
+ {
9890
+ size: "sm",
9891
+ mono: true,
9892
+ placeholder: "\uAC12 (JSON \u2014 \uBABB \uC77D\uC73C\uBA74 \uBB38\uC790\uC5F4)",
9893
+ value: row.text,
9894
+ onChange: (e) => patchRow(index, { text: e.target.value })
9895
+ }
9896
+ ),
9897
+ /* @__PURE__ */ jsx(
9898
+ IconButton,
9899
+ {
9900
+ icon: "trash-2",
9901
+ label: "\uC2DC\uB4DC \uC0AD\uC81C",
9902
+ size: "sm",
9903
+ onClick: () => patch({ manual: value.manual.filter((_, i) => i !== index) })
9904
+ }
9905
+ )
9906
+ ] }, index)),
9907
+ stateKeys.length > 0 ? /* @__PURE__ */ jsx("datalist", { id: listId, children: stateKeys.map((key) => /* @__PURE__ */ jsx("option", { value: key }, key)) }) : null,
9908
+ /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(
9909
+ Button,
9910
+ {
9911
+ size: "xs",
9912
+ icon: "plus",
9913
+ onClick: () => patch({ manual: [...value.manual, { key: "", text: "" }] }),
9914
+ children: "\uC2DC\uB4DC \uCD94\uAC00"
9915
+ }
9916
+ ) })
9917
+ ] }),
9918
+ /* @__PURE__ */ jsxs("section", { className: "sdui-editor-seedpanel__sec", children: [
9919
+ /* @__PURE__ */ jsx(
9920
+ Switch,
9921
+ {
9922
+ size: "sm",
9923
+ checked: value.argsOn,
9924
+ label: "args \uC0D8\uD50C \uC8FC\uC785 \u2014 Fragment\uB85C \uAC10\uC2F8 \uBBF8\uB9AC\uBCF4\uAE30",
9925
+ onChange: (e) => patch({ argsOn: e.target.checked })
9926
+ }
9927
+ ),
9928
+ /* @__PURE__ */ jsxs("p", { className: "sdui-editor-seedpanel__desc", children: [
9929
+ "\uC774 \uC2A4\uD399\uC744 ",
9930
+ /* @__PURE__ */ jsxs("code", { children: [
9931
+ '{"component":"Fragment","props":{"id":"',
9932
+ specId,
9933
+ '","args":\u2026}}'
9934
+ ] }),
9935
+ "\uC73C\uB85C \uAC10\uC2FC \uC784\uC2DC \uD398\uC774\uC9C0\uB97C \uADF8\uB9BD\uB2C8\uB2E4 \u2014 \uC870\uAC01 \uC548\uC758 ",
9936
+ /* @__PURE__ */ jsx("code", { children: "$arg" }),
9937
+ "\uAC00 \uC2E4\uC81C \uACBD\uB85C\uB85C \uD480\uB9BD\uB2C8\uB2E4."
9938
+ ] }),
9939
+ value.argsOn ? /* @__PURE__ */ jsxs(Fragment, { children: [
9940
+ /* @__PURE__ */ jsx(
9941
+ "textarea",
9942
+ {
9943
+ className: "sdui-editor-seedpanel__json",
9944
+ rows: 4,
9945
+ spellCheck: false,
9946
+ value: value.argsText,
9947
+ onChange: (e) => patch({ argsText: e.target.value })
9948
+ }
9949
+ ),
9950
+ argsBroken ? /* @__PURE__ */ jsx("p", { className: "sdui-editor-seedpanel__err", children: "args\uB97C \uD3C9\uBA74 \uC624\uBE0C\uC81D\uD2B8 JSON\uC73C\uB85C \uC77D\uC9C0 \uBABB\uD574 \uB798\uD37C\uB97C \uC801\uC6A9\uD558\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4 \u2014 \uC6D0\uBCF8 \uADF8\uB300\uB85C \uBBF8\uB9AC\uBD05\uB2C8\uB2E4." }) : null
9951
+ ] }) : null
9952
+ ] }),
9953
+ /* @__PURE__ */ jsxs("section", { className: "sdui-editor-seedpanel__sec", children: [
9954
+ /* @__PURE__ */ jsx(
9955
+ Switch,
9956
+ {
9957
+ size: "sm",
9958
+ checked: value.dataArmed,
9959
+ disabled: !canFetch,
9960
+ label: "\uC2E4\uB370\uC774\uD130 \uC870\uD68C (GET\uB9CC \uAC1C\uBC29)",
9961
+ onChange: (e) => patch({ dataArmed: e.target.checked })
9962
+ }
9963
+ ),
9964
+ /* @__PURE__ */ jsx("p", { className: "sdui-editor-seedpanel__desc", children: canFetch ? "\uCF1C\uBA74 page.data\uC758 GET \uC870\uD68C\uB9CC \uD3B8\uC9D1\uC790 \uC138\uC158\uC73C\uB85C \uB098\uAC11\uB2C8\uB2E4. \uC800\uC7A5\xB7\uC0AD\uC81C\xB7\uB2E4\uC6B4\uB85C\uB4DC\xB7\uAD6C\uB3C5\uC740 \uACC4\uC18D \uCC28\uB2E8\uB418\uACE0, \uC790\uCCB4 \uC870\uD68C \uC704\uC82F(\uC6D0\uACA9 \uBAA9\uB85D \uB4F1)\uC740 sandbox \uC120\uC5B8\uC744 \uBCF4\uACE0 \uBE48 \uD45C\uB85C \uB0A8\uC2B5\uB2C8\uB2E4." : "\uD638\uC2A4\uD2B8\uAC00 previewFetch\uB97C \uC8FC\uC785\uD558\uC9C0 \uC54A\uC544 \uC774 \uD3B8\uC9D1\uAE30\uC5D0\uC11C\uB294 \uC2E4\uB370\uC774\uD130\uB97C \uBCFC \uC218 \uC5C6\uC2B5\uB2C8\uB2E4." }),
9965
+ params.length > 0 ? previewEntitySearch && canFetch ? params.map((param) => /* @__PURE__ */ jsxs("div", { className: "sdui-editor-seedpanel__entity", children: [
9966
+ /* @__PURE__ */ jsx("span", { className: "sdui-editor-seedpanel__label wp-mono-xs", children: `route.${param}` }),
9967
+ /* @__PURE__ */ jsx(
9968
+ AutocompleteField,
9969
+ {
9970
+ placeholder: `${param} \uAC80\uC0C9\uC73C\uB85C \uCC44\uC6B0\uAE30`,
9971
+ minChars: 1,
9972
+ subKey: "sublabel",
9973
+ value: entityValueOf(param),
9974
+ selectedLabel: value.entityLabels[param] ?? entityValueOf(param),
9975
+ search: async (keyword, signal) => (await previewEntitySearch(specId, param, keyword, signal)).map(
9976
+ (option) => ({ ...option })
9977
+ ),
9978
+ onChange: (picked, option) => pickEntity(param, picked, option)
9979
+ }
9980
+ )
9981
+ ] }, param)) : /* @__PURE__ */ jsxs("p", { className: "sdui-editor-seedpanel__desc", children: [
9982
+ `\uD328\uD134 \uD30C\uB77C\uBBF8\uD130(${params.map((p) => `{${p}}`).join(", ")})\uAC00 \uC788\uB294 \uC2A4\uD399\uC785\uB2C8\uB2E4 \u2014 `,
9983
+ previewEntitySearch ? "\uC2E4\uB370\uC774\uD130\uB97C \uCF1C\uBA74 \uAC80\uC0C9\uC73C\uB85C \uCC44\uC6B8 \uC218 \uC788\uC2B5\uB2C8\uB2E4." : "\uD638\uC2A4\uD2B8\uAC00 previewEntitySearch\uB97C \uC8FC\uC785\uD558\uBA74 \uAC80\uC0C9\uC73C\uB85C \uCC44\uC6B8 \uC218 \uC788\uC2B5\uB2C8\uB2E4. \uC9C0\uAE08\uC740 \uC704 \uC0C1\uD0DC \uC2DC\uB4DC\uC5D0 route.* \uD0A4\uB97C \uC9C1\uC811 \uB123\uC73C\uC138\uC694."
9984
+ ] }) : null
9985
+ ] })
9986
+ ] });
9987
+ }
9988
+ function isPlainObject9(value) {
9989
+ return typeof value === "object" && value !== null && !Array.isArray(value);
9990
+ }
9991
+ function createEditorUseSpec(store, getOverride) {
9992
+ const cache = /* @__PURE__ */ new Map();
9993
+ const waiters = /* @__PURE__ */ new Map();
9994
+ return function useEditorSpec(id) {
9995
+ const [, bump] = useReducer((n) => n + 1, 0);
9996
+ useEffect(() => {
9997
+ if (cache.has(id)) return void 0;
9998
+ let set = waiters.get(id);
9999
+ const first = !set;
10000
+ if (!set) {
10001
+ set = /* @__PURE__ */ new Set();
10002
+ waiters.set(id, set);
10003
+ }
10004
+ set.add(bump);
10005
+ if (first) {
10006
+ store.read(id).then((record) => {
10007
+ cache.set(id, record && isPlainObject9(record.spec) ? record.spec : null);
10008
+ }).catch(() => {
10009
+ cache.set(id, null);
10010
+ }).then(() => {
10011
+ const current = waiters.get(id);
10012
+ waiters.delete(id);
10013
+ current?.forEach((notify) => notify());
10014
+ });
10015
+ }
10016
+ return () => {
10017
+ waiters.get(id)?.delete(bump);
10018
+ };
10019
+ }, [id, bump]);
10020
+ const override = getOverride?.(id);
10021
+ if (override !== void 0) return override;
10022
+ return cache.has(id) ? cache.get(id) : void 0;
10023
+ };
10024
+ }
9501
10025
  var PreviewBoundary = class extends React.Component {
9502
10026
  state = { error: null };
9503
10027
  static getDerivedStateFromError(error) {
@@ -9538,7 +10062,17 @@ function pruneKnownComponents(issues, registry) {
9538
10062
  return !Object.hasOwn(registry, issue.message.slice(UNKNOWN_COMPONENT.length));
9539
10063
  });
9540
10064
  }
9541
- function EditorView2({ store, id, onBack, onPreview, registry, catalog }) {
10065
+ function EditorView2({
10066
+ store,
10067
+ id,
10068
+ onBack,
10069
+ onPreview,
10070
+ registry,
10071
+ catalog,
10072
+ onOpenSpec,
10073
+ previewFetch,
10074
+ previewEntitySearch
10075
+ }) {
9542
10076
  const caps = store.capabilities;
9543
10077
  const draftKey = `sdui-draft:${id}`;
9544
10078
  const renderRegistry = registry ?? editorRegistry;
@@ -9560,6 +10094,12 @@ function EditorView2({ store, id, onBack, onPreview, registry, catalog }) {
9560
10094
  const [previewKey, setPreviewKey] = useState(0);
9561
10095
  const [previewNotice, setPreviewNotice] = useState(null);
9562
10096
  const noticeTimer = useRef(null);
10097
+ const [seedSetup, setSeedSetup] = useState(INITIAL_PREVIEW_SETUP);
10098
+ const [seedPanelOpen, setSeedPanelOpen] = useState(false);
10099
+ useEffect(() => {
10100
+ setSeedSetup(INITIAL_PREVIEW_SETUP);
10101
+ setSeedPanelOpen(false);
10102
+ }, [id]);
9563
10103
  const [mode, setMode] = useState("json");
9564
10104
  const [modeNotice, setModeNotice] = useState(null);
9565
10105
  const [builderSpec, setBuilderSpec] = useState(null);
@@ -9830,10 +10370,37 @@ function EditorView2({ store, id, onBack, onPreview, registry, catalog }) {
9830
10370
  if (noticeTimer.current) clearTimeout(noticeTimer.current);
9831
10371
  noticeTimer.current = setTimeout(() => setPreviewNotice(null), 2500);
9832
10372
  }, []);
10373
+ const parsedRef = useRef(parsed);
10374
+ parsedRef.current = parsed;
10375
+ const overrideSpec = useCallback(
10376
+ (specId) => specId === id ? parsedRef.current.spec ?? void 0 : void 0,
10377
+ [id]
10378
+ );
10379
+ const useSpec = useMemo(() => createEditorUseSpec(store, overrideSpec), [store, overrideSpec]);
9833
10380
  const previewAdapter = useMemo(
9834
- () => createCanvasAdapter(notifyPreview),
9835
- [notifyPreview]
10381
+ () => resolvePreviewAdapter({
10382
+ armed: seedSetup.dataArmed,
10383
+ previewFetch,
10384
+ onBlocked: notifyPreview,
10385
+ useSpec
10386
+ }),
10387
+ [seedSetup.dataArmed, previewFetch, notifyPreview, useSpec]
9836
10388
  );
10389
+ const autoSeeds = useMemo(
10390
+ () => isDialogSpecId(id) && parsed.spec ? dialogOpenSeeds(parsed.spec) : {},
10391
+ [id, parsed.spec]
10392
+ );
10393
+ const seeds = useMemo(() => seedsOf(seedSetup, autoSeeds), [seedSetup, autoSeeds]);
10394
+ const seededOpen = seedSetup.autoOpen && Object.keys(autoSeeds).length > 0;
10395
+ const previewStateKeys = useMemo(() => collectStateKeys(parsed.spec), [parsed.spec]);
10396
+ const previewPage = useMemo(() => {
10397
+ if (!parsed.spec) return null;
10398
+ if (seedSetup.argsOn) {
10399
+ const wrapped = argsWrapperPage(id, seedSetup.argsText);
10400
+ if (wrapped) return wrapped.page;
10401
+ }
10402
+ return parsed.spec;
10403
+ }, [parsed.spec, seedSetup.argsOn, seedSetup.argsText, id]);
9837
10404
  if (!id) {
9838
10405
  return /* @__PURE__ */ jsxs("div", { className: "sdui-editor-page sdui-base", children: [
9839
10406
  /* @__PURE__ */ jsx(Alert, { tone: "warning", title: "\uD398\uC774\uC9C0 id\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4", children: "\uD3B8\uC9D1\uD560 \uD398\uC774\uC9C0\uB97C \uBAA9\uB85D\uC5D0\uC11C \uC120\uD0DD\uD558\uC138\uC694." }),
@@ -9945,6 +10512,11 @@ function EditorView2({ store, id, onBack, onPreview, registry, catalog }) {
9945
10512
  onOpenJson: openJson,
9946
10513
  onSave: () => void save(),
9947
10514
  pageIds,
10515
+ onOpenSpec,
10516
+ useSpec,
10517
+ previewFetch,
10518
+ dataArmed: seedSetup.dataArmed,
10519
+ canvasInitialState: seeds,
9948
10520
  registry: renderRegistry,
9949
10521
  catalog
9950
10522
  }
@@ -9981,18 +10553,43 @@ function EditorView2({ store, id, onBack, onPreview, registry, catalog }) {
9981
10553
  /* @__PURE__ */ jsxs("div", { className: "sdui-editor-preview", children: [
9982
10554
  /* @__PURE__ */ jsxs("div", { className: "sdui-editor-preview__head", children: [
9983
10555
  /* @__PURE__ */ jsx("span", { className: "wp-overline", children: "\uBBF8\uB9AC\uBCF4\uAE30" }),
9984
- previewNotice ? /* @__PURE__ */ jsx("span", { className: "sdui-editor-preview__notice", children: previewNotice }) : null
10556
+ seededOpen ? /* @__PURE__ */ jsx(Badge, { tone: "brand", children: "\uC5F4\uB9BC \uC2DC\uB529" }) : null,
10557
+ seedSetup.dataArmed && previewFetch ? /* @__PURE__ */ jsx(Badge, { tone: "warning", children: "\uC2E4\uB370\uC774\uD130" }) : null,
10558
+ previewNotice ? /* @__PURE__ */ jsx("span", { className: "sdui-editor-preview__notice", children: previewNotice }) : null,
10559
+ /* @__PURE__ */ jsx("span", { className: "wp-spacer" }),
10560
+ /* @__PURE__ */ jsx(Button, { size: "xs", icon: "sliders-horizontal", onClick: () => setSeedPanelOpen((v) => !v), children: "\uC2DC\uB529" })
9985
10561
  ] }),
9986
- /* @__PURE__ */ jsx("div", { className: "sdui-editor-preview__body", children: parsed.spec ? /* @__PURE__ */ jsx(PreviewBoundary, { resetKey: previewKey, signature: debounced, children: /* @__PURE__ */ jsx(
9987
- SduiRenderer,
10562
+ seedPanelOpen ? /* @__PURE__ */ jsx(
10563
+ PreviewSeedPanel,
9988
10564
  {
9989
- page: parsed.spec,
9990
- registry: renderRegistry,
9991
- adapter: previewAdapter,
9992
- syncUrlState: false
9993
- },
9994
- previewKey
9995
- ) }) : /* @__PURE__ */ jsx(Alert, { tone: parsed.error ? "warning" : "neutral", children: parsed.error ? "JSON\uC744 \uACE0\uCE58\uBA74 \uBBF8\uB9AC\uBCF4\uAE30\uAC00 \uB2E4\uC2DC \uADF8\uB824\uC9D1\uB2C8\uB2E4." : "\uC2A4\uD399\uC744 \uC785\uB825\uD558\uC138\uC694." }) })
10565
+ specId: id,
10566
+ autoSeeds,
10567
+ stateKeys: previewStateKeys,
10568
+ value: seedSetup,
10569
+ onChange: setSeedSetup,
10570
+ previewEntitySearch,
10571
+ canFetch: previewFetch !== void 0,
10572
+ onClose: () => setSeedPanelOpen(false)
10573
+ }
10574
+ ) : null,
10575
+ /* @__PURE__ */ jsx("div", { className: "sdui-editor-preview__body", children: previewPage ? /* @__PURE__ */ jsx(
10576
+ PreviewBoundary,
10577
+ {
10578
+ resetKey: previewKey,
10579
+ signature: `${debounced}:${seedSignature(seeds)}:${seedSetup.argsOn ? seedSetup.argsText : ""}`,
10580
+ children: /* @__PURE__ */ jsx(
10581
+ SduiRenderer,
10582
+ {
10583
+ page: previewPage,
10584
+ registry: renderRegistry,
10585
+ adapter: previewAdapter,
10586
+ initialState: seeds,
10587
+ syncUrlState: false
10588
+ },
10589
+ `${previewKey}:${seedSignature(seeds)}`
10590
+ )
10591
+ }
10592
+ ) : /* @__PURE__ */ jsx(Alert, { tone: parsed.error ? "warning" : "neutral", children: parsed.error ? "JSON\uC744 \uACE0\uCE58\uBA74 \uBBF8\uB9AC\uBCF4\uAE30\uAC00 \uB2E4\uC2DC \uADF8\uB824\uC9D1\uB2C8\uB2E4." : "\uC2A4\uD399\uC744 \uC785\uB825\uD558\uC138\uC694." }) })
9996
10593
  ] }),
9997
10594
  helpOpen ? /* @__PURE__ */ jsx(HelpPanel, { onClose: () => setHelpOpen(false) }) : null
9998
10595
  ] })
@@ -10021,7 +10618,9 @@ function ViewerView({
10021
10618
  onNavigate,
10022
10619
  registry = editorRegistry,
10023
10620
  adapter: hostAdapter,
10024
- can
10621
+ can,
10622
+ previewFetch,
10623
+ previewEntitySearch
10025
10624
  }) {
10026
10625
  const [state, setState] = useState({
10027
10626
  spec: null,
@@ -10055,9 +10654,46 @@ function ViewerView({
10055
10654
  useEffect(() => () => {
10056
10655
  if (blockedTimer.current) clearTimeout(blockedTimer.current);
10057
10656
  }, []);
10657
+ const [seedSetup, setSeedSetup] = useState(INITIAL_PREVIEW_SETUP);
10658
+ const [seedPanelOpen, setSeedPanelOpen] = useState(false);
10659
+ useEffect(() => {
10660
+ setSeedSetup(INITIAL_PREVIEW_SETUP);
10661
+ setSeedPanelOpen(false);
10662
+ }, [id]);
10663
+ const autoSeeds = useMemo(
10664
+ () => isDialogSpecId(id) && state.spec ? dialogOpenSeeds(state.spec) : {},
10665
+ [id, state.spec]
10666
+ );
10667
+ const seeds = useMemo(() => seedsOf(seedSetup, autoSeeds), [seedSetup, autoSeeds]);
10668
+ const seededOpen = seedSetup.autoOpen && Object.keys(autoSeeds).length > 0;
10669
+ const previewStateKeys = useMemo(() => collectStateKeys(state.spec), [state.spec]);
10670
+ const viewPage = useMemo(() => {
10671
+ if (!state.spec) return null;
10672
+ if (seedSetup.argsOn) {
10673
+ const wrapped = argsWrapperPage(id, seedSetup.argsText);
10674
+ if (wrapped) return wrapped.page;
10675
+ }
10676
+ return state.spec;
10677
+ }, [state.spec, seedSetup.argsOn, seedSetup.argsText, id]);
10678
+ const specRef = useRef(state.spec);
10679
+ specRef.current = state.spec;
10680
+ const overrideSpec = useCallback(
10681
+ (specId) => specId === id ? specRef.current ?? void 0 : void 0,
10682
+ [id]
10683
+ );
10684
+ const useSpec = useMemo(() => createEditorUseSpec(store, overrideSpec), [store, overrideSpec]);
10058
10685
  const adapter = useMemo(
10059
- () => hostAdapter ?? { ...createCanvasAdapter(notifyBlocked), navigate: onNavigate, refresh: load },
10060
- [hostAdapter, notifyBlocked, onNavigate, load]
10686
+ () => hostAdapter ?? {
10687
+ ...resolvePreviewAdapter({
10688
+ armed: seedSetup.dataArmed,
10689
+ previewFetch,
10690
+ onBlocked: notifyBlocked,
10691
+ useSpec
10692
+ }),
10693
+ navigate: onNavigate,
10694
+ refresh: load
10695
+ },
10696
+ [hostAdapter, notifyBlocked, onNavigate, load, seedSetup.dataArmed, previewFetch, useSpec]
10061
10697
  );
10062
10698
  useEffect(() => {
10063
10699
  if (state.spec?.title) {
@@ -10076,10 +10712,26 @@ function ViewerView({
10076
10712
  "/",
10077
10713
  id
10078
10714
  ] }),
10715
+ seededOpen ? /* @__PURE__ */ jsx(Badge, { tone: "brand", children: "\uC5F4\uB9BC\uC744 \uC2DC\uB529\uD588\uC2B5\uB2C8\uB2E4" }) : null,
10716
+ seedSetup.dataArmed && previewFetch && !hostAdapter ? /* @__PURE__ */ jsx(Badge, { tone: "warning", children: "\uC2E4\uB370\uC774\uD130" }) : null,
10079
10717
  /* @__PURE__ */ jsx("span", { className: "wp-spacer" }),
10080
10718
  blocked ? /* @__PURE__ */ jsx("span", { className: "sdui-editor-preview__notice", children: blocked }) : null,
10719
+ /* @__PURE__ */ jsx(Button, { size: "sm", icon: "sliders-horizontal", onClick: () => setSeedPanelOpen((v) => !v), children: "\uC2DC\uB529" }),
10081
10720
  /* @__PURE__ */ jsx(Button, { size: "sm", icon: "pencil", onClick: () => onEdit(id), children: "\uD3B8\uC9D1" })
10082
10721
  ] }),
10722
+ seedPanelOpen ? /* @__PURE__ */ jsx(
10723
+ PreviewSeedPanel,
10724
+ {
10725
+ specId: id,
10726
+ autoSeeds,
10727
+ stateKeys: previewStateKeys,
10728
+ value: seedSetup,
10729
+ onChange: setSeedSetup,
10730
+ previewEntitySearch,
10731
+ canFetch: previewFetch !== void 0 && !hostAdapter,
10732
+ onClose: () => setSeedPanelOpen(false)
10733
+ }
10734
+ ) : null,
10083
10735
  /* @__PURE__ */ jsx("div", { className: "sdui-editor-viewer__body", children: state.error ? /* @__PURE__ */ jsx(Alert, { tone: "danger", title: "\uD398\uC774\uC9C0\uB97C \uD45C\uC2DC\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4", children: state.error }) : !state.spec ? /* @__PURE__ */ jsx("div", { className: "sdui-editor-center", style: { padding: 64 }, children: /* @__PURE__ */ jsx(Spinner, { size: "lg", label: "\uBD88\uB7EC\uC624\uB294 \uC911" }) }) : /* @__PURE__ */ jsx(
10084
10736
  ViewerBoundary,
10085
10737
  {
@@ -10103,12 +10755,14 @@ function ViewerView({
10103
10755
  children: /* @__PURE__ */ jsx(
10104
10756
  SduiRenderer,
10105
10757
  {
10106
- page: state.spec,
10758
+ page: viewPage ?? state.spec,
10107
10759
  registry,
10108
10760
  adapter,
10761
+ initialState: seeds,
10109
10762
  can,
10110
10763
  syncUrlState: false
10111
- }
10764
+ },
10765
+ `${id}#${state.seq}:${seedSignature(seeds)}`
10112
10766
  )
10113
10767
  }
10114
10768
  ) })
@@ -10125,6 +10779,8 @@ function SduiEditor({
10125
10779
  onNavigate,
10126
10780
  viewerAdapter,
10127
10781
  can,
10782
+ previewFetch,
10783
+ previewEntitySearch,
10128
10784
  className
10129
10785
  }) {
10130
10786
  const [internal, setInternal] = useState(defaultView ?? { name: "list" });
@@ -10162,8 +10818,11 @@ function SduiEditor({
10162
10818
  id: current.id,
10163
10819
  onBack: openList,
10164
10820
  onPreview: openPreview,
10821
+ onOpenSpec: openEdit,
10165
10822
  registry,
10166
- catalog
10823
+ catalog,
10824
+ previewFetch,
10825
+ previewEntitySearch
10167
10826
  }
10168
10827
  ) : /* @__PURE__ */ jsx(
10169
10828
  ViewerView,
@@ -10175,10 +10834,12 @@ function SduiEditor({
10175
10834
  onEdit: openEdit,
10176
10835
  onNavigate: navigateFromSpec,
10177
10836
  adapter: viewerAdapter,
10178
- can
10837
+ can,
10838
+ previewFetch,
10839
+ previewEntitySearch
10179
10840
  }
10180
10841
  )
10181
10842
  ] });
10182
10843
  }
10183
10844
 
10184
- export { ActionEditor, BindingEditor, BuilderView, COALESCE_WINDOW_MS, Canvas, DEFAULT_SLOT, EMPTY_SELECTION, EditorView2 as EditorView, HISTORY_LIMIT, HistoryBar, Inspector, PAGE_ID_PATTERN, PageExistsError, PageGoneError, PageIdConflictError, PageIdRejectedError, PageListView, Palette, ROOT_ID, RestSpecStore, SPEC_PAIR_FREQ, SduiEditor, StoreUnauthorizedError, StoreUnavailableError, StoreUnsupportedError, TEMPLATES, TEXT_CHILD, TreeView, VersionMismatchError, ViewerView, addressOf, ancestorIdsOf, bindingContextFor, breadcrumbOf, buildStateKeyIndex, builderFidelity, canDrop, canDropAt, canMoveTo, canRedo, canUndo, createBindingContext, createBindingSource, createEditorState, createRestSpecStore, describeChild, dropFrequency, dropTargetsAround, duplicate, editorReducer, editorRegistry, emptyDoc, flattenTree, formatAction, formatActionList, formatValue, fromPage, getChild, getNode, gradeIssues, inDocumentOrder, insert, isDescendantOf, isMultiSelection, isSelected, isTypingTarget, isValidPageId, looksLikeActionProp, move, readActionList, recommendedChildren, redoLabel, remove, replaceChild, sameInsertionPoint, sameSpecMeaning, setChildren, setNodeKey, setPageMeta, setProp, setProps, setText, slotsOf, specOf, stateKeyStatus, suggestStateKeys, titleOf, toPage, toSpec, topmost, undoLabel, useUndoRedoShortcuts, walkDoc, writeActionList };
10845
+ export { ActionEditor, BindingEditor, BuilderView, COALESCE_WINDOW_MS, Canvas, DEFAULT_SLOT, DIALOG_PREFIX, EMPTY_SELECTION, EditorView2 as EditorView, HISTORY_LIMIT, HistoryBar, INITIAL_PREVIEW_SETUP, Inspector, PAGE_ID_PATTERN, PageExistsError, PageGoneError, PageIdConflictError, PageIdRejectedError, PageListView, Palette, PreviewSeedPanel, ROOT_ID, RestSpecStore, SPEC_PAIR_FREQ, SduiEditor, StoreUnauthorizedError, StoreUnavailableError, StoreUnsupportedError, TEMPLATES, TEXT_CHILD, TreeView, VersionMismatchError, ViewerView, addressOf, ancestorIdsOf, argsWrapperPage, bindingContextFor, breadcrumbOf, buildStateKeyIndex, builderFidelity, canDrop, canDropAt, canMoveTo, canRedo, canUndo, collectStateKeys, createBindingContext, createBindingSource, createDataPreviewAdapter, createEditorState, createEditorUseSpec, createRestSpecStore, describeChild, dialogOpenSeeds, dropFrequency, dropTargetsAround, duplicate, editorReducer, editorRegistry, emptyDoc, flattenTree, formatAction, formatActionList, formatValue, fromPage, getChild, getNode, gradeIssues, inDocumentOrder, insert, isDescendantOf, isDialogSpecId, isMultiSelection, isSelected, isTypingTarget, isValidPageId, looksLikeActionProp, move, parseSeedValue, patternParams, readActionList, recommendedChildren, redoLabel, remove, replaceChild, resolvePreviewAdapter, sameInsertionPoint, sameSpecMeaning, seedSignature, seedsOf, setChildren, setNodeKey, setPageMeta, setProp, setProps, setText, slotsOf, specOf, stateKeyStatus, suggestStateKeys, titleOf, toPage, toSpec, topmost, undoLabel, useUndoRedoShortcuts, walkDoc, writeActionList };