@poveste/controls 0.5.3 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,15 @@
1
+ /**
2
+ * `JSON.stringify` for values that were never promised to be JSON.
3
+ *
4
+ * State reaches this control straight out of a story's own scope, so it holds
5
+ * whatever a `<script setup>` holds: a function, a DOM node, an object that
6
+ * refers back to itself. `JSON.stringify` throws on the cycle, and it throws
7
+ * from inside a lifecycle hook — which takes the rest of that flush with it, so
8
+ * the panel and the toolbar beside it never mount. A marker in the editor is
9
+ * the smaller loss, and it is what the story events pane already does.
10
+ *
11
+ * Nodes and windows are named rather than walked. Both are cyclic, so the guard
12
+ * below would terminate on them, but only after walking a few thousand
13
+ * properties of `window` on every keystroke.
14
+ */
15
+ export declare function stringifyState(value: unknown, space?: number): string;
package/dist/index.es.css CHANGED
@@ -1,2 +1,2 @@
1
- .bind-col-size{grid-template-columns:repeat(auto-fill, minmax(var(--poveste-col-size), 1fr))}.__poveste-json-code[data-v-8570567f] .cm-editor{min-width:280px;height:100%}.v-popper{line-height:0}.v-popper--theme-dropdown .v-popper__inner:where(.ptw-dark,.ptw-dark *){border-color:var(--color-gray-850,#1f1f21);background-color:var(--color-gray-700,var(--color-zinc-700));color:var(--color-gray-100,var(--color-zinc-100))}.v-popper--theme-dropdown .v-popper__arrow-inner:where(.ptw-dark,.ptw-dark *){border-color:var(--color-gray-700,var(--color-zinc-700))}.v-popper--theme-dropdown .v-popper__arrow-outer:where(.ptw-dark,.ptw-dark *){border-color:var(--color-gray-850,#1f1f21)}.v-popper--theme-dropdown.v-popper__popper--show-from .v-popper__wrapper{transform:scale(.75)}.v-popper--theme-dropdown.v-popper__popper--show-to .v-popper__wrapper{transition:transform .15s cubic-bezier(0,1,.5,1);transform:none}.v-popper__popper:focus-visible{outline:none}@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style:solid}}}.range-input::-webkit-slider-thumb{height:calc(var(--spacing,.25rem) * 3);width:calc(var(--spacing,.25rem) * 3);appearance:none;border-style:var(--tw-border-style);--tw-border-style:solid;border:1px solid #00000040;border-radius:2147483647px}@supports (color:color-mix(in lab, red, red)){.range-input::-webkit-slider-thumb{border-color:color-mix(in oklab, var(--color-black,#000) 25%, transparent)}}.range-input::-webkit-slider-thumb{background-color:var(--color-white,#fff)}.range-input::-webkit-slider-thumb:where(.ptw-dark,.ptw-dark *){border-color:#ffffff40}@supports (color:color-mix(in lab, red, red)){.range-input::-webkit-slider-thumb:where(.ptw-dark,.ptw-dark *){border-color:color-mix(in oklab, var(--color-white,#fff) 25%, transparent)}}.range-input::-webkit-slider-thumb:where(.ptw-dark,.ptw-dark *){background-color:var(--color-gray-700,var(--color-zinc-700))}.range-input:hover::-webkit-slider-thumb{border-color:var(--color-primary-500,var(--color-emerald-500))!important;background-color:var(--color-primary-500,var(--color-emerald-500))!important}.range-input::-moz-range-thumb{height:calc(var(--spacing,.25rem) * 3);width:calc(var(--spacing,.25rem) * 3);appearance:none;border-style:var(--tw-border-style);--tw-border-style:solid;border:1px solid #00000040;border-radius:2147483647px}@supports (color:color-mix(in lab, red, red)){.range-input::-moz-range-thumb{border-color:color-mix(in oklab, var(--color-black,#000) 25%, transparent)}}.range-input::-moz-range-thumb{background-color:var(--color-white,#fff)}.range-input::-moz-range-thumb:where(.ptw-dark,.ptw-dark *){border-color:#ffffff40}@supports (color:color-mix(in lab, red, red)){.range-input::-moz-range-thumb:where(.ptw-dark,.ptw-dark *){border-color:color-mix(in oklab, var(--color-white,#fff) 25%, transparent)}}.range-input::-moz-range-thumb:where(.ptw-dark,.ptw-dark *){background-color:var(--color-gray-700,var(--color-zinc-700))}.range-input:hover::-moz-range-thumb{border-color:var(--color-primary-500,var(--color-emerald-500))!important;background-color:var(--color-primary-500,var(--color-emerald-500))!important}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}
1
+ .bind-col-size{grid-template-columns:repeat(auto-fill, minmax(var(--poveste-col-size), 1fr))}.__poveste-json-code[data-v-e4c89a1b] .cm-editor{min-width:280px;height:100%}.v-popper{line-height:0}.v-popper--theme-dropdown .v-popper__inner:where(.ptw-dark,.ptw-dark *){border-color:var(--color-gray-850,#1f1f21);background-color:var(--color-gray-700,var(--color-zinc-700));color:var(--color-gray-100,var(--color-zinc-100))}.v-popper--theme-dropdown .v-popper__arrow-inner:where(.ptw-dark,.ptw-dark *){border-color:var(--color-gray-700,var(--color-zinc-700))}.v-popper--theme-dropdown .v-popper__arrow-outer:where(.ptw-dark,.ptw-dark *){border-color:var(--color-gray-850,#1f1f21)}.v-popper--theme-dropdown.v-popper__popper--show-from .v-popper__wrapper{transform:scale(.75)}.v-popper--theme-dropdown.v-popper__popper--show-to .v-popper__wrapper{transition:transform .15s cubic-bezier(0,1,.5,1);transform:none}.v-popper__popper:focus-visible{outline:none}@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style:solid}}}.range-input::-webkit-slider-thumb{height:calc(var(--spacing,.25rem) * 3);width:calc(var(--spacing,.25rem) * 3);appearance:none;border-style:var(--tw-border-style);--tw-border-style:solid;border:1px solid #00000040;border-radius:2147483647px}@supports (color:color-mix(in lab, red, red)){.range-input::-webkit-slider-thumb{border-color:color-mix(in oklab, var(--color-black,#000) 25%, transparent)}}.range-input::-webkit-slider-thumb{background-color:var(--color-white,#fff)}.range-input::-webkit-slider-thumb:where(.ptw-dark,.ptw-dark *){border-color:#ffffff40}@supports (color:color-mix(in lab, red, red)){.range-input::-webkit-slider-thumb:where(.ptw-dark,.ptw-dark *){border-color:color-mix(in oklab, var(--color-white,#fff) 25%, transparent)}}.range-input::-webkit-slider-thumb:where(.ptw-dark,.ptw-dark *){background-color:var(--color-gray-700,var(--color-zinc-700))}.range-input:hover::-webkit-slider-thumb{border-color:var(--color-primary-500,var(--color-emerald-500))!important;background-color:var(--color-primary-500,var(--color-emerald-500))!important}.range-input::-moz-range-thumb{height:calc(var(--spacing,.25rem) * 3);width:calc(var(--spacing,.25rem) * 3);appearance:none;border-style:var(--tw-border-style);--tw-border-style:solid;border:1px solid #00000040;border-radius:2147483647px}@supports (color:color-mix(in lab, red, red)){.range-input::-moz-range-thumb{border-color:color-mix(in oklab, var(--color-black,#000) 25%, transparent)}}.range-input::-moz-range-thumb{background-color:var(--color-white,#fff)}.range-input::-moz-range-thumb:where(.ptw-dark,.ptw-dark *){border-color:#ffffff40}@supports (color:color-mix(in lab, red, red)){.range-input::-moz-range-thumb:where(.ptw-dark,.ptw-dark *){border-color:color-mix(in oklab, var(--color-white,#fff) 25%, transparent)}}.range-input::-moz-range-thumb:where(.ptw-dark,.ptw-dark *){background-color:var(--color-gray-700,var(--color-zinc-700))}.range-input:hover::-moz-range-thumb{border-color:var(--color-primary-500,var(--color-emerald-500))!important;background-color:var(--color-primary-500,var(--color-emerald-500))!important}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}
2
2
  /*$vite$:1*/
package/dist/index.es.js CHANGED
@@ -12836,8 +12836,22 @@ var mh = [
12836
12836
  ]), Nh = h(!1);
12837
12837
  window.__pvt_controls_dark || (window.__pvt_controls_dark = []), window.__pvt_controls_dark.push(Nh), window.__pvt_controls_dark_ready?.();
12838
12838
  //#endregion
12839
+ //#region src/components/json/serialize.ts
12840
+ function Ph(e, t) {
12841
+ let n = [];
12842
+ return JSON.stringify(e, function(e, t) {
12843
+ if (typeof t == "function") return "[Function]";
12844
+ if (typeof t == "bigint") return t.toString();
12845
+ if (typeof t != "object" || !t) return t;
12846
+ if (typeof Node < "u" && t instanceof Node) return "[Node]";
12847
+ if (typeof Window < "u" && t instanceof Window) return "[Window]";
12848
+ for (; n.length > 0 && n[n.length - 1] !== this;) n.pop();
12849
+ return n.includes(t) ? "[Circular]" : (n.push(t), t);
12850
+ }, t);
12851
+ }
12852
+ //#endregion
12839
12853
  //#region src/components/json/HstJson.vue
12840
- var Ph = /*#__PURE__*/ ((e, t) => {
12854
+ var Fh = /*#__PURE__*/ ((e, t) => {
12841
12855
  let n = e.__vccOpts || e;
12842
12856
  for (let [e, r] of t) n[e] = r;
12843
12857
  return n;
@@ -12850,10 +12864,10 @@ var Ph = /*#__PURE__*/ ((e, t) => {
12850
12864
  },
12851
12865
  emits: { "update:modelValue": (e) => !0 },
12852
12866
  setup(e, { emit: t }) {
12853
- let i = e, o = t, s, c = h(""), p = h(!1), g = h(), v = {
12867
+ let i = e, o = t, s, c, p = h(""), g = h(!1), v = h(), y = {
12854
12868
  light: [W.baseTheme({}), Od(jd)],
12855
12869
  dark: [jh, Od(Mh)]
12856
- }, y = new Rt(), x = [
12870
+ }, x = new Rt(), te = [
12857
12871
  cl(),
12858
12872
  _c(),
12859
12873
  uc(),
@@ -12867,50 +12881,52 @@ var Ph = /*#__PURE__*/ ((e, t) => {
12867
12881
  ...th
12868
12882
  ]),
12869
12883
  W.updateListener.of((e) => {
12870
- c.value = e.view.state.doc.toString();
12884
+ p.value = e.view.state.doc.toString();
12871
12885
  }),
12872
- y.of(v.light)
12886
+ x.of(y.light)
12873
12887
  ];
12874
12888
  return f(() => {
12875
- s = new W({
12876
- doc: JSON.stringify(i.modelValue, null, 2),
12877
- extensions: x,
12878
- parent: g.value
12889
+ c = Ph(i.modelValue, 2), s = new W({
12890
+ doc: c,
12891
+ extensions: te,
12892
+ parent: v.value
12879
12893
  }), ee(() => {
12880
- s.dispatch({ effects: [y.reconfigure(v[Nh.value ? "dark" : "light"])] });
12894
+ s.dispatch({ effects: [x.reconfigure(y[Nh.value ? "dark" : "light"])] });
12881
12895
  });
12882
12896
  }), S(() => i.modelValue, () => {
12883
12897
  let e;
12884
12898
  try {
12885
- e = JSON.stringify(JSON.parse(c.value)) === JSON.stringify(i.modelValue);
12899
+ e = Ph(JSON.parse(p.value)) === Ph(i.modelValue);
12886
12900
  } catch {
12887
12901
  e = !1;
12888
12902
  }
12889
- e || s.dispatch({ changes: [{
12903
+ e || (c = Ph(i.modelValue, 2), s.dispatch({ changes: [{
12890
12904
  from: 0,
12891
12905
  to: s.state.doc.length,
12892
- insert: JSON.stringify(i.modelValue, null, 2)
12893
- }] });
12894
- }, { deep: !0 }), S(() => c.value, () => {
12895
- p.value = !1;
12896
- try {
12897
- o("update:modelValue", JSON.parse(c.value));
12898
- } catch {
12899
- p.value = !0;
12906
+ insert: c
12907
+ }] }));
12908
+ }, { deep: !0 }), S(() => p.value, () => {
12909
+ if (p.value !== c) {
12910
+ g.value = !1;
12911
+ try {
12912
+ o("update:modelValue", JSON.parse(p.value));
12913
+ } catch {
12914
+ g.value = !0;
12915
+ }
12900
12916
  }
12901
12917
  }), (t, i) => (m(), n(ue, {
12902
12918
  title: e.title,
12903
12919
  class: u(["poveste-json cursor-text", t.$attrs.class]),
12904
12920
  style: d(t.$attrs.style)
12905
12921
  }, {
12906
- actions: C(() => [p.value ? w((m(), n(b(ie), {
12922
+ actions: C(() => [g.value ? w((m(), n(b(ie), {
12907
12923
  key: 0,
12908
12924
  icon: "carbon:warning-alt",
12909
12925
  class: "text-orange-500"
12910
12926
  }, null, 512)), [[b(T), "JSON error"]]) : r("", !0), _(t.$slots, "actions", {}, void 0, !0)]),
12911
12927
  default: C(() => [a("div", l({
12912
12928
  ref_key: "editorElement",
12913
- ref: g,
12929
+ ref: v,
12914
12930
  class: "__poveste-json-code w-full border border-solid border-black/25 dark:border-white/25 focus-within:border-primary-500 dark:focus-within:border-primary-500 rounded-sm box-border overflow-auto resize-y min-h-32 h-48 relative"
12915
12931
  }, {
12916
12932
  ...t.$attrs,
@@ -12924,7 +12940,7 @@ var Ph = /*#__PURE__*/ ((e, t) => {
12924
12940
  "style"
12925
12941
  ]));
12926
12942
  }
12927
- }), [["__scopeId", "data-v-8570567f"]]), Fh = /* @__PURE__ */ c({
12943
+ }), [["__scopeId", "data-v-e4c89a1b"]]), Ih = /* @__PURE__ */ c({
12928
12944
  name: "HstNumber",
12929
12945
  inheritAttrs: !1,
12930
12946
  props: {
@@ -12987,13 +13003,13 @@ var Ph = /*#__PURE__*/ ((e, t) => {
12987
13003
  "style"
12988
13004
  ]));
12989
13005
  }
12990
- }), Ih = { class: "-my-1" }, Lh = [
13006
+ }), Lh = { class: "-my-1" }, Rh = [
12991
13007
  "id",
12992
13008
  "name",
12993
13009
  "value",
12994
13010
  "checked",
12995
13011
  "onChange"
12996
- ], Rh = ["for", "onKeydown"], zh = /* @__PURE__ */ c({
13012
+ ], zh = ["for", "onKeydown"], Bh = /* @__PURE__ */ c({
12997
13013
  name: "HstRadio",
12998
13014
  props: {
12999
13015
  title: {},
@@ -13014,7 +13030,7 @@ var Ph = /*#__PURE__*/ ((e, t) => {
13014
13030
  style: d(t.$attrs.style)
13015
13031
  }, {
13016
13032
  actions: C(() => [_(t.$slots, "actions")]),
13017
- default: C(() => [a("div", Ih, [(m(!0), i(e, null, g(f.value, (t, n) => (m(), i(e, { key: n }, [a("input", {
13033
+ default: C(() => [a("div", Lh, [(m(!0), i(e, null, g(f.value, (t, n) => (m(), i(e, { key: n }, [a("input", {
13018
13034
  id: `${n}-radio_${r.title}`,
13019
13035
  type: "radio",
13020
13036
  name: `${n}-radio_${r.title}`,
@@ -13022,7 +13038,7 @@ var Ph = /*#__PURE__*/ ((e, t) => {
13022
13038
  checked: n === r.modelValue,
13023
13039
  class: "hidden!",
13024
13040
  onChange: (e) => p(n)
13025
- }, null, 40, Lh), a("label", {
13041
+ }, null, 40, Rh), a("label", {
13026
13042
  tabindex: "0",
13027
13043
  for: `${n}-radio_${r.title}`,
13028
13044
  class: "cursor-pointer flex items-center relative py-1 group",
@@ -13039,7 +13055,7 @@ var Ph = /*#__PURE__*/ ((e, t) => {
13039
13055
  { "delay-150": r.modelValue === n },
13040
13056
  r.modelValue === n ? "fill-primary-500" : "fill-transparent scale-0"
13041
13057
  ]])
13042
- }, null, 2)], 2)), o(" " + y(t), 1)], 40, Rh)], 64))), 128))])]),
13058
+ }, null, 2)], 2)), o(" " + y(t), 1)], 40, zh)], 64))), 128))])]),
13043
13059
  _: 3
13044
13060
  }, 8, [
13045
13061
  "title",
@@ -13047,7 +13063,7 @@ var Ph = /*#__PURE__*/ ((e, t) => {
13047
13063
  "style"
13048
13064
  ]));
13049
13065
  }
13050
- }), Bh = { class: "cursor-pointer w-full outline-none px-2 h-[27px] -my-1 border border-solid border-black/25 dark:border-white/25 hover:border-primary-500 dark:hover:border-primary-500 rounded-sm flex gap-2 items-center leading-normal" }, Vh = { class: "flex-1 truncate" }, Hh = { class: "flex flex-col bg-gray-50 dark:bg-gray-700" }, Uh = ["onClick"], Wh = /* @__PURE__ */ c({
13066
+ }), Vh = { class: "cursor-pointer w-full outline-none px-2 h-[27px] -my-1 border border-solid border-black/25 dark:border-white/25 hover:border-primary-500 dark:hover:border-primary-500 rounded-sm flex gap-2 items-center leading-normal" }, Hh = { class: "flex-1 truncate" }, Uh = { class: "flex flex-col bg-gray-50 dark:bg-gray-700" }, Wh = ["onClick"], Gh = /* @__PURE__ */ c({
13051
13067
  name: "CustomSelect",
13052
13068
  props: {
13053
13069
  modelValue: {},
@@ -13063,7 +13079,7 @@ var Ph = /*#__PURE__*/ ((e, t) => {
13063
13079
  "auto-size": "",
13064
13080
  "auto-boundary-max-size": ""
13065
13081
  }, {
13066
- popper: C(({ hide: n }) => [a("div", Hh, [(m(!0), i(e, null, g(f.value, ([e, r]) => (m(), i("div", l({ ref_for: !0 }, {
13082
+ popper: C(({ hide: n }) => [a("div", Uh, [(m(!0), i(e, null, g(f.value, ([e, r]) => (m(), i("div", l({ ref_for: !0 }, {
13067
13083
  ...t.$attrs,
13068
13084
  class: null,
13069
13085
  style: null
@@ -13071,15 +13087,15 @@ var Ph = /*#__PURE__*/ ((e, t) => {
13071
13087
  key: r,
13072
13088
  class: ["px-2 py-1 cursor-pointer hover:bg-primary-100 dark:hover:bg-primary-700", { "bg-primary-200 dark:bg-primary-800": u.modelValue === e }],
13073
13089
  onClick: (t) => h(e, n)
13074
- }), y(r), 17, Uh))), 128))])]),
13075
- default: C(() => [a("div", Bh, [a("div", Vh, [_(t.$slots, "default", { label: p.value }, () => [o(y(p.value), 1)])]), s(b(ie), {
13090
+ }), y(r), 17, Wh))), 128))])]),
13091
+ default: C(() => [a("div", Vh, [a("div", Hh, [_(t.$slots, "default", { label: p.value }, () => [o(y(p.value), 1)])]), s(b(ie), {
13076
13092
  icon: "carbon:chevron-sort",
13077
13093
  class: "w-4 h-4 flex-none ml-auto"
13078
13094
  })])]),
13079
13095
  _: 3
13080
13096
  }));
13081
13097
  }
13082
- }), Gh = /* @__PURE__ */ c({
13098
+ }), Kh = /* @__PURE__ */ c({
13083
13099
  name: "HstSelect",
13084
13100
  props: {
13085
13101
  title: {},
@@ -13095,7 +13111,7 @@ var Ph = /*#__PURE__*/ ((e, t) => {
13095
13111
  style: d(t.$attrs.style)
13096
13112
  }, {
13097
13113
  actions: C(() => [_(t.$slots, "actions")]),
13098
- default: C(() => [s(Wh, {
13114
+ default: C(() => [s(Gh, {
13099
13115
  options: e.options,
13100
13116
  "model-value": e.modelValue,
13101
13117
  "onUpdate:modelValue": i[0] ||= (e) => r("update:modelValue", e)
@@ -13107,7 +13123,7 @@ var Ph = /*#__PURE__*/ ((e, t) => {
13107
13123
  "style"
13108
13124
  ]));
13109
13125
  }
13110
- }), Kh = { class: "relative w-full flex items-center" }, qh = /* @__PURE__ */ c({
13126
+ }), qh = { class: "relative w-full flex items-center" }, Jh = /* @__PURE__ */ c({
13111
13127
  name: "HstSlider",
13112
13128
  inheritAttrs: !1,
13113
13129
  props: {
@@ -13129,7 +13145,7 @@ var Ph = /*#__PURE__*/ ((e, t) => {
13129
13145
  title: e.title,
13130
13146
  style: d(t.$attrs.style)
13131
13147
  }, {
13132
- default: C(() => [a("div", Kh, [
13148
+ default: C(() => [a("div", qh, [
13133
13149
  o[3] ||= a("div", { class: "absolute inset-0 flex items-center" }, [a("div", { class: "border border-black/25 dark:border-white/25 h-1 w-full rounded-full" })], -1),
13134
13150
  w(a("input", l({
13135
13151
  ref_key: "input",
@@ -13170,7 +13186,7 @@ var Ph = /*#__PURE__*/ ((e, t) => {
13170
13186
  "style"
13171
13187
  ]));
13172
13188
  }
13173
- }), Jh = ["value"], Yh = /* @__PURE__ */ c({
13189
+ }), Yh = ["value"], Xh = /* @__PURE__ */ c({
13174
13190
  name: "HstText",
13175
13191
  props: {
13176
13192
  title: {},
@@ -13198,7 +13214,7 @@ var Ph = /*#__PURE__*/ ((e, t) => {
13198
13214
  value: e.modelValue,
13199
13215
  class: "text-inherit bg-transparent w-full outline-none px-2 py-1 -my-1 border border-solid border-black/25 dark:border-white/25 focus:border-primary-500 dark:focus:border-primary-500 rounded-sm",
13200
13216
  onInput: o[0] ||= (e) => r("update:modelValue", e.target.value)
13201
- }), null, 16, Jh)]),
13217
+ }), null, 16, Yh)]),
13202
13218
  _: 3
13203
13219
  }, 8, [
13204
13220
  "title",
@@ -13206,7 +13222,7 @@ var Ph = /*#__PURE__*/ ((e, t) => {
13206
13222
  "style"
13207
13223
  ]));
13208
13224
  }
13209
- }), Xh = ["value"], Zh = /* @__PURE__ */ c({
13225
+ }), Zh = ["value"], Qh = /* @__PURE__ */ c({
13210
13226
  name: "HstTextarea",
13211
13227
  inheritAttrs: !1,
13212
13228
  props: {
@@ -13234,7 +13250,7 @@ var Ph = /*#__PURE__*/ ((e, t) => {
13234
13250
  value: e.modelValue,
13235
13251
  class: "text-inherit bg-transparent w-full outline-none px-2 py-1 -my-1 border border-solid border-black/25 dark:border-white/25 focus:border-primary-500 dark:focus:border-primary-500 rounded-sm box-border resize-y min-h-[26px]",
13236
13252
  onInput: o[0] ||= (e) => r("update:modelValue", e.target.value)
13237
- }), null, 16, Xh)]),
13253
+ }), null, 16, Zh)]),
13238
13254
  _: 3
13239
13255
  }, 8, [
13240
13256
  "title",
@@ -13242,23 +13258,23 @@ var Ph = /*#__PURE__*/ ((e, t) => {
13242
13258
  "style"
13243
13259
  ]));
13244
13260
  }
13245
- }), Qh = oe, $h = fe, eg = ge, tg = ye, ng = Yh, rg = Fh, ig = qh, ag = Zh, og = Gh, sg = Ae, cg = Ue, lg = Ie, ug = Ce, dg = zh, fg = Ph, pg = Se, mg = {
13246
- HstButton: Qh,
13247
- HstButtonGroup: $h,
13248
- HstCheckbox: eg,
13249
- HstCheckboxList: tg,
13250
- HstText: ng,
13251
- HstNumber: rg,
13252
- HstSlider: ig,
13253
- HstTextarea: ag,
13254
- HstSelect: og,
13255
- HstRadio: dg,
13256
- HstJson: fg,
13257
- HstColorShades: sg,
13258
- HstTokenList: cg,
13259
- HstTokenGrid: lg,
13260
- HstCopyIcon: ug,
13261
- HstColorSelect: pg
13261
+ }), $h = oe, eg = fe, tg = ge, ng = ye, rg = Xh, ig = Ih, ag = Jh, og = Qh, sg = Kh, cg = Ae, lg = Ue, ug = Ie, dg = Ce, fg = Bh, pg = Fh, mg = Se, hg = {
13262
+ HstButton: $h,
13263
+ HstButtonGroup: eg,
13264
+ HstCheckbox: tg,
13265
+ HstCheckboxList: ng,
13266
+ HstText: rg,
13267
+ HstNumber: ig,
13268
+ HstSlider: ag,
13269
+ HstTextarea: og,
13270
+ HstSelect: sg,
13271
+ HstRadio: fg,
13272
+ HstJson: pg,
13273
+ HstColorShades: cg,
13274
+ HstTokenList: lg,
13275
+ HstTokenGrid: ug,
13276
+ HstCopyIcon: dg,
13277
+ HstColorSelect: mg
13262
13278
  };
13263
13279
  //#endregion
13264
- export { Qh as HstButton, $h as HstButtonGroup, eg as HstCheckbox, tg as HstCheckboxList, pg as HstColorSelect, sg as HstColorShades, ug as HstCopyIcon, fg as HstJson, rg as HstNumber, dg as HstRadio, og as HstSelect, ig as HstSlider, ng as HstText, ag as HstTextarea, lg as HstTokenGrid, cg as HstTokenList, mg as components };
13280
+ export { $h as HstButton, eg as HstButtonGroup, tg as HstCheckbox, ng as HstCheckboxList, mg as HstColorSelect, cg as HstColorShades, dg as HstCopyIcon, pg as HstJson, ig as HstNumber, fg as HstRadio, sg as HstSelect, ag as HstSlider, rg as HstText, og as HstTextarea, ug as HstTokenGrid, lg as HstTokenList, hg as components };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@poveste/controls",
3
3
  "type": "module",
4
- "version": "0.5.3",
4
+ "version": "0.6.1",
5
5
  "description": "Prebuilt controls components",
6
6
  "author": {
7
7
  "name": "Sorin Gitlan"
@@ -38,8 +38,8 @@
38
38
  "@codemirror/state": "^6.4.1",
39
39
  "@codemirror/theme-one-dark": "^6.1.2",
40
40
  "@codemirror/view": "^6.35.0",
41
- "@poveste/vendors": "^0.5.3",
42
- "@poveste/shared": "^0.5.3"
41
+ "@poveste/shared": "^0.6.1",
42
+ "@poveste/vendors": "^0.6.1"
43
43
  },
44
44
  "devDependencies": {
45
45
  "@tailwindcss/cli": "^4.3.3",
@@ -34,6 +34,7 @@ import { VTooltip as vTooltip } from 'floating-vue'
34
34
  import { onMounted, ref, watch, watchEffect } from 'vue'
35
35
  import { isDark } from '../../utils'
36
36
  import HstWrapper from '../HstWrapper.vue'
37
+ import { stringifyState } from './serialize.js'
37
38
 
38
39
  const props = defineProps<{
39
40
  title?: string
@@ -45,6 +46,12 @@ const emit = defineEmits({
45
46
  })
46
47
 
47
48
  let editorView: EditorView
49
+ // The document this control last rendered from `modelValue`. A change producing
50
+ // exactly this is our own render, not an edit, and must not be written back:
51
+ // `stringifyState` substitutes markers for what JSON cannot carry, so echoing
52
+ // our own document replaces a cycle in the story's state with the string
53
+ // `[Circular]`, and a function with `[Function]`.
54
+ let renderedDoc: string | undefined
48
55
  const internalValue = ref('')
49
56
  const invalidValue = ref(false)
50
57
  const editorElement = ref<HTMLInputElement>()
@@ -76,8 +83,10 @@ const extensions = [
76
83
  ]
77
84
 
78
85
  onMounted(() => {
86
+ renderedDoc = stringifyState(props.modelValue, 2)
87
+
79
88
  editorView = new EditorView({
80
- doc: JSON.stringify(props.modelValue, null, 2),
89
+ doc: renderedDoc,
81
90
  extensions,
82
91
  parent: editorElement.value,
83
92
  })
@@ -95,18 +104,25 @@ watch(() => props.modelValue, () => {
95
104
  let sameDocument
96
105
 
97
106
  try {
98
- sameDocument = (JSON.stringify(JSON.parse(internalValue.value)) === JSON.stringify(props.modelValue))
107
+ sameDocument = (stringifyState(JSON.parse(internalValue.value)) === stringifyState(props.modelValue))
99
108
  }
100
109
  catch (e) {
101
110
  sameDocument = false
102
111
  }
103
112
 
104
113
  if (!sameDocument) {
105
- editorView.dispatch({ changes: [{ from: 0, to: editorView.state.doc.length, insert: JSON.stringify(props.modelValue, null, 2) }] })
114
+ renderedDoc = stringifyState(props.modelValue, 2)
115
+ editorView.dispatch({ changes: [{ from: 0, to: editorView.state.doc.length, insert: renderedDoc }] })
106
116
  }
107
117
  }, { deep: true })
108
118
 
109
119
  watch(() => internalValue.value, () => {
120
+ // Our own render coming back round. The model already holds this, and parsing
121
+ // it would hand back the markers instead of the values they stand for.
122
+ if (internalValue.value === renderedDoc) {
123
+ return
124
+ }
125
+
110
126
  invalidValue.value = false
111
127
  try {
112
128
  emit('update:modelValue', JSON.parse(internalValue.value))
@@ -0,0 +1,85 @@
1
+ import { EditorView } from '@codemirror/view'
2
+ import { mount } from '@vue/test-utils'
3
+ import { describe, expect, it } from 'vitest'
4
+ import { nextTick } from 'vue'
5
+ import HstJson from './HstJson.vue'
6
+ import { stringifyState } from './serialize.js'
7
+
8
+ describe('stringifyState', () => {
9
+ it('matches JSON.stringify on values JSON can express', () => {
10
+ const value = { a: 1, b: 'two', c: [1, 2], d: { e: null }, f: true }
11
+ expect(stringifyState(value)).toBe(JSON.stringify(value))
12
+ expect(stringifyState(value, 2)).toBe(JSON.stringify(value, null, 2))
13
+ })
14
+
15
+ it('names a cycle instead of throwing', () => {
16
+ // The ComplexParameter story in the vue3 example holds exactly this, and
17
+ // once the state sync started delivering the story's own scope it reached
18
+ // the editor. `JSON.stringify` throws here, from a lifecycle hook, taking
19
+ // the rest of the flush — the panel and the toolbar — down with it.
20
+ const parent: any = { name: 'hello' }
21
+ const child: any = { parent }
22
+ parent.child = child
23
+
24
+ expect(JSON.parse(stringifyState(parent))).toEqual({
25
+ name: 'hello',
26
+ child: { parent: '[Circular]' },
27
+ })
28
+ })
29
+
30
+ it('names a function, which JSON.stringify drops silently', () => {
31
+ expect(JSON.parse(stringifyState({ action: () => {} }))).toEqual({ action: '[Function]' })
32
+ })
33
+
34
+ it('does not mistake a shared reference for a cycle', () => {
35
+ // The guard tracks ancestors, not everything seen. The same object twice as
36
+ // a sibling serialises fine and must not be blanked out.
37
+ const shared = { a: 1 }
38
+ expect(JSON.parse(stringifyState({ x: shared, y: shared }))).toEqual({ x: { a: 1 }, y: { a: 1 } })
39
+ })
40
+
41
+ it('names a node rather than walking it', () => {
42
+ const el = document.createElement('div')
43
+ expect(JSON.parse(stringifyState({ el }))).toEqual({ el: '[Node]' })
44
+ })
45
+
46
+ it('carries a bigint across as its digits', () => {
47
+ expect(JSON.parse(stringifyState({ n: 10n }))).toEqual({ n: '10' })
48
+ })
49
+ })
50
+
51
+ describe('hstJson round-trip', () => {
52
+ it('does not write its own markers back into the model', async () => {
53
+ // The markers stand for what JSON cannot carry. Parsing the rendered
54
+ // document and emitting it replaces the thing itself with its label — a
55
+ // cycle in the story's state becomes the string `[Circular]` — and it used
56
+ // to happen on mount, with nobody touching the editor.
57
+ const parent: any = { name: 'hello' }
58
+ parent.child = { parent }
59
+
60
+ const wrapper = mount(HstJson, { props: { modelValue: parent, title: 'Recursive' } })
61
+ await nextTick()
62
+
63
+ await wrapper.setProps({ modelValue: { ...parent, name: 'changed' } })
64
+ await nextTick()
65
+ await new Promise(resolve => setTimeout(resolve, 50))
66
+
67
+ expect(wrapper.emitted('update:modelValue')).toBeUndefined()
68
+ })
69
+
70
+ it('still reports a document the reader changed', async () => {
71
+ // The guard is keyed on the exact text this control last rendered, so it
72
+ // must not swallow anything else. Without this, suppressing the echo would
73
+ // quietly turn the editor read-only and the test above would still pass.
74
+ const wrapper = mount(HstJson, { props: { modelValue: { a: 1 }, title: 'Plain' } })
75
+ await nextTick()
76
+
77
+ const view = EditorView.findFromDOM(wrapper.find('.cm-content').element as HTMLElement)!
78
+ expect(view).toBeTruthy()
79
+
80
+ view.dispatch({ changes: { from: 0, to: view.state.doc.length, insert: '{ "a": 2 }' } })
81
+ await nextTick()
82
+
83
+ expect(wrapper.emitted('update:modelValue')?.at(-1)).toEqual([{ a: 2 }])
84
+ })
85
+ })
@@ -0,0 +1,54 @@
1
+ /**
2
+ * `JSON.stringify` for values that were never promised to be JSON.
3
+ *
4
+ * State reaches this control straight out of a story's own scope, so it holds
5
+ * whatever a `<script setup>` holds: a function, a DOM node, an object that
6
+ * refers back to itself. `JSON.stringify` throws on the cycle, and it throws
7
+ * from inside a lifecycle hook — which takes the rest of that flush with it, so
8
+ * the panel and the toolbar beside it never mount. A marker in the editor is
9
+ * the smaller loss, and it is what the story events pane already does.
10
+ *
11
+ * Nodes and windows are named rather than walked. Both are cyclic, so the guard
12
+ * below would terminate on them, but only after walking a few thousand
13
+ * properties of `window` on every keystroke.
14
+ */
15
+ export function stringifyState(value: unknown, space?: number) {
16
+ // Ancestors, not everything seen: the same object appearing twice as a
17
+ // sibling is a shared reference, not a cycle, and is perfectly serialisable.
18
+ // The replacer's `this` is the holder, which is what lets the stack unwind.
19
+ const ancestors: any[] = []
20
+
21
+ return JSON.stringify(value, function (this: any, _key, current) {
22
+ if (typeof current === 'function') {
23
+ return '[Function]'
24
+ }
25
+
26
+ if (typeof current === 'bigint') {
27
+ return current.toString()
28
+ }
29
+
30
+ if (current === null || typeof current !== 'object') {
31
+ return current
32
+ }
33
+
34
+ if (typeof Node !== 'undefined' && current instanceof Node) {
35
+ return '[Node]'
36
+ }
37
+
38
+ if (typeof Window !== 'undefined' && current instanceof Window) {
39
+ return '[Window]'
40
+ }
41
+
42
+ while (ancestors.length > 0 && ancestors[ancestors.length - 1] !== this) {
43
+ ancestors.pop()
44
+ }
45
+
46
+ if (ancestors.includes(current)) {
47
+ return '[Circular]'
48
+ }
49
+
50
+ ancestors.push(current)
51
+
52
+ return current
53
+ }, space)
54
+ }