@wildwinter/app-shell 0.27.0 → 0.28.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.cjs +151 -135
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +12 -1
- package/dist/index.d.ts +12 -1
- package/dist/index.js +151 -135
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -568,7 +568,18 @@ interface TipRect {
|
|
|
568
568
|
declare function tipAt(key: string, rect: TipRect, text: string): void;
|
|
569
569
|
/** Take any tip down: the pointer has left, or a gesture has started. */
|
|
570
570
|
declare function hideTip(): void;
|
|
571
|
-
/**
|
|
571
|
+
/**
|
|
572
|
+
* Wire the one delegated controller. Mounting happens once; OPTIONS do not.
|
|
573
|
+
*
|
|
574
|
+
* An explicit call is always authoritative about its options, even after something else has already
|
|
575
|
+
* mounted the listener. That ordering is not hypothetical, and Storyletter caught it before it shipped:
|
|
576
|
+
* once components self-mount (see `ensureTooltipHost`), a host's own
|
|
577
|
+
* `initTooltips({ suppressed })` could land second and be dropped by an early return, silently taking
|
|
578
|
+
* a rule like "no tooltips in Writing View" with it. Nothing would fail and nothing would show.
|
|
579
|
+
*
|
|
580
|
+
* Components therefore call this with NO arguments: they mount the renderer without claiming the
|
|
581
|
+
* options, and the host keeps the last word on behaviour.
|
|
582
|
+
*/
|
|
572
583
|
declare function initTooltips(opts?: TooltipOptions): void;
|
|
573
584
|
|
|
574
585
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -568,7 +568,18 @@ interface TipRect {
|
|
|
568
568
|
declare function tipAt(key: string, rect: TipRect, text: string): void;
|
|
569
569
|
/** Take any tip down: the pointer has left, or a gesture has started. */
|
|
570
570
|
declare function hideTip(): void;
|
|
571
|
-
/**
|
|
571
|
+
/**
|
|
572
|
+
* Wire the one delegated controller. Mounting happens once; OPTIONS do not.
|
|
573
|
+
*
|
|
574
|
+
* An explicit call is always authoritative about its options, even after something else has already
|
|
575
|
+
* mounted the listener. That ordering is not hypothetical, and Storyletter caught it before it shipped:
|
|
576
|
+
* once components self-mount (see `ensureTooltipHost`), a host's own
|
|
577
|
+
* `initTooltips({ suppressed })` could land second and be dropped by an early return, silently taking
|
|
578
|
+
* a rule like "no tooltips in Writing View" with it. Nothing would fail and nothing would show.
|
|
579
|
+
*
|
|
580
|
+
* Components therefore call this with NO arguments: they mount the renderer without claiming the
|
|
581
|
+
* options, and the host keeps the last word on behaviour.
|
|
582
|
+
*/
|
|
572
583
|
declare function initTooltips(opts?: TooltipOptions): void;
|
|
573
584
|
|
|
574
585
|
/**
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,155 @@
|
|
|
1
1
|
export { JOB_PROGRESS } from './chunk-MDGOIT3O.js';
|
|
2
2
|
export { EDIT_MENU, PANE_MENU } from './chunk-YLINCOQD.js';
|
|
3
3
|
|
|
4
|
+
// src/tooltip.ts
|
|
5
|
+
var B_OPEN = "";
|
|
6
|
+
var B_CLOSE = "";
|
|
7
|
+
function tipBold(s) {
|
|
8
|
+
return `${B_OPEN}${s}${B_CLOSE}`;
|
|
9
|
+
}
|
|
10
|
+
function renderTip(host, text) {
|
|
11
|
+
host.textContent = "";
|
|
12
|
+
const re = /\u0002([^\u0003]*)\u0003/g;
|
|
13
|
+
let last = 0;
|
|
14
|
+
let m;
|
|
15
|
+
while ((m = re.exec(text)) !== null) {
|
|
16
|
+
if (m.index > last) host.append(text.slice(last, m.index));
|
|
17
|
+
const strong = document.createElement("strong");
|
|
18
|
+
strong.textContent = m[1] ?? "";
|
|
19
|
+
host.append(strong);
|
|
20
|
+
last = m.index + m[0].length;
|
|
21
|
+
}
|
|
22
|
+
if (last < text.length) host.append(text.slice(last));
|
|
23
|
+
}
|
|
24
|
+
var inited = false;
|
|
25
|
+
var checked = false;
|
|
26
|
+
var suppressed;
|
|
27
|
+
var SHOW_DELAY = 350;
|
|
28
|
+
var EDGE = 6;
|
|
29
|
+
var GAP = 7;
|
|
30
|
+
var tip = null;
|
|
31
|
+
var timer = 0;
|
|
32
|
+
var active = null;
|
|
33
|
+
var activeKey = null;
|
|
34
|
+
function ensureEl() {
|
|
35
|
+
if (tip) return tip;
|
|
36
|
+
const el2 = document.createElement("div");
|
|
37
|
+
el2.className = "tooltip";
|
|
38
|
+
el2.setAttribute("role", "tooltip");
|
|
39
|
+
el2.hidden = true;
|
|
40
|
+
document.body.appendChild(el2);
|
|
41
|
+
tip = el2;
|
|
42
|
+
return el2;
|
|
43
|
+
}
|
|
44
|
+
var rectOf = (anchor) => anchor instanceof HTMLElement ? anchor.getBoundingClientRect() : anchor;
|
|
45
|
+
function place(anchor) {
|
|
46
|
+
const t = ensureEl();
|
|
47
|
+
const r = rectOf(anchor);
|
|
48
|
+
const tr = t.getBoundingClientRect();
|
|
49
|
+
let top = r.top - tr.height - GAP;
|
|
50
|
+
if (top < EDGE) top = r.top + r.height + GAP;
|
|
51
|
+
let left = r.left + r.width / 2 - tr.width / 2;
|
|
52
|
+
left = Math.max(EDGE, Math.min(left, window.innerWidth - tr.width - EDGE));
|
|
53
|
+
t.style.left = `${Math.round(left)}px`;
|
|
54
|
+
t.style.top = `${Math.round(top)}px`;
|
|
55
|
+
}
|
|
56
|
+
function show(anchor) {
|
|
57
|
+
const text = anchor.dataset["tip"];
|
|
58
|
+
if (!text) return;
|
|
59
|
+
paint(anchor, text);
|
|
60
|
+
}
|
|
61
|
+
function paint(anchor, text) {
|
|
62
|
+
if (suppressed?.()) return;
|
|
63
|
+
const t = ensureEl();
|
|
64
|
+
const host = (anchor instanceof HTMLElement ? anchor.closest("dialog[open]") : null) ?? document.body;
|
|
65
|
+
if (t.parentElement !== host) host.appendChild(t);
|
|
66
|
+
active = anchor instanceof HTMLElement ? anchor : null;
|
|
67
|
+
renderTip(t, text);
|
|
68
|
+
t.hidden = false;
|
|
69
|
+
t.classList.remove("show");
|
|
70
|
+
void t.offsetWidth;
|
|
71
|
+
place(anchor);
|
|
72
|
+
t.classList.add("show");
|
|
73
|
+
}
|
|
74
|
+
function hide() {
|
|
75
|
+
if (timer) {
|
|
76
|
+
window.clearTimeout(timer);
|
|
77
|
+
timer = 0;
|
|
78
|
+
}
|
|
79
|
+
active = null;
|
|
80
|
+
activeKey = null;
|
|
81
|
+
if (tip) {
|
|
82
|
+
tip.hidden = true;
|
|
83
|
+
tip.classList.remove("show");
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function schedule(anchor) {
|
|
87
|
+
if (anchor === active) return;
|
|
88
|
+
if (timer) window.clearTimeout(timer);
|
|
89
|
+
timer = window.setTimeout(() => show(anchor), SHOW_DELAY);
|
|
90
|
+
}
|
|
91
|
+
function tipAt(key, rect, text) {
|
|
92
|
+
if (key === activeKey) {
|
|
93
|
+
if (tip && !tip.hidden) {
|
|
94
|
+
renderTip(tip, text);
|
|
95
|
+
place(rect);
|
|
96
|
+
}
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
hide();
|
|
100
|
+
activeKey = key;
|
|
101
|
+
timer = window.setTimeout(() => {
|
|
102
|
+
timer = 0;
|
|
103
|
+
paint(rect, text);
|
|
104
|
+
}, SHOW_DELAY);
|
|
105
|
+
}
|
|
106
|
+
function hideTip() {
|
|
107
|
+
hide();
|
|
108
|
+
}
|
|
109
|
+
function ensureTooltipHost() {
|
|
110
|
+
initTooltips();
|
|
111
|
+
}
|
|
112
|
+
function checkTooltipHost() {
|
|
113
|
+
if (checked) return;
|
|
114
|
+
checked = true;
|
|
115
|
+
setTimeout(() => {
|
|
116
|
+
if (inited || typeof document === "undefined") return;
|
|
117
|
+
if (document.querySelector("[data-tip]")) {
|
|
118
|
+
console.warn("app-shell: something set `data-tip` but no tooltip host is mounted, so those rollovers will not draw. Call initTooltips() once per window.");
|
|
119
|
+
}
|
|
120
|
+
}, 0);
|
|
121
|
+
}
|
|
122
|
+
var tipAncestor = (n) => n instanceof Element ? n.closest("[data-tip]") : null;
|
|
123
|
+
function initTooltips(opts = {}) {
|
|
124
|
+
if (opts.suppressed !== void 0) suppressed = opts.suppressed;
|
|
125
|
+
if (inited) return;
|
|
126
|
+
inited = true;
|
|
127
|
+
document.addEventListener("pointerover", (e) => {
|
|
128
|
+
const el2 = tipAncestor(e.target);
|
|
129
|
+
if (el2) schedule(el2);
|
|
130
|
+
});
|
|
131
|
+
document.addEventListener("pointerout", (e) => {
|
|
132
|
+
const el2 = tipAncestor(e.target);
|
|
133
|
+
if (!el2) return;
|
|
134
|
+
const to = e.relatedTarget;
|
|
135
|
+
if (to instanceof Node && el2.contains(to)) return;
|
|
136
|
+
hide();
|
|
137
|
+
});
|
|
138
|
+
document.addEventListener("focusin", (e) => {
|
|
139
|
+
const el2 = tipAncestor(e.target);
|
|
140
|
+
if (el2) schedule(el2);
|
|
141
|
+
});
|
|
142
|
+
document.addEventListener("focusout", () => hide());
|
|
143
|
+
document.addEventListener("pointerdown", () => hide(), true);
|
|
144
|
+
document.addEventListener("scroll", () => {
|
|
145
|
+
if (active) hide();
|
|
146
|
+
}, true);
|
|
147
|
+
document.addEventListener("keydown", (e) => {
|
|
148
|
+
if (e.key === "Escape") hide();
|
|
149
|
+
});
|
|
150
|
+
window.addEventListener("blur", () => hide());
|
|
151
|
+
}
|
|
152
|
+
|
|
4
153
|
// src/dom.ts
|
|
5
154
|
function el(tag, classNameOrProps, ...children) {
|
|
6
155
|
const node = document.createElement(tag);
|
|
@@ -11,12 +160,14 @@ function el(tag, classNameOrProps, ...children) {
|
|
|
11
160
|
if (props.tip !== void 0) {
|
|
12
161
|
node.dataset["tip"] = props.tip;
|
|
13
162
|
node.setAttribute("aria-label", props.tip);
|
|
163
|
+
ensureTooltipHost();
|
|
14
164
|
}
|
|
15
165
|
if (props.onClick) node.addEventListener("click", props.onClick);
|
|
16
166
|
for (const child of children) {
|
|
17
167
|
if (child === null || child === void 0) continue;
|
|
18
168
|
node.append(child);
|
|
19
169
|
}
|
|
170
|
+
checkTooltipHost();
|
|
20
171
|
return node;
|
|
21
172
|
}
|
|
22
173
|
function iconBtn(glyph, title, onClick, disabled = false, danger = false) {
|
|
@@ -1183,141 +1334,6 @@ function mountJobProgress(host, opts) {
|
|
|
1183
1334
|
return view;
|
|
1184
1335
|
}
|
|
1185
1336
|
|
|
1186
|
-
// src/tooltip.ts
|
|
1187
|
-
var B_OPEN = "";
|
|
1188
|
-
var B_CLOSE = "";
|
|
1189
|
-
function tipBold(s) {
|
|
1190
|
-
return `${B_OPEN}${s}${B_CLOSE}`;
|
|
1191
|
-
}
|
|
1192
|
-
function renderTip(host, text) {
|
|
1193
|
-
host.textContent = "";
|
|
1194
|
-
const re = /\u0002([^\u0003]*)\u0003/g;
|
|
1195
|
-
let last = 0;
|
|
1196
|
-
let m;
|
|
1197
|
-
while ((m = re.exec(text)) !== null) {
|
|
1198
|
-
if (m.index > last) host.append(text.slice(last, m.index));
|
|
1199
|
-
const strong = document.createElement("strong");
|
|
1200
|
-
strong.textContent = m[1] ?? "";
|
|
1201
|
-
host.append(strong);
|
|
1202
|
-
last = m.index + m[0].length;
|
|
1203
|
-
}
|
|
1204
|
-
if (last < text.length) host.append(text.slice(last));
|
|
1205
|
-
}
|
|
1206
|
-
var inited = false;
|
|
1207
|
-
var suppressed;
|
|
1208
|
-
var SHOW_DELAY = 350;
|
|
1209
|
-
var EDGE = 6;
|
|
1210
|
-
var GAP = 7;
|
|
1211
|
-
var tip = null;
|
|
1212
|
-
var timer = 0;
|
|
1213
|
-
var active = null;
|
|
1214
|
-
var activeKey = null;
|
|
1215
|
-
function ensureEl() {
|
|
1216
|
-
if (tip) return tip;
|
|
1217
|
-
const el2 = document.createElement("div");
|
|
1218
|
-
el2.className = "tooltip";
|
|
1219
|
-
el2.setAttribute("role", "tooltip");
|
|
1220
|
-
el2.hidden = true;
|
|
1221
|
-
document.body.appendChild(el2);
|
|
1222
|
-
tip = el2;
|
|
1223
|
-
return el2;
|
|
1224
|
-
}
|
|
1225
|
-
var rectOf = (anchor) => anchor instanceof HTMLElement ? anchor.getBoundingClientRect() : anchor;
|
|
1226
|
-
function place(anchor) {
|
|
1227
|
-
const t = ensureEl();
|
|
1228
|
-
const r = rectOf(anchor);
|
|
1229
|
-
const tr = t.getBoundingClientRect();
|
|
1230
|
-
let top = r.top - tr.height - GAP;
|
|
1231
|
-
if (top < EDGE) top = r.top + r.height + GAP;
|
|
1232
|
-
let left = r.left + r.width / 2 - tr.width / 2;
|
|
1233
|
-
left = Math.max(EDGE, Math.min(left, window.innerWidth - tr.width - EDGE));
|
|
1234
|
-
t.style.left = `${Math.round(left)}px`;
|
|
1235
|
-
t.style.top = `${Math.round(top)}px`;
|
|
1236
|
-
}
|
|
1237
|
-
function show(anchor) {
|
|
1238
|
-
const text = anchor.dataset["tip"];
|
|
1239
|
-
if (!text) return;
|
|
1240
|
-
paint(anchor, text);
|
|
1241
|
-
}
|
|
1242
|
-
function paint(anchor, text) {
|
|
1243
|
-
if (suppressed?.()) return;
|
|
1244
|
-
const t = ensureEl();
|
|
1245
|
-
const host = (anchor instanceof HTMLElement ? anchor.closest("dialog[open]") : null) ?? document.body;
|
|
1246
|
-
if (t.parentElement !== host) host.appendChild(t);
|
|
1247
|
-
active = anchor instanceof HTMLElement ? anchor : null;
|
|
1248
|
-
renderTip(t, text);
|
|
1249
|
-
t.hidden = false;
|
|
1250
|
-
t.classList.remove("show");
|
|
1251
|
-
void t.offsetWidth;
|
|
1252
|
-
place(anchor);
|
|
1253
|
-
t.classList.add("show");
|
|
1254
|
-
}
|
|
1255
|
-
function hide() {
|
|
1256
|
-
if (timer) {
|
|
1257
|
-
window.clearTimeout(timer);
|
|
1258
|
-
timer = 0;
|
|
1259
|
-
}
|
|
1260
|
-
active = null;
|
|
1261
|
-
activeKey = null;
|
|
1262
|
-
if (tip) {
|
|
1263
|
-
tip.hidden = true;
|
|
1264
|
-
tip.classList.remove("show");
|
|
1265
|
-
}
|
|
1266
|
-
}
|
|
1267
|
-
function schedule(anchor) {
|
|
1268
|
-
if (anchor === active) return;
|
|
1269
|
-
if (timer) window.clearTimeout(timer);
|
|
1270
|
-
timer = window.setTimeout(() => show(anchor), SHOW_DELAY);
|
|
1271
|
-
}
|
|
1272
|
-
function tipAt(key, rect, text) {
|
|
1273
|
-
if (key === activeKey) {
|
|
1274
|
-
if (tip && !tip.hidden) {
|
|
1275
|
-
renderTip(tip, text);
|
|
1276
|
-
place(rect);
|
|
1277
|
-
}
|
|
1278
|
-
return;
|
|
1279
|
-
}
|
|
1280
|
-
hide();
|
|
1281
|
-
activeKey = key;
|
|
1282
|
-
timer = window.setTimeout(() => {
|
|
1283
|
-
timer = 0;
|
|
1284
|
-
paint(rect, text);
|
|
1285
|
-
}, SHOW_DELAY);
|
|
1286
|
-
}
|
|
1287
|
-
function hideTip() {
|
|
1288
|
-
hide();
|
|
1289
|
-
}
|
|
1290
|
-
var tipAncestor = (n) => n instanceof Element ? n.closest("[data-tip]") : null;
|
|
1291
|
-
function initTooltips(opts = {}) {
|
|
1292
|
-
if (inited) return;
|
|
1293
|
-
inited = true;
|
|
1294
|
-
suppressed = opts.suppressed;
|
|
1295
|
-
document.addEventListener("pointerover", (e) => {
|
|
1296
|
-
const el2 = tipAncestor(e.target);
|
|
1297
|
-
if (el2) schedule(el2);
|
|
1298
|
-
});
|
|
1299
|
-
document.addEventListener("pointerout", (e) => {
|
|
1300
|
-
const el2 = tipAncestor(e.target);
|
|
1301
|
-
if (!el2) return;
|
|
1302
|
-
const to = e.relatedTarget;
|
|
1303
|
-
if (to instanceof Node && el2.contains(to)) return;
|
|
1304
|
-
hide();
|
|
1305
|
-
});
|
|
1306
|
-
document.addEventListener("focusin", (e) => {
|
|
1307
|
-
const el2 = tipAncestor(e.target);
|
|
1308
|
-
if (el2) schedule(el2);
|
|
1309
|
-
});
|
|
1310
|
-
document.addEventListener("focusout", () => hide());
|
|
1311
|
-
document.addEventListener("pointerdown", () => hide(), true);
|
|
1312
|
-
document.addEventListener("scroll", () => {
|
|
1313
|
-
if (active) hide();
|
|
1314
|
-
}, true);
|
|
1315
|
-
document.addEventListener("keydown", (e) => {
|
|
1316
|
-
if (e.key === "Escape") hide();
|
|
1317
|
-
});
|
|
1318
|
-
window.addEventListener("blur", () => hide());
|
|
1319
|
-
}
|
|
1320
|
-
|
|
1321
1337
|
// src/save.ts
|
|
1322
1338
|
function createSaveController(opts) {
|
|
1323
1339
|
const delayMs = opts.delayMs ?? 700;
|