@wildwinter/app-shell 0.56.0 → 0.58.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.
Files changed (58) hide show
  1. package/README.md +215 -0
  2. package/dist/chunk-4JQWNC6Z.js +319 -0
  3. package/dist/chunk-4JQWNC6Z.js.map +1 -0
  4. package/dist/chunk-ICYPXUNA.js +8 -0
  5. package/dist/chunk-ICYPXUNA.js.map +1 -0
  6. package/dist/{chunk-CPNTROWD.js → chunk-OZQ2BYXH.js} +13 -4
  7. package/dist/chunk-OZQ2BYXH.js.map +1 -0
  8. package/dist/controls.css +52 -0
  9. package/dist/controls.css.map +1 -1
  10. package/dist/index.cjs +352 -3
  11. package/dist/index.cjs.map +1 -1
  12. package/dist/index.d.cts +17 -1
  13. package/dist/index.d.ts +17 -1
  14. package/dist/index.js +24 -1
  15. package/dist/index.js.map +1 -1
  16. package/dist/tool-window.cjs +11 -2
  17. package/dist/tool-window.cjs.map +1 -1
  18. package/dist/tool-window.d.cts +10 -4
  19. package/dist/tool-window.d.ts +10 -4
  20. package/dist/tool-window.js +1 -1
  21. package/dist/updater.cjs.map +1 -1
  22. package/dist/updater.js +1 -1
  23. package/dist/value-rows.cjs +200 -10
  24. package/dist/value-rows.cjs.map +1 -1
  25. package/dist/value-rows.d.cts +36 -1
  26. package/dist/value-rows.d.ts +36 -1
  27. package/dist/value-rows.js +197 -12
  28. package/dist/value-rows.js.map +1 -1
  29. package/dist/view-client.cjs +172 -0
  30. package/dist/view-client.cjs.map +1 -0
  31. package/dist/view-client.d.cts +46 -0
  32. package/dist/view-client.d.ts +46 -0
  33. package/dist/view-client.js +165 -0
  34. package/dist/view-client.js.map +1 -0
  35. package/dist/view-host-web.cjs +327 -0
  36. package/dist/view-host-web.cjs.map +1 -0
  37. package/dist/view-host-web.d.cts +61 -0
  38. package/dist/view-host-web.d.ts +61 -0
  39. package/dist/view-host-web.js +4 -0
  40. package/dist/view-host-web.js.map +1 -0
  41. package/dist/view-host.cjs +532 -0
  42. package/dist/view-host.cjs.map +1 -0
  43. package/dist/view-host.d.cts +178 -0
  44. package/dist/view-host.d.ts +178 -0
  45. package/dist/view-host.js +426 -0
  46. package/dist/view-host.js.map +1 -0
  47. package/dist/view-preload.cjs +16 -0
  48. package/dist/view-preload.cjs.map +1 -0
  49. package/dist/view-preload.js +8 -0
  50. package/dist/view-preload.js.map +1 -0
  51. package/dist/view-protocol.cjs +12 -0
  52. package/dist/view-protocol.cjs.map +1 -0
  53. package/dist/view-protocol.d.cts +257 -0
  54. package/dist/view-protocol.d.ts +257 -0
  55. package/dist/view-protocol.js +3 -0
  56. package/dist/view-protocol.js.map +1 -0
  57. package/package.json +25 -4
  58. package/dist/chunk-CPNTROWD.js.map +0 -1
package/dist/index.cjs CHANGED
@@ -1262,10 +1262,10 @@ function mountSettingsDialog(opts) {
1262
1262
  for (const s of opts.sections) {
1263
1263
  const found = handles.get(s.id)?.firstInvalid?.();
1264
1264
  if (!found) continue;
1265
- const bad = found instanceof HTMLElement ? found : found.el;
1266
- const message = (found instanceof HTMLElement ? void 0 : found.message) ?? bad.dataset["tip"];
1265
+ const bad2 = found instanceof HTMLElement ? found : found.el;
1266
+ const message = (found instanceof HTMLElement ? void 0 : found.message) ?? bad2.dataset["tip"];
1267
1267
  showTab(s.id);
1268
- bad.focus();
1268
+ bad2.focus();
1269
1269
  error.textContent = message || "Fix the highlighted fields first.";
1270
1270
  error.hidden = false;
1271
1271
  return;
@@ -1460,6 +1460,27 @@ function followButton(opts) {
1460
1460
  reflect();
1461
1461
  } };
1462
1462
  }
