@wildwinter/app-shell 0.17.0 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -64,13 +64,61 @@ event loop (16ms), so a fast inner loop neither floods IPC nor stutters.
64
64
  **A strip, not a modal.** The window that started the job shows it; the rest of
65
65
  the app stays usable.
66
66
 
67
+ ## v0.18 (the bars, and the third semantic colour)
68
+
69
+ Three drawings each app had grown its own copy of, and the token the family kept
70
+ needing and never had.
71
+
72
+ - **`renderStepperBar`** - a bottom bar that walks a list of things to attend to,
73
+ one at a time: count, previous, position, next, the entry as a button, an
74
+ action slot. Four bars across the two apps are this shape (problems, review
75
+ walks). It knows about a list and an index; it knows nothing about what a
76
+ problem or a comment is.
77
+
78
+ ```ts
79
+ import { renderStepperBar } from "@wildwinter/app-shell";
80
+ import "@wildwinter/app-shell/stepper.css";
81
+ renderStepperBar(host, {
82
+ items: problems.map((p) => ({ kind: p.severity, kindClass: `sev-${p.severity}`, where: p.path, text: p.message })),
83
+ at, tone: "danger", onStep: (i) => { at = i; paint(); }, onGo: (i) => jumpTo(problems[i]),
84
+ });
85
+ ```
86
+
87
+ Whether stepping NAVIGATES is the caller's decision, and the bar takes no view:
88
+ an ambient surface moves the view and never the focus; a mode you entered may
89
+ take you somewhere.
90
+
91
+ - **`saveIndicator`** - the six lines that draw what `createSaveController`
92
+ already computes. Three states, always present: a dot that only ever appears
93
+ to worry you leaves "did that get written?" unanswered.
94
+
95
+ ```ts
96
+ const ind = saveIndicator();
97
+ topbar.append(ind.el);
98
+ const saver = createSaveController({ write, onStatus: ind.set });
99
+ ```
100
+
101
+ - **`staleBar`** - a running session that has fallen behind its source. The app
102
+ supplies the noun and the callback; the sentence is shared, because both apps
103
+ had written it independently and landed a word apart.
104
+
105
+ ```ts
106
+ staleBar({ subject: "The project", onRestart: () => void rebuild() });
107
+ ```
108
+
109
+ - **`--ok`** joins `--danger` and `--warn` in `tokens.css`, with a `light-dark()`
110
+ default every palette should override in its own `theme.css`. It is the only
111
+ colour in the grammar layer, and it is there because it kept not being
112
+ anywhere: "all is well" was being met with raw green literals that could not
113
+ theme-shift.
114
+
67
115
  ## Roadmap
68
116
 
69
117
  Later slices lift the rest of the common shell from Patterpad (canonical),
70
- reconciling Storylet Studio's variants: the pane grid + toggles, the save/dirty
71
- controller, the command palette, the anchored popover/panel + toast, and the
72
- detached-window out-of-date / restart pattern. Storylet Studio migrates each
73
- first (proving app-agnosticism); Patterpad follows.
118
+ reconciling Storylet Studio's variants: the pane grid + toggles, the command
119
+ palette, and version control's renderer half. Storylet Studio migrates each
120
+ first (proving app-agnosticism); Patterpad follows - see
121
+ `patterkit/design/from-storylets/patterpad-onto-the-shell.md` for that order.
74
122
 
75
123
  ## Release
76
124
 
package/dist/anchored.css CHANGED
@@ -10,6 +10,11 @@
10
10
  color: var(--ink);
11
11
  box-shadow: var(--shadow-2, var(--shadow-1));
12
12
  overflow: hidden;
13
+ animation: var(--anim-panel-in);
14
+ }
15
+ .shell-anchored.closing {
16
+ animation: var(--anim-panel-out);
17
+ pointer-events: none;
13
18
  }
