@wildwinter/app-shell 0.14.0 → 0.16.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.d.cts CHANGED
@@ -269,7 +269,7 @@ interface AnchoredPanelOptions {
269
269
  * inspector); `"below"` suits one hung off a button in a document's header
270
270
  * (Storyletter's topline). Either way it flips when the room is not there.
271
271
  */
272
- prefer?: "left" | "below";
272
+ prefer?: "left" | "below" | "centre";
273
273
  /** Selectors for chrome the panel must not cover - a problems bar, a review
274
274
  * bar - so those stay usable while it is open. */
275
275
  keepClear?: string[];
@@ -284,7 +284,44 @@ declare function openAnchoredPanel(opts: AnchoredPanelOptions): AnchoredPanel |
284
284
  * an element that no longer exists would hang in the air. */
285
285
  declare function closeAnchoredPanel(): void;
286
286
  /** Patterpad's placement, with the side as a parameter. */
287
- declare function placeAnchored(panel: HTMLElement, anchor: HTMLElement, width: number, prefer: "left" | "below", keepClear: string[]): void;
287
+ declare function placeAnchored(panel: HTMLElement, anchor: HTMLElement, width: number, prefer: "left" | "below" | "centre", keepClear: string[]): void;
288
+
289
+ /**
290
+ * Slugify a name into hyphen form: lower case, apostrophes dropped, runs of
291
+ * anything else to a single hyphen, no leading or trailing hyphen.
292
+ *
293
+ * May return "", when there was nothing usable in the input. Callers treat that
294
+ * as "no pinned address" rather than as an address.
295
+ */
296
+ declare function gameIdify(text: string): string;
297
+ /** Is this a legal address? Lower case, digits and hyphens, starting and ending
298
+ * with a letter or a digit. Note "" is NOT legal: an empty pinned address means
299
+ * "derive one", which is a different thing from an address. */
300
+ declare function isValidGameId(gameId: string): boolean;
301
+
302
+ interface GameIdEditorOptions {
303
+ anchor: HTMLElement;
304
+ /** The pinned address, or "" when it is being derived. */
305
+ value: string;
306
+ /** What it derives to, shown as the placeholder. */
307
+ derived: string;
308
+ /** Persist. "" means "stop pinning, go back to deriving". */
309
+ onCommit: (gameId: string) => void;
310
+ /** The panel's title. Both apps say "Game ID"; a third may not. */
311
+ title?: string;
312
+ /** Override the rules. Defaults are the shell's, which is what a new app
313
+ * should use; an app whose domain layer owns them can pass its own. */
314
+ slugify?: (text: string) => string;
315
+ validate?: (gameId: string) => boolean;
316
+ /** Where the panel goes, for hosts that anchor it somewhere tight. */
317
+ prefer?: "left" | "below" | "centre";
318
+ }
319
+ /**
320
+ * Open it. Returns false when that click TOGGLED the panel shut, which is the
321
+ * anchored panel's contract and worth passing on: a caller that repaints on open
322
+ * needs to know it did not.
323
+ */
324
+ declare function openGameIdEditor(opts: GameIdEditorOptions): boolean;
288
325
 
289
326
  /** One shard's state as the renderer sees it. The app's own DTO will be this
290
327
  * shape (it crosses its own bridge); this is what the view needs. */
@@ -365,6 +402,11 @@ interface CommentMessage {
365
402
  author: string;
366
403
  ts: string;
367
404
  body: string;
405
+ /** A TOMBSTONE: the words are gone, the turn in the conversation is not.
406
+ * Deleting a reply out of a thread would renumber the argument around it, so
407
+ * what is left says who spoke and when and that they withdrew it. The body is
408
+ * emptied on the way out, so "deleted" means deleted from the file too. */
409
+ deleted?: boolean;
368
410
  }
369
411
  /** A thread, anchored to the id of whatever it is about. */
370
412
  interface Comment {
@@ -385,6 +427,22 @@ interface CommentsOptions {
385
427
  newThreadId: () => string;
386
428
  post: (threadId: string, body: string) => void;
387
429
  setResolved: (threadId: string, resolved: boolean) => void;
430
+ /**
431
+ * Withdraw one message. Absent = the host does not offer deletion.
432
+ *
433
+ * By INDEX, because a message has no id of its own and giving every message
434
+ * one would be a format change to every stored thread for the sake of one
435
+ * verb. The index is read off the list the reader is looking at, and these
436
+ * files are edited by one person at a time with a VCS underneath.
437
+ *
438
+ * The host decides what deleting MEANS: the agreed rule is that the message
439
+ * becomes a tombstone, and the whole thread goes when nothing readable would
440
+ * be left (which covers the common case of withdrawing a comment nobody had
441
+ * replied to).
442
+ */
443
+ deleteMessage?: (threadId: string, index: number) => void;
444
+ /** Where the panel sits: "centre" for a point on a canvas (see anchored.ts). */
445
+ prefer?: "left" | "below" | "centre";
388
446
  }
389
447
  declare function openComments(opts: CommentsOptions): void;
390
448
 
@@ -503,4 +561,4 @@ interface SaveController {
503
561
  }
504
562
  declare function createSaveController(opts: SaveControllerOptions): SaveController;
505
563
 
506
- export { type AboutOptions, type AnchoredPanel, type AnchoredPanelOptions, type Child, type Comment, type CommentMessage, type CommentsOptions, type ConfirmOptions, type DocLine, type DupGuard, type ElProps, type IconName, type IconSvgName, type Identity, type IdentityOptions, type JobProgressOptions, type JobProgressView, type Notes, type NotesEditorOptions, PALETTE, PALETTE_SIZE, type PaneShell, type PaneShellOptions, type PaneShellState, type PaneSide, type PaneSideConfig, type PinButtonOptions, type SaveController, type SaveControllerOptions, type SaveStatus, type SettingsDialog, type SettingsDialogOptions, type SettingsSection, type SettingsSectionHandle, type ShardVc, type TipRect, type TooltipOptions, type VcMap, askIdentity, closeAnchoredPanel, colourFor, colourIndex, confirmDialog, createSaveController, dupGuard, el, expandableRow, focusNewRow, foldVc, hideTip, icon, iconBtn, iconSvg, initTooltips, labelled, lockControls, lockNotice, mountJobProgress, mountPaneShell, mountSettingsDialog, moveItem, openAnchoredPanel, openComments, openNotesEditor, paintVcBadges, pinButton, placeAnchored, showAbout, tagChips, tipAt, tipBold, vcBadgeFor };
564
+ export { type AboutOptions, type AnchoredPanel, type AnchoredPanelOptions, type Child, type Comment, type CommentMessage, type CommentsOptions, type ConfirmOptions, type DocLine, type DupGuard, type ElProps, type GameIdEditorOptions, type IconName, type IconSvgName, type Identity, type IdentityOptions, type JobProgressOptions, type JobProgressView, type Notes, type NotesEditorOptions, PALETTE, PALETTE_SIZE, type PaneShell, type PaneShellOptions, type PaneShellState, type PaneSide, type PaneSideConfig, type PinButtonOptions, type SaveController, type SaveControllerOptions, type SaveStatus, type SettingsDialog, type SettingsDialogOptions, type SettingsSection, type SettingsSectionHandle, type ShardVc, type TipRect, type TooltipOptions, type VcMap, askIdentity, closeAnchoredPanel, colourFor, colourIndex, confirmDialog, createSaveController, dupGuard, el, expandableRow, focusNewRow, foldVc, gameIdify, hideTip, icon, iconBtn, iconSvg, initTooltips, isValidGameId, labelled, lockControls, lockNotice, mountJobProgress, mountPaneShell, mountSettingsDialog, moveItem, openAnchoredPanel, openComments, openGameIdEditor, openNotesEditor, paintVcBadges, pinButton, placeAnchored, showAbout, tagChips, tipAt, tipBold, vcBadgeFor };
package/dist/index.d.ts CHANGED
@@ -269,7 +269,7 @@ interface AnchoredPanelOptions {
269
269
  * inspector); `"below"` suits one hung off a button in a document's header
270
270
  * (Storyletter's topline). Either way it flips when the room is not there.
271
271
  */
272
- prefer?: "left" | "below";
272
+ prefer?: "left" | "below" | "centre";
273
273
  /** Selectors for chrome the panel must not cover - a problems bar, a review
274
274
  * bar - so those stay usable while it is open. */
275
275
  keepClear?: string[];
@@ -284,7 +284,44 @@ declare function openAnchoredPanel(opts: AnchoredPanelOptions): AnchoredPanel |
284
284
  * an element that no longer exists would hang in the air. */
285
285
  declare function closeAnchoredPanel(): void;
286
286
  /** Patterpad's placement, with the side as a parameter. */
287
- declare function placeAnchored(panel: HTMLElement, anchor: HTMLElement, width: number, prefer: "left" | "below", keepClear: string[]): void;
287
+ declare function placeAnchored(panel: HTMLElement, anchor: HTMLElement, width: number, prefer: "left" | "below" | "centre", keepClear: string[]): void;
288
+
289
+ /**
290
+ * Slugify a name into hyphen form: lower case, apostrophes dropped, runs of
291
+ * anything else to a single hyphen, no leading or trailing hyphen.
292
+ *
293
+ * May return "", when there was nothing usable in the input. Callers treat that
294
+ * as "no pinned address" rather than as an address.
295
+ */
296
+ declare function gameIdify(text: string): string;
297
+ /** Is this a legal address? Lower case, digits and hyphens, starting and ending
298
+ * with a letter or a digit. Note "" is NOT legal: an empty pinned address means
299
+ * "derive one", which is a different thing from an address. */
300
+ declare function isValidGameId(gameId: string): boolean;
301
+
302
+ interface GameIdEditorOptions {
303
+ anchor: HTMLElement;
304
+ /** The pinned address, or "" when it is being derived. */
305
+ value: string;
306
+ /** What it derives to, shown as the placeholder. */
307
+ derived: string;
308
+ /** Persist. "" means "stop pinning, go back to deriving". */
309
+ onCommit: (gameId: string) => void;
310
+ /** The panel's title. Both apps say "Game ID"; a third may not. */
311
+ title?: string;
312
+ /** Override the rules. Defaults are the shell's, which is what a new app
313
+ * should use; an app whose domain layer owns them can pass its own. */
314
+ slugify?: (text: string) => string;
315
+ validate?: (gameId: string) => boolean;
316
+ /** Where the panel goes, for hosts that anchor it somewhere tight. */
317
+ prefer?: "left" | "below" | "centre";
318
+ }
319
+ /**
320
+ * Open it. Returns false when that click TOGGLED the panel shut, which is the
321
+ * anchored panel's contract and worth passing on: a caller that repaints on open
322
+ * needs to know it did not.
323
+ */
324
+ declare function openGameIdEditor(opts: GameIdEditorOptions): boolean;
288
325
 
289
326
  /** One shard's state as the renderer sees it. The app's own DTO will be this
290
327
  * shape (it crosses its own bridge); this is what the view needs. */
@@ -365,6 +402,11 @@ interface CommentMessage {
365
402
  author: string;
366
403
  ts: string;
367
404
  body: string;
405
+ /** A TOMBSTONE: the words are gone, the turn in the conversation is not.
406
+ * Deleting a reply out of a thread would renumber the argument around it, so
407
+ * what is left says who spoke and when and that they withdrew it. The body is
408
+ * emptied on the way out, so "deleted" means deleted from the file too. */
409
+ deleted?: boolean;
368
410
  }
369
411
  /** A thread, anchored to the id of whatever it is about. */
370
412
  interface Comment {
@@ -385,6 +427,22 @@ interface CommentsOptions {
385
427
  newThreadId: () => string;
386
428
  post: (threadId: string, body: string) => void;
387
429
  setResolved: (threadId: string, resolved: boolean) => void;
430
+ /**
431
+ * Withdraw one message. Absent = the host does not offer deletion.
432
+ *
433
+ * By INDEX, because a message has no id of its own and giving every message
434
+ * one would be a format change to every stored thread for the sake of one
435
+ * verb. The index is read off the list the reader is looking at, and these
436
+ * files are edited by one person at a time with a VCS underneath.
437
+ *
438
+ * The host decides what deleting MEANS: the agreed rule is that the message
439
+ * becomes a tombstone, and the whole thread goes when nothing readable would
440
+ * be left (which covers the common case of withdrawing a comment nobody had
441
+ * replied to).
442
+ */
443
+ deleteMessage?: (threadId: string, index: number) => void;
444
+ /** Where the panel sits: "centre" for a point on a canvas (see anchored.ts). */
445
+ prefer?: "left" | "below" | "centre";
388
446
  }
389
447
  declare function openComments(opts: CommentsOptions): void;
390
448
 
@@ -503,4 +561,4 @@ interface SaveController {
503
561
  }
504
562
  declare function createSaveController(opts: SaveControllerOptions): SaveController;
505
563
 
506
- export { type AboutOptions, type AnchoredPanel, type AnchoredPanelOptions, type Child, type Comment, type CommentMessage, type CommentsOptions, type ConfirmOptions, type DocLine, type DupGuard, type ElProps, type IconName, type IconSvgName, type Identity, type IdentityOptions, type JobProgressOptions, type JobProgressView, type Notes, type NotesEditorOptions, PALETTE, PALETTE_SIZE, type PaneShell, type PaneShellOptions, type PaneShellState, type PaneSide, type PaneSideConfig, type PinButtonOptions, type SaveController, type SaveControllerOptions, type SaveStatus, type SettingsDialog, type SettingsDialogOptions, type SettingsSection, type SettingsSectionHandle, type ShardVc, type TipRect, type TooltipOptions, type VcMap, askIdentity, closeAnchoredPanel, colourFor, colourIndex, confirmDialog, createSaveController, dupGuard, el, expandableRow, focusNewRow, foldVc, hideTip, icon, iconBtn, iconSvg, initTooltips, labelled, lockControls, lockNotice, mountJobProgress, mountPaneShell, mountSettingsDialog, moveItem, openAnchoredPanel, openComments, openNotesEditor, paintVcBadges, pinButton, placeAnchored, showAbout, tagChips, tipAt, tipBold, vcBadgeFor };
564
+ export { type AboutOptions, type AnchoredPanel, type AnchoredPanelOptions, type Child, type Comment, type CommentMessage, type CommentsOptions, type ConfirmOptions, type DocLine, type DupGuard, type ElProps, type GameIdEditorOptions, type IconName, type IconSvgName, type Identity, type IdentityOptions, type JobProgressOptions, type JobProgressView, type Notes, type NotesEditorOptions, PALETTE, PALETTE_SIZE, type PaneShell, type PaneShellOptions, type PaneShellState, type PaneSide, type PaneSideConfig, type PinButtonOptions, type SaveController, type SaveControllerOptions, type SaveStatus, type SettingsDialog, type SettingsDialogOptions, type SettingsSection, type SettingsSectionHandle, type ShardVc, type TipRect, type TooltipOptions, type VcMap, askIdentity, closeAnchoredPanel, colourFor, colourIndex, confirmDialog, createSaveController, dupGuard, el, expandableRow, focusNewRow, foldVc, gameIdify, hideTip, icon, iconBtn, iconSvg, initTooltips, isValidGameId, labelled, lockControls, lockNotice, mountJobProgress, mountPaneShell, mountSettingsDialog, moveItem, openAnchoredPanel, openComments, openGameIdEditor, openNotesEditor, paintVcBadges, pinButton, placeAnchored, showAbout, tagChips, tipAt, tipBold, vcBadgeFor };
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  export { JOB_PROGRESS } from './chunk-MDGOIT3O.js';
2
- export { EDIT_MENU, PANE_MENU } from './chunk-R6RO5TXB.js';
2
+ export { EDIT_MENU, PANE_MENU } from './chunk-YLINCOQD.js';
3
3
 
4
4
  // src/dom.ts
5
5
  function el(tag, classNameOrProps, ...children) {
@@ -628,9 +628,14 @@ function placeAnchored(panel, anchor, width, prefer, keepClear) {
628
628
  const CAP = window.innerHeight * 0.7;
629
629
  const MIN_H = 140;
630
630
  panel.style.width = `${width}px`;
631
- const leftOf = a.left - width - GAP2;
632
- const alignedRight = Math.min(a.right - width, window.innerWidth - width - 8);
633
- panel.style.left = `${Math.round(prefer === "left" && leftOf >= 8 ? leftOf : Math.max(8, prefer === "left" ? a.left : alignedRight))}px`;
631
+ if (prefer === "centre") {
632
+ const centred = a.left + a.width / 2 - width / 2;
633
+ panel.style.left = `${Math.round(Math.min(Math.max(8, centred), window.innerWidth - width - 8))}px`;
634
+ } else {
635
+ const leftOf = a.left - width - GAP2;
636
+ const alignedRight = Math.min(a.right - width, window.innerWidth - width - 8);
637
+ panel.style.left = `${Math.round(prefer === "left" && leftOf >= 8 ? leftOf : Math.max(8, prefer === "left" ? a.left : alignedRight))}px`;
638
+ }
634
639
  let bottomLimit = window.innerHeight - 8;
635
640
  for (const selector of keepClear) {
636
641
  const bar = document.querySelector(selector);
@@ -638,6 +643,16 @@ function placeAnchored(panel, anchor, width, prefer, keepClear) {
638
643
  const r = bar.getBoundingClientRect();
639
644
  if (r.height > 0) bottomLimit = Math.min(bottomLimit, r.top - 6);
640
645
  }
646
+ if (prefer === "centre") {
647
+ const height = Math.min(CAP, Math.max(MIN_H, panel.offsetHeight));
648
+ const mid = a.top + a.height / 2 - height / 2;
649
+ if (mid >= 8 && mid + height <= bottomLimit) {
650
+ panel.style.top = `${Math.round(mid)}px`;
651
+ panel.style.bottom = "auto";
652
+ panel.style.maxHeight = `${Math.round(CAP)}px`;
653
+ return;
654
+ }
655
+ }
641
656
  const roomBelow = bottomLimit - (a.bottom + GAP2);
642
657
  const roomAbove = a.top - GAP2 - 8;
643
658
  if (roomAbove > roomBelow) {
@@ -651,6 +666,75 @@ function placeAnchored(panel, anchor, width, prefer, keepClear) {
651
666
  }
652
667
  }
653
668
 
669
+ // src/ids.ts
670
+ function gameIdify(text) {
671
+ return text.toLowerCase().replace(/['’]/g, "").replace(/[^a-z0-9-]+/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
672
+ }
673
+ function isValidGameId(gameId) {
674
+ return /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/.test(gameId);
675
+ }
676
+
677
+ // src/id-editor.ts
678
+ function openGameIdEditor(opts) {
679
+ const slugify = opts.slugify ?? gameIdify;
680
+ const valid = opts.validate ?? isValidGameId;
681
+ const panel = openAnchoredPanel({
682
+ anchor: opts.anchor,
683
+ className: "shell-id-editor",
684
+ title: opts.title ?? "Game ID",
685
+ width: 260,
686
+ ...opts.prefer !== void 0 ? { prefer: opts.prefer } : {}
687
+ });
688
+ if (!panel) return false;
689
+ const input = el("input", "shell-id-input");
690
+ input.type = "text";
691
+ input.value = opts.value;
692
+ input.placeholder = opts.derived || "<game-id>";
693
+ input.spellcheck = false;
694
+ const hint = el("div", "shell-id-hint", "Lower case, digits, hyphens. Empty = auto from the name.");
695
+ const actions = el("div", "shell-id-actions");
696
+ const reset = el("button", "shell-id-reset", "Reset to auto");
697
+ const set = el("button", "shell-id-set", "Set");
698
+ reset.type = "button";
699
+ set.type = "button";
700
+ actions.append(reset, set);
701
+ panel.body.append(input, hint, actions);
702
+ const legal = () => {
703
+ const v = input.value.trim();
704
+ return v === "" || valid(v);
705
+ };
706
+ const sync = () => {
707
+ set.disabled = !legal();
708
+ hint.classList.toggle("bad", !legal());
709
+ };
710
+ const commit = () => {
711
+ if (!legal()) return;
712
+ opts.onCommit(input.value.trim());
713
+ panel.close();
714
+ };
715
+ input.addEventListener("input", sync);
716
+ input.addEventListener("keydown", (e) => {
717
+ e.stopPropagation();
718
+ if (e.key === "Enter") {
719
+ e.preventDefault();
720
+ commit();
721
+ }
722
+ if (e.key === "Tab" && input.value.trim() !== "") {
723
+ e.preventDefault();
724
+ input.value = slugify(input.value);
725
+ sync();
726
+ }
727
+ });
728
+ set.addEventListener("click", commit);
729
+ reset.addEventListener("click", () => {
730
+ opts.onCommit("");
731
+ panel.close();
732
+ });
733
+ sync();
734
+ setTimeout(() => input.focus(), 0);
735
+ return true;
736
+ }
737
+
654
738
  // src/vc-view.ts
655
739
  function foldVc(shards, keys) {
656
740
  if (!keys) return void 0;
@@ -884,7 +968,7 @@ var stamp = (ts) => {
884
968
  return when.toLocaleString(void 0, { dateStyle: "medium", timeStyle: "short" });
885
969
  };
886
970
  var gist = (thread) => {
887
- const first = thread.messages[0]?.body ?? "";
971
+ const first = thread.messages.find((m) => m.deleted !== true)?.body ?? "";
888
972
  return first.length > 60 ? `${first.slice(0, 57)}\u2026` : first;
889
973
  };
890
974
  function openComments(opts) {
@@ -897,7 +981,7 @@ function openComments(opts) {
897
981
  // Below its button (the topline), unlike Patterpad's inspector rows which
898
982
  // want it to the left; and never over the problems bar, which an author may
899
983
  // be stepping through while reading the thread.
900
- prefer: "below",
984
+ prefer: opts.prefer ?? "below",
901
985
  keepClear: [".problembar"]
902
986
  });
903
987
  if (!panel) return;
@@ -933,19 +1017,26 @@ function openComments(opts) {
933
1017
  const thread = open;
934
1018
  if (thread) {
935
1019
  const list = el("div", { className: `shell-cmt-list${thread.resolved === true ? " resolved" : ""}` });
936
- for (const message of thread.messages) {
1020
+ thread.messages.forEach((message, index) => {
1021
+ const head = el(
1022
+ "div",
1023
+ { className: "shell-cmt-msg-head" },
1024
+ el("span", { className: "shell-cmt-author", text: message.author === "" ? "Someone" : message.author }),
1025
+ el("span", { className: "shell-cmt-ts", text: stamp(message.ts) })
1026
+ );
1027
+ if (opts.deleteMessage && message.deleted !== true) {
1028
+ const bin = el("button", { className: "shell-cmt-del", text: "\u2715" });
1029
+ bin.title = "Delete this comment";
1030
+ bin.addEventListener("click", () => opts.deleteMessage?.(thread.id, index));
1031
+ head.append(bin);
1032
+ }
937
1033
  list.append(el(
938
1034
  "div",
939
- { className: "shell-cmt-msg" },
940
- el(
941
- "div",
942
- { className: "shell-cmt-msg-head" },
943
- el("span", { className: "shell-cmt-author", text: message.author === "" ? "Someone" : message.author }),
944
- el("span", { className: "shell-cmt-ts", text: stamp(message.ts) })
945
- ),
946
- el("div", { className: "shell-cmt-body", text: message.body })
1035
+ { className: `shell-cmt-msg${message.deleted === true ? " gone" : ""}` },
1036
+ head,
1037
+ el("div", { className: "shell-cmt-body", text: message.deleted === true ? "Comment deleted" : message.body })
947
1038
  ));
948
- }
1039
+ });
949
1040
  body.append(list);
950
1041
  }
951
1042
  const input = el("textarea", { className: "shell-cmt-input" });
@@ -1273,6 +1364,6 @@ function createSaveController(opts) {
1273
1364
  };
1274
1365
  }
1275
1366
 
1276
- export { PALETTE, PALETTE_SIZE, askIdentity, closeAnchoredPanel, colourFor, colourIndex, confirmDialog, createSaveController, dupGuard, el, expandableRow, focusNewRow, foldVc, hideTip, icon, iconBtn, iconSvg, initTooltips, labelled, lockControls, lockNotice, mountJobProgress, mountPaneShell, mountSettingsDialog, moveItem, openAnchoredPanel, openComments, openNotesEditor, paintVcBadges, pinButton, placeAnchored, showAbout, tagChips, tipAt, tipBold, vcBadgeFor };
1367
+ export { PALETTE, PALETTE_SIZE, askIdentity, closeAnchoredPanel, colourFor, colourIndex, confirmDialog, createSaveController, dupGuard, el, expandableRow, focusNewRow, foldVc, gameIdify, hideTip, icon, iconBtn, iconSvg, initTooltips, isValidGameId, labelled, lockControls, lockNotice, mountJobProgress, mountPaneShell, mountSettingsDialog, moveItem, openAnchoredPanel, openComments, openGameIdEditor, openNotesEditor, paintVcBadges, pinButton, placeAnchored, showAbout, tagChips, tipAt, tipBold, vcBadgeFor };
1277
1368
  //# sourceMappingURL=index.js.map
1278
1369
  //# sourceMappingURL=index.js.map