1463
+ function selectionFollowButton(opts) {
1464
+ const what = opts.what ?? "line";
1465
+ const b = el("button", "btn swin-follow", "Follow selection");
1466
+ b.type = "button";
1467
+ let on = opts.on;
1468
+ const reflect = () => {
1469
+ b.classList.toggle("on", on);
1470
+ b.setAttribute("aria-pressed", String(on));
1471
+ b.dataset["tip"] = on ? `Shows whatever is selected in the editor. Click to stay on this ${what}.` : `Stays on this ${what}. Click to follow the selection.`;
1472
+ };
1473
+ reflect();
1474
+ b.addEventListener("click", () => {
1475
+ on = !on;
1476
+ reflect();
1477
+ opts.onToggle(on);
1478
+ });
1479
+ return { el: b, set: (next) => {
1480
+ on = next;
1481
+ reflect();
1482
+ } };
1483
+ }
1463
1484
 
1464
1485
  // src/exit.ts
1465
1486
  function closeWithExit(element, done) {
@@ -3817,6 +3838,325 @@ function historyNav(onBack, onForward) {
3817
3838
  };
3818
3839
  }
3819
3840
 
3841
+ // src/view-protocol.ts
3842
+ var VIEW_API = 1;
3843
+ var VIEW_PORT_CHANNEL = "wildwinter-view:port";
3844
+ var VIEW_PORT_MESSAGE = "wildwinter-view:port";
3845
+
3846
+ // src/view-host-web.ts
3847
+ var PALETTE_TOKENS = [
3848
+ "--bg",
3849
+ "--surface",
3850
+ "--card",
3851
+ "--ink",
3852
+ "--muted",
3853
+ "--line",
3854
+ "--line-soft",
3855
+ "--accent",
3856
+ "--accent-soft",
3857
+ "--on-accent",
3858
+ "--danger",
3859
+ "--warn",
3860
+ "--ok",
3861
+ "--font-ui",
3862
+ "--font-read",
3863
+ "--font-mono",
3864
+ ...Array.from({ length: 12 }, (_, i) => `--char-${i}`)
3865
+ ];
3866
+ var GRAMMAR_TOKENS = [
3867
+ "--color-scheme",
3868
+ "--chrome-size",
3869
+ "--scrim",
3870
+ "--shadow-1",
3871
+ "--shadow-2",
3872
+ "--shadow-3",
3873
+ "--ease-standard",
3874
+ "--dur-quick",
3875
+ "--dur-base",
3876
+ "--dur-settle",
3877
+ "--dur-slow",
3878
+ "--dur-panel",
3879
+ "--dur-exit",
3880
+ "--radius-sm",
3881
+ "--radius-md",
3882
+ "--radius-lg",
3883
+ "--radius-pill",
3884
+ "--scroll-thumb",
3885
+ "--scroll-thumb-hover"
3886
+ ];
3887
+ var VIEW_THEME_TOKENS = [...PALETTE_TOKENS, ...GRAMMAR_TOKENS];
3888
+ function readTheme(root = document.documentElement, tokens = VIEW_THEME_TOKENS) {
3889
+ const style = getComputedStyle(root);
3890
+ const out = {};
3891
+ for (const name of tokens) {
3892
+ const value = style.getPropertyValue(name).trim();
3893
+ if (value !== "") out[name] = value;
3894
+ }
3895
+ const declared = (style.getPropertyValue("color-scheme") || style.colorScheme || "").trim();
3896
+ const words = declared.split(/\s+/);
3897
+ let scheme = "light";
3898
+ if (declared === "dark") scheme = "dark";
3899
+ else if (words.includes("light") && words.includes("dark")) {
3900
+ scheme = typeof matchMedia === "function" && matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
3901
+ }
3902
+ return { scheme, tokens: out };
3903
+ }
3904
+ function onViewPort(fn, target = window, closed) {
3905
+ const listener = (e) => {
3906
+ if (e.source !== target) return;
3907
+ const data = e.data;
3908
+ if (!data || typeof data !== "object" || data.type !== VIEW_PORT_MESSAGE) return;
3909
+ const port = e.ports?.[0];
3910
+ const meta = data.meta;
3911
+ if (!meta || typeof meta.window !== "number" || typeof meta.extension !== "string" || typeof meta.view !== "string") return;
3912
+ if (meta.closed === true) {
3913
+ closed?.(meta);
3914
+ return;
3915
+ }
3916
+ if (!port) return;
3917
+ fn(port, meta);
3918
+ };
3919
+ target.addEventListener("message", listener);
3920
+ return () => target.removeEventListener("message", listener);
3921
+ }
3922
+ var NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
3923
+ var isObj = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
3924
+ var isId = (v) => typeof v === "string" && v !== "";
3925
+ function isJson(v, depth = 0) {
3926
+ if (depth > 64) return false;
3927
+ if (v === null || typeof v === "string" || typeof v === "boolean") return true;
3928
+ if (typeof v === "number") return Number.isFinite(v);
3929
+ if (Array.isArray(v)) return v.every((x) => isJson(x, depth + 1));
3930
+ if (isObj(v)) return Object.getPrototypeOf(v) === Object.prototype && Object.values(v).every((x) => isJson(x, depth + 1));
3931
+ return false;
3932
+ }
3933
+ var bad = (message) => ({ code: "bad-request", message });
3934
+ function argsProblem(op, args) {
3935
+ switch (op) {
3936
+ case "project":
3937
+ case "selection":
3938
+ return null;
3939
+ case "node":
3940
+ if (!isId(args["id"])) return "A node request names the node by its id.";
3941
+ if (args["children"] !== void 0 && typeof args["children"] !== "boolean") return "A node request's children is true or false.";
3942
+ return null;
3943
+ case "media":
3944
+ if (!Array.isArray(args["ids"]) || !args["ids"].every(isId)) return "A media request lists the lines' ids.";
3945
+ return null;
3946
+ case "select":
3947
+ return isId(args["id"]) ? null : "A select request names the node by its id.";
3948
+ case "write": {
3949
+ if (typeof args["label"] !== "string" || args["label"].trim() === "") return "A write has a label, for the undo step.";
3950
+ const edits = args["edits"];
3951
+ if (!Array.isArray(edits)) return "A write lists its edits.";
3952
+ for (const e of edits) {
3953
+ if (!isObj(e) || !isId(e["node"])) return "Each edit names its node by id.";
3954
+ if (typeof e["field"] !== "string" || !NAME.test(e["field"])) return "Each edit names one of the extension's fields, never a path inside one.";
3955
+ if ("value" in e && e["value"] !== void 0 && !isJson(e["value"])) return `The value for ${e["field"]} isn't plain JSON.`;
3956
+ }
3957
+ return null;
3958
+ }
3959
+ }
3960
+ }
3961
+ var OPS = ["project", "node", "selection", "media", "write", "select"];
3962
+ function hostView(port, adapter, opts) {
3963
+ const { meta } = opts;
3964
+ const ext = meta.extension;
3965
+ const allowed = /* @__PURE__ */ new Set([ext, ...opts.reads]);
3966
+ const theme = opts.theme ?? (() => readTheme());
3967
+ const debounceMs = opts.debounceMs ?? 100;
3968
+ let welcomed = false;
3969
+ let closed = false;
3970
+ let pending = null;
3971
+ let timer3;
3972
+ const post = (msg) => {
3973
+ if (!closed) port.postMessage(msg);
3974
+ };
3975
+ const emit = (name, value) => {
3976
+ if (welcomed) post({ t: "event", name, value });
3977
+ };
3978
+ const filterNode = (node2) => {
3979
+ const keptExt = {};
3980
+ for (const [id, data] of Object.entries(node2.ext ?? {})) if (allowed.has(id)) keptExt[id] = data;
3981
+ const base = { id: node2.id, kind: node2.kind, parent: node2.parent, label: node2.label, host: node2.host ?? {}, ext: keptExt };
3982
+ const out = typeof node2.held === "string" ? { ...base, held: node2.held } : base;
3983
+ return node2.children ? { ...out, children: node2.children.map(filterNode) } : out;
3984
+ };
3985
+ const answer = async (op, args) => {
3986
+ switch (op) {
3987
+ case "project":
3988
+ return { value: filterNode(await adapter.project()) };
3989
+ case "node": {
3990
+ const node2 = await adapter.node(args["id"], args["children"] === true);
3991
+ return { value: node2 ? filterNode(node2) : null };
3992
+ }
3993
+ case "selection":
3994
+ return { value: await adapter.selection() };
3995
+ case "media":
3996
+ if (!adapter.media) return { refusal: { code: "unsupported", message: "This app has no recordings or timings for lines." } };
3997
+ return { value: await adapter.media(args["ids"]) };
3998
+ case "select":
3999
+ return { value: await adapter.select(args["id"]) };
4000
+ case "write": {
4001
+ const writable = adapter.writable(ext);
4002
+ if (writable.readOnly) return { refusal: { code: "read-only", message: writable.why ?? "This project is read-only just now." } };
4003
+ const edits = args["edits"].map((e) => "value" in e && e.value !== void 0 ? { node: e.node, field: e.field, value: e.value } : { node: e.node, field: e.field });
4004
+ if (edits.length === 0) return { value: { written: 0 } };
4005
+ if (edits.length > 1) {
4006
+ const projectId = (await adapter.project()).id;
4007
+ const onProject = edits.filter((e) => e.node === projectId).length;
4008
+ if (onProject > 0 && onProject < edits.length) {
4009
+ return { refusal: bad("A write changes the project or other nodes, not both: they undo separately. Make two writes.") };
4010
+ }
4011
+ }
4012
+ const refused = await adapter.write(ext, args["label"], edits);
4013
+ if (refused) return { refusal: refused };
4014
+ return { value: { written: edits.length } };
4015
+ }
4016
+ }
4017
+ };
4018
+ const request = async (msg) => {
4019
+ const id = msg["id"];
4020
+ if (typeof id !== "number" || !Number.isInteger(id)) return;
4021
+ const reply = (r) => post("refusal" in r ? { t: "reply", id, ok: false, refusal: r.refusal } : { t: "reply", id, ok: true, value: r.value });
4022
+ const op = msg["op"];
4023
+ if (typeof op !== "string" || !OPS.includes(op)) {
4024
+ reply({ refusal: bad(`There's no request called ${JSON.stringify(op)}.`) });
4025
+ return;
4026
+ }
4027
+ const args = msg["args"] ?? {};
4028
+ if (!isObj(args)) {
4029
+ reply({ refusal: bad("A request's args are an object.") });
4030
+ return;
4031
+ }
4032
+ const problem = argsProblem(op, args);
4033
+ if (problem) {
4034
+ reply({ refusal: bad(problem) });
4035
+ return;
4036
+ }
4037
+ try {
4038
+ reply(await answer(op, args));
4039
+ } catch (e) {
4040
+ reply({ refusal: { code: "failed", message: `The app couldn't do that: ${e instanceof Error ? e.message : String(e)}` } });
4041
+ }
4042
+ };
4043
+ const hello = (msg) => {
4044
+ const api = msg["api"];
4045
+ if (api !== VIEW_API) {
4046
+ const who = `${opts.names?.extension ?? ext}'s ${opts.names?.view ?? meta.view}`;
4047
+ const message = typeof api === "number" ? `${who} was built for view API ${api}; this app serves ${VIEW_API}.` : `${who} didn't say which view API it was built for; this app serves ${VIEW_API}.`;
4048
+ post({ t: "refused", message });
4049
+ close(false);
4050
+ return;
4051
+ }
4052
+ const welcome = {
4053
+ t: "welcome",
4054
+ api: VIEW_API,
4055
+ host: adapter.host,
4056
+ extension: { id: ext, version: meta.version, ...meta.pinned !== void 0 ? { pinned: meta.pinned } : {} },
4057
+ view: meta.view,
4058
+ settings: adapter.settings(ext),
4059
+ theme: theme(),
4060
+ writable: adapter.writable(ext)
4061
+ };
4062
+ welcomed = true;
4063
+ post(welcome);
4064
+ };
4065
+ const onMessage = (e) => {
4066
+ const msg = e.data;
4067
+ if (closed || !isObj(msg)) return;
4068
+ if (!welcomed) {
4069
+ if (msg["t"] === "hello") hello(msg);
4070
+ return;
4071
+ }
4072
+ if (msg["t"] === "request") void request(msg);
4073
+ else if (msg["t"] === "key" && typeof msg["combo"] === "string") adapter.key?.(msg["combo"]);
4074
+ };
4075
+ port.addEventListener("message", onMessage);
4076
+ port.start();
4077
+ const flush = () => {
4078
+ timer3 = void 0;
4079
+ const nodes = pending;
4080
+ pending = null;
4081
+ if (nodes === null) return;
4082
+ emit("changed", { nodes: nodes === "all" ? "all" : [...nodes] });
4083
+ };
4084
+ function close(sayClosing = true) {
4085
+ if (closed) return;
4086
+ if (sayClosing) emit("closing", {});
4087
+ clearTimeout(timer3);
4088
+ closed = true;
4089
+ port.removeEventListener("message", onMessage);
4090
+ port.close();
4091
+ }
4092
+ return {
4093
+ changed(nodes) {
4094
+ if (closed || !welcomed) return;
4095
+ if (nodes === "all" || pending === "all") pending = "all";
4096
+ else {
4097
+ pending ??= /* @__PURE__ */ new Set();
4098
+ for (const n of nodes) pending.add(n);
4099
+ }
4100
+ if (timer3 === void 0) timer3 = setTimeout(flush, debounceMs);
4101
+ },
4102
+ selection() {
4103
+ if (closed || !welcomed) return;
4104
+ void Promise.resolve().then(() => adapter.selection()).then((s) => emit("selection", s), () => {
4105
+ });
4106
+ },
4107
+ theme() {
4108
+ if (!closed) emit("theme", theme());
4109
+ },
4110
+ writable() {
4111
+ if (!closed) emit("writable", adapter.writable(ext));
4112
+ },
4113
+ close: () => close(true)
4114
+ };
4115
+ }
4116
+ function hostViews(setup, opts = {}) {
4117
+ const handles = /* @__PURE__ */ new Map();
4118
+ const drop = (meta) => {
4119
+ handles.get(meta.window)?.close();
4120
+ handles.delete(meta.window);
4121
+ };
4122
+ const stop = onViewPort((port, meta) => {
4123
+ drop(meta);
4124
+ const s = setup(meta);
4125
+ if (!s) {
4126
+ port.close();
4127
+ return;
4128
+ }
4129
+ handles.set(meta.window, hostView(port, s.adapter, {
4130
+ meta,
4131
+ reads: s.reads,
4132
+ ...s.names !== void 0 ? { names: s.names } : {},
4133
+ ...opts.theme !== void 0 ? { theme: opts.theme } : {},
4134
+ ...opts.debounceMs !== void 0 ? { debounceMs: opts.debounceMs } : {}
4135
+ }));
4136
+ }, opts.target ?? window, drop);
4137
+ const each = (f) => {
4138
+ for (const h of handles.values()) f(h);
4139
+ };
4140
+ return {
4141
+ get size() {
4142
+ return handles.size;
4143
+ },
4144
+ changed: (nodes) => each((h) => h.changed(nodes)),
4145
+ selection: () => each((h) => h.selection()),
4146
+ theme: () => each((h) => h.theme()),
4147
+ writable: () => each((h) => h.writable()),
4148
+ close: () => {
4149
+ each((h) => h.close());
4150
+ handles.clear();
4151
+ },
4152
+ dispose: () => {
4153
+ stop();
4154
+ each((h) => h.close());
4155
+ handles.clear();
4156
+ }
4157
+ };
4158
+ }
4159
+
3820
4160
  exports.EDIT_MENU = EDIT_MENU;