14
19
  .shell-anchored-head {
15
20
  flex: none;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/anchored.css"],"sourcesContent":["/* The anchored panel (anchored.ts): a floating panel hung off the thing it is\n about. Sized and positioned in script (the placement has to measure); this is\n only what it looks like. Its body scrolls, because the placement caps the\n height rather than letting a long thread run off the screen. */\n.shell-anchored {\n position: fixed; z-index: 40;\n display: flex; flex-direction: column;\n border: 1px solid var(--line);\n border-radius: var(--radius-md);\n background: var(--surface); color: var(--ink);\n box-shadow: var(--shadow-2, var(--shadow-1));\n overflow: hidden;\n}\n.shell-anchored-head {\n flex: none;\n display: flex; align-items: center; justify-content: space-between; gap: 8px;\n padding: 8px 10px;\n border-bottom: 1px solid var(--line-soft);\n}\n.shell-anchored-title {\n font: 600 0.72rem/1 var(--font-ui);\n text-transform: uppercase; letter-spacing: 0.04em; color: var(--muted);\n}\n.shell-anchored-close {\n border: 0; background: none; color: var(--muted);\n font-size: 0.8rem; line-height: 1; padding: 0 2px; cursor: pointer;\n}\n.shell-anchored-close:hover { color: var(--accent); }\n.shell-anchored-body {\n flex: 1; min-height: 0; overflow-y: auto;\n display: flex; flex-direction: column; gap: 8px;\n padding: 10px;\n}\n\n/* The gameId editor (id-editor.ts). Refusal is the whole point of the design, so\n it has to LOOK refused: the hint reddens and Set goes flat. */\n.shell-id-editor .shell-anchored-body { display: flex; flex-direction: column; gap: 8px; }\n.shell-id-input {\n font: 0.82rem var(--font-mono, monospace);\n padding: 5px 8px; border: 1px solid var(--line); border-radius: var(--radius-sm);\n background: var(--card); color: var(--ink);\n}\n.shell-id-hint { font: 0.74rem/1.4 var(--font-ui); color: var(--muted); }\n.shell-id-hint.bad { color: var(--danger, #b00); }\n.shell-id-actions { display: flex; align-items: center; justify-content: space-between; gap: 8px; }\n.shell-id-reset {\n font: 0.74rem var(--font-ui); padding: 3px 10px; cursor: pointer;\n background: none; border: 1px dashed var(--line); border-radius: var(--radius-sm); color: var(--muted);\n}\n.shell-id-reset:hover { border-color: var(--accent); color: var(--accent); }\n.shell-id-set {\n font: 0.78rem var(--font-ui); padding: 3px 12px; cursor: pointer;\n border: 1px solid var(--accent); border-radius: var(--radius-sm);\n background: var(--accent); color: var(--surface);\n}\n.shell-id-set:disabled {\n cursor: default; background: none; color: var(--muted); border-color: var(--line);\n}\n"],"mappings":";AAIA,CAAC;AACC,YAAU;AAAO,WAAS;AAC1B,WAAS;AAAM,kBAAgB;AAC/B,UAAQ,IAAI,MAAM,IAAI;AACtB,iBAAe,IAAI;AACnB,cAAY,IAAI;AAAY,SAAO,IAAI;AACvC,cAAY,IAAI,UAAU,EAAE,IAAI;AAChC,YAAU;AACZ;AACA,CAAC;AACC,QAAM;AACN,WAAS;AAAM,eAAa;AAAQ,mBAAiB;AAAe,OAAK;AACzE,WAAS,IAAI;AACb,iBAAe,IAAI,MAAM,IAAI;AAC/B;AACA,CAAC;AACC,QAAM,IAAI,OAAO,CAAC,EAAE,IAAI;AACxB,kBAAgB;AAAW,kBAAgB;AAAQ,SAAO,IAAI;AAChE;AACA,CAAC;AACC,UAAQ;AAAG,cAAY;AAAM,SAAO,IAAI;AACxC,aAAW;AAAQ,eAAa;AAAG,WAAS,EAAE;AAAK,UAAQ;AAC7D;AACA,CAJC,oBAIoB;AAAS,SAAO,IAAI;AAAW;AACpD,CAAC;AACC,QAAM;AAAG,cAAY;AAAG,cAAY;AACpC,WAAS;AAAM,kBAAgB;AAAQ,OAAK;AAC5C,WAAS;AACX;AAIA,CAAC,gBAAgB,CARhB;AAQuC,WAAS;AAAM,kBAAgB;AAAQ,OAAK;AAAK;AACzF,CAAC;AACC,QAAM,QAAQ,IAAI,WAAW,EAAE;AAC/B,WAAS,IAAI;AAAK,UAAQ,IAAI,MAAM,IAAI;AAAS,iBAAe,IAAI;AACpE,cAAY,IAAI;AAAS,SAAO,IAAI;AACtC;AACA,CAAC;AAAgB,QAAM,OAAO,CAAC,IAAI,IAAI;AAAY,SAAO,IAAI;AAAU;AACxE,CADC,aACa,CAAC;AAAM,SAAO,IAAI,QAAQ,EAAE;AAAO;AACjD,CAAC;AAAmB,WAAS;AAAM,eAAa;AAAQ,mBAAiB;AAAe,OAAK;AAAK;AAClG,CAAC;AACC,QAAM,QAAQ,IAAI;AAAY,WAAS,IAAI;AAAM,UAAQ;AACzD,cAAY;AAAM,UAAQ,IAAI,OAAO,IAAI;AAAS,iBAAe,IAAI;AAAc,SAAO,IAAI;AAChG;AACA,CAJC,cAIc;AAAS,gBAAc,IAAI;AAAW,SAAO,IAAI;AAAW;AAC3E,CAAC;AACC,QAAM,QAAQ,IAAI;AAAY,WAAS,IAAI;AAAM,UAAQ;AACzD,UAAQ,IAAI,MAAM,IAAI;AAAW,iBAAe,IAAI;AACpD,cAAY,IAAI;AAAW,SAAO,IAAI;AACxC;AACA,CALC,YAKY;AACX,UAAQ;AAAS,cAAY;AAAM,SAAO,IAAI;AAAU,gBAAc,IAAI;AAC5E;","names":[]}
1
+ {"version":3,"sources":["../src/anchored.css"],"sourcesContent":["/* The anchored panel (anchored.ts): a floating panel hung off the thing it is\n about. Sized and positioned in script (the placement has to measure); this is\n only what it looks like. Its body scrolls, because the placement caps the\n height rather than letting a long thread run off the screen. */\n.shell-anchored {\n position: fixed; z-index: 40;\n display: flex; flex-direction: column;\n border: 1px solid var(--line);\n border-radius: var(--radius-md);\n background: var(--surface); color: var(--ink);\n box-shadow: var(--shadow-2, var(--shadow-1));\n overflow: hidden;\n /* The family's panel motion. Placement is set in script as left/top, so a\n transform-based keyframe composes with it rather than fighting it. */\n animation: var(--anim-panel-in);\n}\n/* Exit: .closing swaps the enter keyframe for its reverse, and the fading panel\n stops taking clicks so a second press cannot reach a control on its way out.\n Zero-duration under prefers-reduced-motion, where teardown is immediate. */\n.shell-anchored.closing { animation: var(--anim-panel-out); pointer-events: none; }\n.shell-anchored-head {\n flex: none;\n display: flex; align-items: center; justify-content: space-between; gap: 8px;\n padding: 8px 10px;\n border-bottom: 1px solid var(--line-soft);\n}\n.shell-anchored-title {\n font: 600 0.72rem/1 var(--font-ui);\n text-transform: uppercase; letter-spacing: 0.04em; color: var(--muted);\n}\n.shell-anchored-close {\n border: 0; background: none; color: var(--muted);\n font-size: 0.8rem; line-height: 1; padding: 0 2px; cursor: pointer;\n}\n.shell-anchored-close:hover { color: var(--accent); }\n.shell-anchored-body {\n flex: 1; min-height: 0; overflow-y: auto;\n display: flex; flex-direction: column; gap: 8px;\n padding: 10px;\n}\n\n/* The gameId editor (id-editor.ts). Refusal is the whole point of the design, so\n it has to LOOK refused: the hint reddens and Set goes flat. */\n.shell-id-editor .shell-anchored-body { display: flex; flex-direction: column; gap: 8px; }\n.shell-id-input {\n font: 0.82rem var(--font-mono, monospace);\n padding: 5px 8px; border: 1px solid var(--line); border-radius: var(--radius-sm);\n background: var(--card); color: var(--ink);\n}\n.shell-id-hint { font: 0.74rem/1.4 var(--font-ui); color: var(--muted); }\n.shell-id-hint.bad { color: var(--danger, #b00); }\n.shell-id-actions { display: flex; align-items: center; justify-content: space-between; gap: 8px; }\n.shell-id-reset {\n font: 0.74rem var(--font-ui); padding: 3px 10px; cursor: pointer;\n background: none; border: 1px dashed var(--line); border-radius: var(--radius-sm); color: var(--muted);\n}\n.shell-id-reset:hover { border-color: var(--accent); color: var(--accent); }\n.shell-id-set {\n font: 0.78rem var(--font-ui); padding: 3px 12px; cursor: pointer;\n border: 1px solid var(--accent); border-radius: var(--radius-sm);\n background: var(--accent); color: var(--surface);\n}\n.shell-id-set:disabled {\n cursor: default; background: none; color: var(--muted); border-color: var(--line);\n}\n"],"mappings":";AAIA,CAAC;AACC,YAAU;AAAO,WAAS;AAC1B,WAAS;AAAM,kBAAgB;AAC/B,UAAQ,IAAI,MAAM,IAAI;AACtB,iBAAe,IAAI;AACnB,cAAY,IAAI;AAAY,SAAO,IAAI;AACvC,cAAY,IAAI,UAAU,EAAE,IAAI;AAChC,YAAU;AAGV,aAAW,IAAI;AACjB;AAIA,CAfC,cAec,CAAC;AAAU,aAAW,IAAI;AAAmB,kBAAgB;AAAM;AAClF,CAAC;AACC,QAAM;AACN,WAAS;AAAM,eAAa;AAAQ,mBAAiB;AAAe,OAAK;AACzE,WAAS,IAAI;AACb,iBAAe,IAAI,MAAM,IAAI;AAC/B;AACA,CAAC;AACC,QAAM,IAAI,OAAO,CAAC,EAAE,IAAI;AACxB,kBAAgB;AAAW,kBAAgB;AAAQ,SAAO,IAAI;AAChE;AACA,CAAC;AACC,UAAQ;AAAG,cAAY;AAAM,SAAO,IAAI;AACxC,aAAW;AAAQ,eAAa;AAAG,WAAS,EAAE;AAAK,UAAQ;AAC7D;AACA,CAJC,oBAIoB;AAAS,SAAO,IAAI;AAAW;AACpD,CAAC;AACC,QAAM;AAAG,cAAY;AAAG,cAAY;AACpC,WAAS;AAAM,kBAAgB;AAAQ,OAAK;AAC5C,WAAS;AACX;AAIA,CAAC,gBAAgB,CARhB;AAQuC,WAAS;AAAM,kBAAgB;AAAQ,OAAK;AAAK;AACzF,CAAC;AACC,QAAM,QAAQ,IAAI,WAAW,EAAE;AAC/B,WAAS,IAAI;AAAK,UAAQ,IAAI,MAAM,IAAI;AAAS,iBAAe,IAAI;AACpE,cAAY,IAAI;AAAS,SAAO,IAAI;AACtC;AACA,CAAC;AAAgB,QAAM,OAAO,CAAC,IAAI,IAAI;AAAY,SAAO,IAAI;AAAU;AACxE,CADC,aACa,CAAC;AAAM,SAAO,IAAI,QAAQ,EAAE;AAAO;AACjD,CAAC;AAAmB,WAAS;AAAM,eAAa;AAAQ,mBAAiB;AAAe,OAAK;AAAK;AAClG,CAAC;AACC,QAAM,QAAQ,IAAI;AAAY,WAAS,IAAI;AAAM,UAAQ;AACzD,cAAY;AAAM,UAAQ,IAAI,OAAO,IAAI;AAAS,iBAAe,IAAI;AAAc,SAAO,IAAI;AAChG;AACA,CAJC,cAIc;AAAS,gBAAc,IAAI;AAAW,SAAO,IAAI;AAAW;AAC3E,CAAC;AACC,QAAM,QAAQ,IAAI;AAAY,WAAS,IAAI;AAAM,UAAQ;AACzD,UAAQ,IAAI,MAAM,IAAI;AAAW,iBAAe,IAAI;AACpD,cAAY,IAAI;AAAW,SAAO,IAAI;AACxC;AACA,CALC,YAKY;AACX,UAAQ;AAAS,cAAY;AAAM,SAAO,IAAI;AAAU,gBAAc,IAAI;AAC5E;","names":[]}
package/dist/comments.css CHANGED
@@ -1,4 +1,11 @@
1
1
  /* src/comments.css */
2
+ .shell-cmt-quote {
3
+ margin: 0;
4
+ padding: 0.2rem 0.5rem;
5
+ border-left: 2px solid var(--accent);
6
+ color: var(--muted);
7
+ font: italic 0.8rem var(--font-ui);
8
+ }
2
9
  .shell-cmt-list {
3
10
  display: flex;
4
11
  flex-direction: column;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/comments.css"],"sourcesContent":["/* A comment thread inside the anchored panel (comments.ts). */\n.shell-cmt-list { display: flex; flex-direction: column; gap: 8px; }\n.shell-cmt-list.resolved { opacity: 0.55; }\n.shell-cmt-msg-head { display: flex; gap: 8px; align-items: baseline; }\n/* The delete: QUIET but always there.\n It was rollover-revealed at first, following the grip grammar the lists use,\n and that was the wrong borrowing: a list row hides its grip because a dense\n column of them would be clutter, whereas this popover holds two or three\n messages and is already a surface you opened on purpose. Hidden, the only\n way to delete a comment was to discover that hovering one exact row revealed\n something - which is to say there was no way to delete a comment. Reported as\n \"there is apparently no delete option\", which is exactly right.\n Low contrast carries \"secondary\"; invisibility carries \"absent\". */\n.shell-cmt-del {\n margin-left: auto; flex: none; padding: 0 4px; line-height: 1;\n background: none; border: none; cursor: pointer;\n font-size: 0.72rem; color: var(--muted); opacity: 0.55;\n transition: opacity var(--dur-quick, 120ms) var(--ease-standard, ease),\n color var(--dur-quick, 120ms) var(--ease-standard, ease);\n}\n.shell-cmt-msg:hover .shell-cmt-del, .shell-cmt-del:focus-visible { opacity: 1; }\n.shell-cmt-del:hover { opacity: 1; color: var(--danger, #b00); }\n/* A withdrawn message keeps its place in the thread and stops asking to be read. */\n.shell-cmt-msg.gone .shell-cmt-body { font-style: italic; color: var(--muted); }\n.shell-cmt-author { font: 600 0.76rem/1.2 var(--font-ui); }\n.shell-cmt-ts { font: 0.68rem/1.2 var(--font-ui); color: var(--muted); }\n.shell-cmt-body { font: 0.82rem/1.45 var(--font-read, var(--font-ui)); white-space: pre-wrap; }\n.shell-cmt-input {\n width: 100%; box-sizing: border-box; resize: vertical;\n border: 1px solid var(--line-soft); border-radius: var(--radius-sm);\n background: var(--card); color: var(--ink);\n font: 0.82rem/1.4 var(--font-read, var(--font-ui)); padding: 6px 8px;\n}\n.shell-cmt-input:focus { outline: none; border-color: var(--accent); }\n.shell-cmt-actions { display: flex; gap: 6px; justify-content: flex-end; }\n.shell-cmt-btn {\n font: 0.76rem/1 var(--font-ui); padding: 5px 10px;\n border-radius: var(--radius-sm); border: 1px solid var(--line);\n background: var(--card); color: var(--ink); cursor: pointer;\n}\n.shell-cmt-btn.primary { background: var(--accent); border-color: var(--accent); color: var(--on-accent, #fff); }\n/* The picker, when one thing has more than one thread. */\n.shell-cmt-row {\n display: flex; gap: 6px; align-items: baseline; width: 100%; text-align: left;\n border: 0; background: none; color: var(--ink); cursor: pointer;\n padding: 6px 4px; border-radius: var(--radius-sm);\n}\n.shell-cmt-row:hover { background: var(--accent-soft); }\n.shell-cmt-row.resolved { opacity: 0.55; }\n.shell-cmt-row-author { font: 600 0.74rem/1.2 var(--font-ui); flex: none; }\n.shell-cmt-row-gist {\n font: 0.78rem/1.3 var(--font-read, var(--font-ui)); color: var(--muted);\n overflow: hidden; text-overflow: ellipsis; white-space: nowrap;\n}\n.shell-cmt-row-count { margin-left: auto; font: 0.7rem/1 var(--font-ui); color: var(--muted); }\n.shell-cmt-new {\n align-self: flex-start; font: 0.76rem/1 var(--font-ui);\n border: 1px dashed var(--line); background: none; color: var(--muted);\n border-radius: var(--radius-sm); padding: 5px 10px; cursor: pointer;\n}\n.shell-cmt-new:hover { color: var(--accent); border-color: var(--accent); }\n"],"mappings":";AACA,CAAC;AAAiB,WAAS;AAAM,kBAAgB;AAAQ,OAAK;AAAK;AACnE,CADC,cACc,CAAC;AAAW,WAAS;AAAM;AAC1C,CAAC;AAAqB,WAAS;AAAM,OAAK;AAAK,eAAa;AAAU;AAUtE,CAAC;AACC,eAAa;AAAM,QAAM;AAAM,WAAS,EAAE;AAAK,eAAa;AAC5D,cAAY;AAAM,UAAQ;AAAM,UAAQ;AACxC,aAAW;AAAS,SAAO,IAAI;AAAU,WAAS;AAClD,cAAY,QAAQ,IAAI,WAAW,EAAE,OAAO,IAAI,eAAe,EAAE,KAAK,EACpE,MAAM,IAAI,WAAW,EAAE,OAAO,IAAI,eAAe,EAAE;AACvD;AACA,CAAC,aAAa,OAAO,CAPpB;AAOoC,CAPpC,aAOkD;AAAiB,WAAS;AAAG;AAChF,CARC,aAQa;AAAS,WAAS;AAAG,SAAO,IAAI,QAAQ,EAAE;AAAO;AAE/D,CAHC,aAGa,CAAC,KAAK,CAAC;AAAiB,cAAY;AAAQ,SAAO,IAAI;AAAU;AAC/E,CAAC;AAAmB,QAAM,IAAI,OAAO,CAAC,IAAI,IAAI;AAAY;AAC1D,CAAC;AAAe,QAAM,OAAO,CAAC,IAAI,IAAI;AAAY,SAAO,IAAI;AAAU;AACvE,CAHqB;AAGH,QAAM,OAAO,CAAC,KAAK,IAAI,WAAW,EAAE,IAAI;AAAa,eAAa;AAAU;AAC9F,CAAC;AACC,SAAO;AAAM,cAAY;AAAY,UAAQ;AAC7C,UAAQ,IAAI,MAAM,IAAI;AAAc,iBAAe,IAAI;AACvD,cAAY,IAAI;AAAS,SAAO,IAAI;AACpC,QAAM,OAAO,CAAC,IAAI,IAAI,WAAW,EAAE,IAAI;AAAa,WAAS,IAAI;AACnE;AACA,CANC,eAMe;AAAS,WAAS;AAAM,gBAAc,IAAI;AAAW;AACrE,CAAC;AAAoB,WAAS;AAAM,OAAK;AAAK,mBAAiB;AAAU;AACzE,CAAC;AACC,QAAM,OAAO,CAAC,EAAE,IAAI;AAAY,WAAS,IAAI;AAC7C,iBAAe,IAAI;AAAc,UAAQ,IAAI,MAAM,IAAI;AACvD,cAAY,IAAI;AAAS,SAAO,IAAI;AAAQ,UAAQ;AACtD;AACA,CALC,aAKa,CAAC;AAAU,cAAY,IAAI;AAAW,gBAAc,IAAI;AAAW,SAAO,IAAI,WAAW,EAAE;AAAO;AAEhH,CAAC;AACC,WAAS;AAAM,OAAK;AAAK,eAAa;AAAU,SAAO;AAAM,cAAY;AACzE,UAAQ;AAAG,cAAY;AAAM,SAAO,IAAI;AAAQ,UAAQ;AACxD,WAAS,IAAI;AAAK,iBAAe,IAAI;AACvC;AACA,CALC,aAKa;AAAS,cAAY,IAAI;AAAgB;AACvD,CANC,aAMa,CA9CE;AA8CU,WAAS;AAAM;AACzC,CAAC;AAAuB,QAAM,IAAI,OAAO,CAAC,IAAI,IAAI;AAAY,QAAM;AAAM;AAC1E,CAAC;AACC,QAAM,OAAO,CAAC,IAAI,IAAI,WAAW,EAAE,IAAI;AAAa,SAAO,IAAI;AAC/D,YAAU;AAAQ,iBAAe;AAAU,eAAa;AAC1D;AACA,CAAC;AAAsB,eAAa;AAAM,QAAM,MAAM,CAAC,EAAE,IAAI;AAAY,SAAO,IAAI;AAAU;AAC9F,CAAC;AACC,cAAY;AAAY,QAAM,OAAO,CAAC,EAAE,IAAI;AAC5C,UAAQ,IAAI,OAAO,IAAI;AAAS,cAAY;AAAM,SAAO,IAAI;AAC7D,iBAAe,IAAI;AAAc,WAAS,IAAI;AAAM,UAAQ;AAC9D;AACA,CALC,aAKa;AAAS,SAAO,IAAI;AAAW,gBAAc,IAAI;AAAW;","names":[]}
1
+ {"version":3,"sources":["../src/comments.css"],"sourcesContent":["/* A comment thread inside the anchored panel (comments.ts). */\n\n/* The span the thread is pinned to (quoteFor). A quotation rather than a heading:\n these are somebody else's words, not a title the panel gave itself. */\n.shell-cmt-quote {\n margin: 0; padding: 0.2rem 0.5rem;\n border-left: 2px solid var(--accent);\n color: var(--muted); font: italic 0.8rem var(--font-ui);\n}\n\n.shell-cmt-list { display: flex; flex-direction: column; gap: 8px; }\n.shell-cmt-list.resolved { opacity: 0.55; }\n.shell-cmt-msg-head { display: flex; gap: 8px; align-items: baseline; }\n/* The delete: QUIET but always there.\n It was rollover-revealed at first, following the grip grammar the lists use,\n and that was the wrong borrowing: a list row hides its grip because a dense\n column of them would be clutter, whereas this popover holds two or three\n messages and is already a surface you opened on purpose. Hidden, the only\n way to delete a comment was to discover that hovering one exact row revealed\n something - which is to say there was no way to delete a comment. Reported as\n \"there is apparently no delete option\", which is exactly right.\n Low contrast carries \"secondary\"; invisibility carries \"absent\". */\n.shell-cmt-del {\n margin-left: auto; flex: none; padding: 0 4px; line-height: 1;\n background: none; border: none; cursor: pointer;\n font-size: 0.72rem; color: var(--muted); opacity: 0.55;\n transition: opacity var(--dur-quick, 120ms) var(--ease-standard, ease),\n color var(--dur-quick, 120ms) var(--ease-standard, ease);\n}\n.shell-cmt-msg:hover .shell-cmt-del, .shell-cmt-del:focus-visible { opacity: 1; }\n.shell-cmt-del:hover { opacity: 1; color: var(--danger, #b00); }\n/* A withdrawn message keeps its place in the thread and stops asking to be read. */\n.shell-cmt-msg.gone .shell-cmt-body { font-style: italic; color: var(--muted); }\n.shell-cmt-author { font: 600 0.76rem/1.2 var(--font-ui); }\n.shell-cmt-ts { font: 0.68rem/1.2 var(--font-ui); color: var(--muted); }\n.shell-cmt-body { font: 0.82rem/1.45 var(--font-read, var(--font-ui)); white-space: pre-wrap; }\n.shell-cmt-input {\n width: 100%; box-sizing: border-box; resize: vertical;\n border: 1px solid var(--line-soft); border-radius: var(--radius-sm);\n background: var(--card); color: var(--ink);\n font: 0.82rem/1.4 var(--font-read, var(--font-ui)); padding: 6px 8px;\n}\n.shell-cmt-input:focus { outline: none; border-color: var(--accent); }\n.shell-cmt-actions { display: flex; gap: 6px; justify-content: flex-end; }\n.shell-cmt-btn {\n font: 0.76rem/1 var(--font-ui); padding: 5px 10px;\n border-radius: var(--radius-sm); border: 1px solid var(--line);\n background: var(--card); color: var(--ink); cursor: pointer;\n}\n.shell-cmt-btn.primary { background: var(--accent); border-color: var(--accent); color: var(--on-accent, #fff); }\n/* The picker, when one thing has more than one thread. */\n.shell-cmt-row {\n display: flex; gap: 6px; align-items: baseline; width: 100%; text-align: left;\n border: 0; background: none; color: var(--ink); cursor: pointer;\n padding: 6px 4px; border-radius: var(--radius-sm);\n}\n.shell-cmt-row:hover { background: var(--accent-soft); }\n.shell-cmt-row.resolved { opacity: 0.55; }\n.shell-cmt-row-author { font: 600 0.74rem/1.2 var(--font-ui); flex: none; }\n.shell-cmt-row-gist {\n font: 0.78rem/1.3 var(--font-read, var(--font-ui)); color: var(--muted);\n overflow: hidden; text-overflow: ellipsis; white-space: nowrap;\n}\n.shell-cmt-row-count { margin-left: auto; font: 0.7rem/1 var(--font-ui); color: var(--muted); }\n.shell-cmt-new {\n align-self: flex-start; font: 0.76rem/1 var(--font-ui);\n border: 1px dashed var(--line); background: none; color: var(--muted);\n border-radius: var(--radius-sm); padding: 5px 10px; cursor: pointer;\n}\n.shell-cmt-new:hover { color: var(--accent); border-color: var(--accent); }\n"],"mappings":";AAIA,CAAC;AACC,UAAQ;AAAG,WAAS,OAAO;AAC3B,eAAa,IAAI,MAAM,IAAI;AAC3B,SAAO,IAAI;AAAU,QAAM,OAAO,OAAO,IAAI;AAC/C;AAEA,CAAC;AAAiB,WAAS;AAAM,kBAAgB;AAAQ,OAAK;AAAK;AACnE,CADC,cACc,CAAC;AAAW,WAAS;AAAM;AAC1C,CAAC;AAAqB,WAAS;AAAM,OAAK;AAAK,eAAa;AAAU;AAUtE,CAAC;AACC,eAAa;AAAM,QAAM;AAAM,WAAS,EAAE;AAAK,eAAa;AAC5D,cAAY;AAAM,UAAQ;AAAM,UAAQ;AACxC,aAAW;AAAS,SAAO,IAAI;AAAU,WAAS;AAClD,cAAY,QAAQ,IAAI,WAAW,EAAE,OAAO,IAAI,eAAe,EAAE,KAAK,EACpE,MAAM,IAAI,WAAW,EAAE,OAAO,IAAI,eAAe,EAAE;AACvD;AACA,CAAC,aAAa,OAAO,CAPpB;AAOoC,CAPpC,aAOkD;AAAiB,WAAS;AAAG;AAChF,CARC,aAQa;AAAS,WAAS;AAAG,SAAO,IAAI,QAAQ,EAAE;AAAO;AAE/D,CAHC,aAGa,CAAC,KAAK,CAAC;AAAiB,cAAY;AAAQ,SAAO,IAAI;AAAU;AAC/E,CAAC;AAAmB,QAAM,IAAI,OAAO,CAAC,IAAI,IAAI;AAAY;AAC1D,CAAC;AAAe,QAAM,OAAO,CAAC,IAAI,IAAI;AAAY,SAAO,IAAI;AAAU;AACvE,CAHqB;AAGH,QAAM,OAAO,CAAC,KAAK,IAAI,WAAW,EAAE,IAAI;AAAa,eAAa;AAAU;AAC9F,CAAC;AACC,SAAO;AAAM,cAAY;AAAY,UAAQ;AAC7C,UAAQ,IAAI,MAAM,IAAI;AAAc,iBAAe,IAAI;AACvD,cAAY,IAAI;AAAS,SAAO,IAAI;AACpC,QAAM,OAAO,CAAC,IAAI,IAAI,WAAW,EAAE,IAAI;AAAa,WAAS,IAAI;AACnE;AACA,CANC,eAMe;AAAS,WAAS;AAAM,gBAAc,IAAI;AAAW;AACrE,CAAC;AAAoB,WAAS;AAAM,OAAK;AAAK,mBAAiB;AAAU;AACzE,CAAC;AACC,QAAM,OAAO,CAAC,EAAE,IAAI;AAAY,WAAS,IAAI;AAC7C,iBAAe,IAAI;AAAc,UAAQ,IAAI,MAAM,IAAI;AACvD,cAAY,IAAI;AAAS,SAAO,IAAI;AAAQ,UAAQ;AACtD;AACA,CALC,aAKa,CAAC;AAAU,cAAY,IAAI;AAAW,gBAAc,IAAI;AAAW,SAAO,IAAI,WAAW,EAAE;AAAO;AAEhH,CAAC;AACC,WAAS;AAAM,OAAK;AAAK,eAAa;AAAU,SAAO;AAAM,cAAY;AACzE,UAAQ;AAAG,cAAY;AAAM,SAAO,IAAI;AAAQ,UAAQ;AACxD,WAAS,IAAI;AAAK,iBAAe,IAAI;AACvC;AACA,CALC,aAKa;AAAS,cAAY,IAAI;AAAgB;AACvD,CANC,aAMa,CA9CE;AA8CU,WAAS;AAAM;AACzC,CAAC;AAAuB,QAAM,IAAI,OAAO,CAAC,IAAI,IAAI;AAAY,QAAM;AAAM;AAC1E,CAAC;AACC,QAAM,OAAO,CAAC,IAAI,IAAI,WAAW,EAAE,IAAI;AAAa,SAAO,IAAI;AAC/D,YAAU;AAAQ,iBAAe;AAAU,eAAa;AAC1D;AACA,CAAC;AAAsB,eAAa;AAAM,QAAM,MAAM,CAAC,EAAE,IAAI;AAAY,SAAO,IAAI;AAAU;AAC9F,CAAC;AACC,cAAY;AAAY,QAAM,OAAO,CAAC,EAAE,IAAI;AAC5C,UAAQ,IAAI,OAAO,IAAI;AAAS,cAAY;AAAM,SAAO,IAAI;AAC7D,iBAAe,IAAI;AAAc,WAAS,IAAI;AAAM,UAAQ;AAC9D;AACA,CALC,aAKa;AAAS,SAAO,IAAI;AAAW,gBAAc,IAAI;AAAW;","names":[]}
package/dist/index.cjs CHANGED
@@ -488,6 +488,29 @@ function pinButton(opts) {
488
488
  return b;
489
489
  }
490
490
 
491
+ // src/exit.ts
492
+ function closeWithExit(element, done) {
493
+ element.classList.add("closing");
494
+ const seconds = parseFloat(getComputedStyle(element).animationDuration) || 0;
495
+ if (seconds === 0) {
496
+ done();
497
+ return;
498
+ }
499
+ let fired = false;
500
+ const finish = () => {
501
+ if (fired) return;
502
+ fired = true;
503
+ element.removeEventListener("animationend", onEnd);
504
+ clearTimeout(timer2);
505
+ done();
506
+ };
507
+ const onEnd = (e) => {
508
+ if (e.target === element) finish();
509
+ };
510
+ element.addEventListener("animationend", onEnd);
511
+ const timer2 = setTimeout(finish, seconds * 1e3 + 120);
512
+ }
513
+
491
514
  // src/confirm.ts
492
515
  function confirmDialog(opts) {
493
516
  return new Promise((resolve) => {
@@ -509,21 +532,9 @@ function confirmDialog(opts) {
509
532
  if (done) return;
510
533
  done = true;
511
534
  resolve(ok);
512
- dlg.classList.add("closing");
513
- const dur = parseFloat(getComputedStyle(dlg).animationDuration) || 0;
514
- const teardown = () => {
535
+ closeWithExit(dlg, () => {
515
536
  dlg.close();
516
537
  dlg.remove();
517
- };
518
- if (dur === 0) {
519
- teardown();
520
- return;
521
- }
522
- const timer2 = setTimeout(teardown, dur * 1e3 + 120);
523
- dlg.addEventListener("animationend", (e) => {
524
- if (e.target !== dlg) return;
525
- clearTimeout(timer2);
526
- teardown();
527
538
  });
528
539
  };
529
540
  cancel.addEventListener("click", () => finish(false));
@@ -616,8 +627,10 @@ function openAnchoredPanel(opts) {
616
627
  if (current?.panel === panel) current = null;
617
628
  document.removeEventListener("pointerdown", onDown, true);
618
629
  document.removeEventListener("keydown", onKey, true);
619
- panel.remove();
620
- opts.onClose?.();
630
+ closeWithExit(panel, () => {
631
+ opts.onClose?.();
632
+ panel.remove();
633
+ });
621
634
  };
622
635
  closeBtn.addEventListener("click", close);
623
636
  const onDown = (e) => {
@@ -1037,6 +1050,10 @@ function openComments(opts) {
1037
1050
  }
1038
1051
  const thread = open;
1039
1052
  if (thread) {
1053
+ const quote = opts.quoteFor?.(thread);
1054
+ if (quote !== void 0 && quote !== "") {
1055
+ body.append(el("blockquote", { className: "shell-cmt-quote", text: quote }));
1056
+ }
1040
1057
  const list = el("div", { className: `shell-cmt-list${thread.resolved === true ? " resolved" : ""}` });
1041
1058
  thread.messages.forEach((message, index) => {
1042
1059
  const head = el(
@@ -1388,6 +1405,99 @@ function createSaveController(opts) {
1388
1405
  };
1389
1406
  }
1390
1407
 
1408
+ // src/save-view.ts
1409
+ var WORDS = {
1410
+ saved: "Saved",
1411
+ saving: "Saving\u2026",
1412
+ unsaved: "Unsaved"
1413
+ };
1414
+ function saveIndicator(initial = "saved") {
1415
+ const node = el("span");
1416
+ const set = (status) => {
1417
+ node.className = `savestat ${status}`;
1418
+ node.textContent = WORDS[status];
1419
+ };
1420
+ set(initial);
1421
+ return { el: node, set };
1422
+ }
1423
+
1424
+ // src/stale.ts
1425
+ function staleBar(opts) {
1426
+ const label2 = opts.restartLabel ?? "Restart";
1427
+ return el(
1428
+ "div",
1429
+ { className: "stale-bar" },
1430
+ el("span", {
1431
+ className: "stale-bar-msg",
1432
+ text: `${opts.subject} changed in the editor. ${opts.advice ?? "Restart to play the new version."}`
1433
+ }),
1434
+ el("button", {
1435
+ className: "stale-bar-go",
1436
+ text: `${icon.restart} ${label2}`,
1437
+ onClick: () => opts.onRestart()
1438
+ })
1439
+ );
1440
+ }
1441
+
1442
+ // src/stepper.ts
1443
+ function renderStepperBar(host, opts) {
1444
+ const { items } = opts;
1445
+ const close = opts.onClose === void 0 ? null : el("button", {
1446
+ className: "stepbar-nav stepbar-close",
1447
+ text: icon.close,
1448
+ tip: opts.closeTip ?? "Close",
1449
+ onClick: opts.onClose
1450
+ });
1451
+ const actions = (opts.actions ?? []).filter((a) => a !== null && a !== void 0);
1452
+ if (items.length === 0) {
1453
+ if (opts.empty === void 0) {
1454
+ host.hidden = true;
1455
+ host.replaceChildren();
1456
+ return;
1457
+ }
1458
+ host.hidden = false;
1459
+ host.replaceChildren(
1460
+ el("span", { className: "stepbar-count muted", text: "0" }),
1461
+ el("span", { className: "stepbar-empty", text: opts.empty }),
1462
+ ...actions,
1463
+ ...close ? [close] : []
1464
+ );
1465
+ return;
1466
+ }
1467
+ host.hidden = false;
1468
+ const index = Math.min(Math.max(opts.at, 0), items.length - 1);
1469
+ const item = items[index];
1470
+ const step = (delta) => opts.onStep((index + delta + items.length) % items.length);
1471
+ const nav = items.length > 1 ? [
1472
+ el("button", {
1473
+ className: "stepbar-nav",
1474
+ text: icon.back,
1475
+ tip: opts.tips?.prev ?? "Previous",
1476
+ onClick: () => step(-1)
1477
+ }),
1478
+ el("span", { className: "stepbar-of", text: `${index + 1}/${items.length}` }),
1479
+ el("button", {
1480
+ className: "stepbar-nav",
1481
+ text: icon.forward,
1482
+ tip: opts.tips?.next ?? "Next",
1483
+ onClick: () => step(1)
1484
+ })
1485
+ ] : [];
1486
+ host.replaceChildren(
1487
+ el("span", { className: `stepbar-count ${opts.tone ?? "muted"}`, text: String(items.length) }),
1488
+ ...nav,
1489
+ el(
1490
+ "button",
1491
+ { className: "stepbar-cur", tip: opts.tips?.go ?? "Go to this", onClick: () => opts.onGo(index) },
1492
+ item.kind === void 0 ? null : el("span", { className: `stepbar-cat${item.kindClass ? ` ${item.kindClass}` : ""}`, text: item.kind }),
1493
+ item.where === void 0 ? null : el("span", { className: "stepbar-where", text: item.where }),
1494
+ el("span", { className: "stepbar-msg", text: item.text })
1495
+ ),
1496
+ ...actions,
1497
+ ...close ? [close] : []
1498
+ );
1499
+ }
1500
+
1391
1501
  exports.EDIT_MENU = EDIT_MENU;
1392
1502
  exports.JOB_PROGRESS = JOB_PROGRESS;
1393
1503
  exports.PALETTE = PALETTE;
@@ -1395,6 +1505,7 @@ exports.PALETTE_SIZE = PALETTE_SIZE;
1395
1505
  exports.PANE_MENU = PANE_MENU;
1396
1506
  exports.askIdentity = askIdentity;
1397
1507
  exports.closeAnchoredPanel = closeAnchoredPanel;
1508
+ exports.closeWithExit = closeWithExit;
1398
1509
  exports.colourFor = colourFor;
1399
1510
  exports.colourIndex = colourIndex;
1400
1511
  exports.confirmDialog = confirmDialog;
@@ -1425,7 +1536,10 @@ exports.openNotesEditor = openNotesEditor;
1425
1536
  exports.paintVcBadges = paintVcBadges;
1426
1537
  exports.pinButton = pinButton;
1427
1538
  exports.placeAnchored = placeAnchored;
1539
+ exports.renderStepperBar = renderStepperBar;
1540
+ exports.saveIndicator = saveIndicator;
1428
1541
  exports.showAbout = showAbout;
1542
+ exports.staleBar = staleBar;
1429
1543
  exports.tagChips = tagChips;
1430
1544
  exports.tipAt = tipAt;
1431
1545
  exports.tipBold = tipBold;