3821
4161
  exports.FILE_MENU = FILE_MENU;
3822
4162
  exports.GO_MENU = GO_MENU;
@@ -3832,7 +4172,11 @@ exports.PROPERTY_TYPES = PROPERTY_TYPES;
3832
4172
  exports.PUBLISH_MENU = PUBLISH_MENU;
3833
4173
  exports.RESERVED_PROPERTY_NAMES = RESERVED_PROPERTY_NAMES;
3834
4174
  exports.REVIEW_MENU = REVIEW_MENU;
4175
+ exports.VIEW_API = VIEW_API;
3835
4176
  exports.VIEW_MENU = VIEW_MENU;
4177
+ exports.VIEW_PORT_CHANNEL = VIEW_PORT_CHANNEL;
4178
+ exports.VIEW_PORT_MESSAGE = VIEW_PORT_MESSAGE;
4179
+ exports.VIEW_THEME_TOKENS = VIEW_THEME_TOKENS;
3836
4180
  exports.askIdentity = askIdentity;
3837
4181
  exports.bindPropertyName = bindPropertyName;
3838
4182
  exports.bindPropertyRef = bindPropertyRef;
@@ -3870,6 +4214,8 @@ exports.gameIdify = gameIdify;
3870
4214
  exports.hideTip = hideTip;
3871
4215
  exports.hintBar = hintBar;
3872
4216
  exports.historyNav = historyNav;
4217
+ exports.hostView = hostView;
4218
+ exports.hostViews = hostViews;
3873
4219
  exports.icon = icon;
3874
4220
  exports.iconBtn = iconBtn;
3875
4221
  exports.iconHtml = iconHtml;
@@ -3904,6 +4250,7 @@ exports.mountPropertyList = mountPropertyList;
3904
4250
  exports.mountSettingsDialog = mountSettingsDialog;
3905
4251
  exports.mountWelcome = mountWelcome;
3906
4252
  exports.moveItem = moveItem;
4253
+ exports.onViewPort = onViewPort;
3907
4254
  exports.openAnchoredPanel = openAnchoredPanel;
3908
4255
  exports.openComments = openComments;
3909
4256
  exports.openContextMenu = openContextMenu;
@@ -3920,6 +4267,7 @@ exports.problemName = problemName;
3920
4267
  exports.propertyNameProblem = propertyNameProblem;
3921
4268
  exports.propertyNameify = propertyNameify;
3922
4269
  exports.propertyRefProblem = propertyRefProblem;
4270
+ exports.readTheme = readTheme;
3923
4271
  exports.relativeTime = relativeTime;
3924
4272
  exports.renderStepperBar = renderStepperBar;
3925
4273
  exports.revalidatePropertyRefs = revalidatePropertyRefs;
@@ -3927,6 +4275,7 @@ exports.revealRow = revealRow;
3927
4275
  exports.revealRowWhenReady = revealRowWhenReady;
3928
4276
  exports.rungStrip = rungStrip;
3929
4277
  exports.saveIndicator = saveIndicator;
4278
+ exports.selectionFollowButton = selectionFollowButton;
3930
4279
  exports.setKeyPlatform = setKeyPlatform;
3931
4280
  exports.showAbout = showAbout;
3932
4281
  exports.showUpdaterDialog = showUpdaterDialog;