@hozu/devtools 0.15.0 → 0.17.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/notes.d.ts +2 -0
- package/dist/notes.d.ts.map +1 -1
- package/dist/notes.js +1 -0
- package/dist/notes.js.map +1 -1
- package/dist/overlay/app.js +291 -34
- package/dist/overlay/app.js.map +1 -1
- package/dist/overlay/assets.d.ts +44 -0
- package/dist/overlay/assets.d.ts.map +1 -0
- package/dist/overlay/assets.js +294 -0
- package/dist/overlay/assets.js.map +1 -0
- package/dist/overlay/look.d.ts +2 -0
- package/dist/overlay/look.d.ts.map +1 -1
- package/dist/overlay/look.js +117 -32
- package/dist/overlay/look.js.map +1 -1
- package/dist/overlay/measure.d.ts +18 -0
- package/dist/overlay/measure.d.ts.map +1 -0
- package/dist/overlay/measure.js +33 -0
- package/dist/overlay/measure.js.map +1 -0
- package/dist/overlay/notes.d.ts +2 -0
- package/dist/overlay/notes.d.ts.map +1 -1
- package/dist/overlay/notes.js +16 -2
- package/dist/overlay/notes.js.map +1 -1
- package/dist/overlay/style.d.ts +2 -2
- package/dist/overlay/style.d.ts.map +1 -1
- package/dist/overlay/style.js +99 -0
- package/dist/overlay/style.js.map +1 -1
- package/dist/plain.d.ts.map +1 -1
- package/dist/plain.js +6 -5
- package/dist/plain.js.map +1 -1
- package/dist/prompt.d.ts.map +1 -1
- package/dist/prompt.js +17 -3
- package/dist/prompt.js.map +1 -1
- package/dist/theme.d.ts +3 -1
- package/dist/theme.d.ts.map +1 -1
- package/dist/theme.js +50 -3
- package/dist/theme.js.map +1 -1
- package/package.json +3 -2
package/dist/notes.d.ts
CHANGED
|
@@ -9,6 +9,8 @@ export interface AgentNote {
|
|
|
9
9
|
at: string | null;
|
|
10
10
|
/** A page path where the target is shown, when one is known. */
|
|
11
11
|
path: string | null;
|
|
12
|
+
/** Only the instances of the target whose text contains this (one row of a list), like browse's `in "<text>"`. */
|
|
13
|
+
within: string | null;
|
|
12
14
|
text: string;
|
|
13
15
|
created: string;
|
|
14
16
|
}
|
package/dist/notes.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"notes.d.ts","sourceRoot":"","sources":["../src/notes.ts"],"names":[],"mappings":"AAGA,kEAAkE;AAClE,MAAM,WAAW,SAAS;IACxB,CAAC,EAAE,MAAM,CAAA;IACT,mFAAmF;IACnF,EAAE,EAAE,MAAM,CAAA;IACV,uDAAuD;IACvD,KAAK,EAAE,MAAM,CAAA;IACb,6CAA6C;IAC7C,EAAE,EAAE,MAAM,GAAG,IAAI,CAAA;IACjB,gEAAgE;IAChE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IACnB,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;CAChB;AAOD,eAAO,MAAM,SAAS,qBAAqB,CAAA;AAoB3C,eAAO,MAAM,SAAS,SAAU,MAAM,KAAG,SAAS,EAAsB,CAAA;AAExE,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,EAAE,GAAG,GAAG,SAAS,CAAC,EAAE,GAAG,OAAa,GAAG,SAAS,CAKzG;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,SAAS,CAM7D;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAI/C;AAED,yGAAyG;AACzG,wBAAgB,aAAa,CAAC,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"notes.d.ts","sourceRoot":"","sources":["../src/notes.ts"],"names":[],"mappings":"AAGA,kEAAkE;AAClE,MAAM,WAAW,SAAS;IACxB,CAAC,EAAE,MAAM,CAAA;IACT,mFAAmF;IACnF,EAAE,EAAE,MAAM,CAAA;IACV,uDAAuD;IACvD,KAAK,EAAE,MAAM,CAAA;IACb,6CAA6C;IAC7C,EAAE,EAAE,MAAM,GAAG,IAAI,CAAA;IACjB,gEAAgE;IAChE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IACnB,kHAAkH;IAClH,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;CAChB;AAOD,eAAO,MAAM,SAAS,qBAAqB,CAAA;AAoB3C,eAAO,MAAM,SAAS,SAAU,MAAM,KAAG,SAAS,EAAsB,CAAA;AAExE,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,EAAE,GAAG,GAAG,SAAS,CAAC,EAAE,GAAG,OAAa,GAAG,SAAS,CAKzG;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,SAAS,CAM7D;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAI/C;AAED,yGAAyG;AACzG,wBAAgB,aAAa,CAAC,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAepE"}
|
package/dist/notes.js
CHANGED
|
@@ -49,6 +49,7 @@ export function replyMarkdown(note, reply) {
|
|
|
49
49
|
`- Note: ${note.text}`,
|
|
50
50
|
note.at ? `- Where: \`${note.at}:1\`` : `- Where: ${note.id}`,
|
|
51
51
|
...(note.path ? [`- Page: ${note.path}`] : []),
|
|
52
|
+
...(note.within ? [`- In: "${note.within}"`] : []),
|
|
52
53
|
`- Reply: ${reply}`,
|
|
53
54
|
'',
|
|
54
55
|
`The note stays on the page until \`hozu show --done ${note.n}\` removes it.`,
|
package/dist/notes.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"notes.js","sourceRoot":"","sources":["../src/notes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AACxF,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;
|
|
1
|
+
{"version":3,"file":"notes.js","sourceRoot":"","sources":["../src/notes.ts"],"names":[],"mappings":"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"}
|
package/dist/overlay/app.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { describeFor, friendlyName, questionFor } from '../plain.js';
|
|
2
2
|
import { joinRequests, labelOf, openRequestsLine, requestMarkdown, scopesFor, } from '../prompt.js';
|
|
3
3
|
import { held, hold, node, one, page, remove, save, saved, theme, tree } from './api.js';
|
|
4
|
+
import { assetsBoard } from './assets.js';
|
|
4
5
|
import { h, read, write } from './dom.js';
|
|
5
6
|
import { drawer } from './effects.js';
|
|
6
7
|
import { previewLabel, renderLayers } from './layers.js';
|
|
7
8
|
import { logo } from './logo.js';
|
|
8
9
|
import { lookSection, preview } from './look.js';
|
|
10
|
+
import { distances, sizeOf } from './measure.js';
|
|
9
11
|
import { agentNotes } from './notes.js';
|
|
10
12
|
import { css, outlineCss } from './style.js';
|
|
11
13
|
import { firstText, previewText, restoreText, textSection } from './text.js';
|
|
@@ -31,6 +33,9 @@ const state = {
|
|
|
31
33
|
};
|
|
32
34
|
const persist = () => write(key, state);
|
|
33
35
|
const host = document.createElement('hozu-devtools');
|
|
36
|
+
host.setAttribute('data-lenis-prevent', '');
|
|
37
|
+
for (const type of ['wheel', 'touchstart', 'touchmove', 'keydown'])
|
|
38
|
+
host.addEventListener(type, (event) => event.stopPropagation(), { passive: true });
|
|
34
39
|
const shadow = host.attachShadow({ mode: 'open' });
|
|
35
40
|
const root = h('div', { class: 'root' });
|
|
36
41
|
const hover = h('div', { class: 'box', hidden: true }, [h('div', { class: 'tag' })]);
|
|
@@ -59,7 +64,119 @@ const api = drawer({
|
|
|
59
64
|
},
|
|
60
65
|
closed: () => { },
|
|
61
66
|
});
|
|
62
|
-
|
|
67
|
+
const screenCookie = 'hozu-dev-preview';
|
|
68
|
+
const screenOf = () => {
|
|
69
|
+
const raw = new RegExp(`(?:^|;\\s*)${screenCookie}=([^;]+)`).exec(document.cookie)?.[1];
|
|
70
|
+
try {
|
|
71
|
+
return raw ? decodeURIComponent(raw) : null;
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
const setScreen = (value) => {
|
|
78
|
+
document.cookie = value
|
|
79
|
+
? `${screenCookie}=${encodeURIComponent(value)}; path=/; SameSite=Lax`
|
|
80
|
+
: `${screenCookie}=; path=/; max-age=0; SameSite=Lax`;
|
|
81
|
+
};
|
|
82
|
+
const showKey = 'hozu-devtools:assets-show';
|
|
83
|
+
function showInstances(c) {
|
|
84
|
+
const here = c.uses.flatMap((u) => [...doc.querySelectorAll(`[data-hz="${CSS.escape(u.node)}"]`)]);
|
|
85
|
+
if (!here.length) {
|
|
86
|
+
const page = c.uses.flatMap((u) => u.pages).find((p) => !p.params);
|
|
87
|
+
if (!page)
|
|
88
|
+
return;
|
|
89
|
+
try {
|
|
90
|
+
sessionStorage.setItem(showKey, c.id);
|
|
91
|
+
}
|
|
92
|
+
catch { }
|
|
93
|
+
win.location.href = page.path;
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
assets.close();
|
|
97
|
+
state.mode = 'select';
|
|
98
|
+
state.picks = [
|
|
99
|
+
...state.picks.filter(hasContent),
|
|
100
|
+
...here.map((el) => ({
|
|
101
|
+
id: el.getAttribute('data-hz') ?? '',
|
|
102
|
+
index: indexOf(el),
|
|
103
|
+
note: '',
|
|
104
|
+
scope: 'component',
|
|
105
|
+
visible: visibleOf(el),
|
|
106
|
+
})),
|
|
107
|
+
];
|
|
108
|
+
state.active = state.picks.length - 1;
|
|
109
|
+
state.panel = 'inspector';
|
|
110
|
+
persist();
|
|
111
|
+
renderDock();
|
|
112
|
+
drawPicks();
|
|
113
|
+
void renderPanel();
|
|
114
|
+
}
|
|
115
|
+
const assets = assetsBoard({
|
|
116
|
+
plain: state.audience === 'builder',
|
|
117
|
+
theme: () => theme(),
|
|
118
|
+
stylesheets: () => [...doc.querySelectorAll('link[rel="stylesheet"]')].map((l) => l.href).filter(Boolean),
|
|
119
|
+
show: (c) => showInstances(c),
|
|
120
|
+
change: (c, note) => {
|
|
121
|
+
const first = c.uses[0];
|
|
122
|
+
if (!first)
|
|
123
|
+
return;
|
|
124
|
+
state.picks.push({ id: first.node, index: 0, note, scope: 'component', visible: '' });
|
|
125
|
+
state.active = state.picks.length - 1;
|
|
126
|
+
persist();
|
|
127
|
+
assets.close();
|
|
128
|
+
renderDock();
|
|
129
|
+
open('changes', null, 'draft');
|
|
130
|
+
},
|
|
131
|
+
screen: (route, path, name) => {
|
|
132
|
+
setScreen(`${route}:${name}`);
|
|
133
|
+
if (win.location.pathname === path)
|
|
134
|
+
win.location.reload();
|
|
135
|
+
else
|
|
136
|
+
win.location.href = path;
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
async function screensSection() {
|
|
140
|
+
const res = await fetch(`/_hozu/dev/previews?path=${encodeURIComponent(win.location.pathname)}`, {
|
|
141
|
+
cache: 'no-store',
|
|
142
|
+
}).catch(() => null);
|
|
143
|
+
const out = res?.ok
|
|
144
|
+
? (await res.json())
|
|
145
|
+
: null;
|
|
146
|
+
if (!out?.route || !out.previews.length)
|
|
147
|
+
return null;
|
|
148
|
+
return h('div', { class: 'sec' }, [
|
|
149
|
+
h('div', { class: 'label' }, ['Previews (previews.ts)']),
|
|
150
|
+
...out.previews.map((p) => {
|
|
151
|
+
const value = `${out.route}:${p.name}`;
|
|
152
|
+
const on = out.current === value;
|
|
153
|
+
return h('div', { class: 'look' }, [
|
|
154
|
+
h('span', { class: 'what' }, [p.name]),
|
|
155
|
+
h('button', {
|
|
156
|
+
class: on ? 'link done' : 'link',
|
|
157
|
+
type: 'button',
|
|
158
|
+
onclick: () => {
|
|
159
|
+
setScreen(on ? null : value);
|
|
160
|
+
win.location.reload();
|
|
161
|
+
},
|
|
162
|
+
}, [on ? 'Exit' : 'Preview']),
|
|
163
|
+
]);
|
|
164
|
+
}),
|
|
165
|
+
]);
|
|
166
|
+
}
|
|
167
|
+
root.append(bench, panel, dock, api.el, assets.board);
|
|
168
|
+
try {
|
|
169
|
+
const pending = sessionStorage.getItem(showKey);
|
|
170
|
+
if (pending) {
|
|
171
|
+
sessionStorage.removeItem(showKey);
|
|
172
|
+
void assets.catalog().then((list) => {
|
|
173
|
+
const c = list?.find((x) => x.id === pending);
|
|
174
|
+
if (c)
|
|
175
|
+
setTimeout(() => showInstances(c), 300);
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
catch { }
|
|
63
180
|
document.documentElement.append(host);
|
|
64
181
|
const darkScheme = matchMedia('(prefers-color-scheme: dark)');
|
|
65
182
|
function applyTheme() {
|
|
@@ -72,6 +189,8 @@ let win = window;
|
|
|
72
189
|
let frameEl = null;
|
|
73
190
|
const frames = h('div');
|
|
74
191
|
let outline = null;
|
|
192
|
+
let measuring = false;
|
|
193
|
+
const measure = h('div', { class: 'measure', hidden: true });
|
|
75
194
|
const agent = agentNotes({
|
|
76
195
|
doc: () => doc,
|
|
77
196
|
win: () => win,
|
|
@@ -89,7 +208,7 @@ function mountOutline(target) {
|
|
|
89
208
|
outline?.remove();
|
|
90
209
|
const el = target.createElement('hozu-devtools-outline');
|
|
91
210
|
el.style.cssText = 'all:initial;position:fixed;inset:0;pointer-events:none;z-index:2147483646';
|
|
92
|
-
el.attachShadow({ mode: 'open' }).append(h('style', {}, [outlineCss]), frames, boxes, agent.layer, hover);
|
|
211
|
+
el.attachShadow({ mode: 'open' }).append(h('style', {}, [outlineCss]), frames, boxes, agent.layer, hover, measure);
|
|
93
212
|
target.documentElement.append(el);
|
|
94
213
|
outline = el;
|
|
95
214
|
}
|
|
@@ -131,6 +250,9 @@ function place(box, el) {
|
|
|
131
250
|
target.style.width = `${r.width + 4}px`;
|
|
132
251
|
target.style.height = `${r.height + 4}px`;
|
|
133
252
|
}
|
|
253
|
+
const size = box.querySelector(':scope > .size');
|
|
254
|
+
if (size)
|
|
255
|
+
size.textContent = sizeOf(r);
|
|
134
256
|
const tag = box.firstElementChild;
|
|
135
257
|
if (!tag?.classList.contains('tag'))
|
|
136
258
|
return;
|
|
@@ -175,6 +297,37 @@ async function showHover(el, force = false) {
|
|
|
175
297
|
if (hovered === el)
|
|
176
298
|
tag.replaceChildren(...tagText(n, el));
|
|
177
299
|
}
|
|
300
|
+
function drawMeasure() {
|
|
301
|
+
const active = state.picks[state.active];
|
|
302
|
+
const from = active ? elementOf(active) : hovered;
|
|
303
|
+
const to = active ? hovered : (hovered?.parentElement?.closest('[data-hz]') ?? null);
|
|
304
|
+
if (!measuring || state.mode !== 'select' || !from || !to || from === to) {
|
|
305
|
+
measure.hidden = true;
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
const a = from.getBoundingClientRect();
|
|
309
|
+
const b = to.getBoundingClientRect();
|
|
310
|
+
const px = (n) => `${n}px`;
|
|
311
|
+
const target = h('div', { class: 'm-target' });
|
|
312
|
+
Object.assign(target.style, { left: px(b.left), top: px(b.top), width: px(b.width), height: px(b.height) });
|
|
313
|
+
const parts = [target];
|
|
314
|
+
for (const l of distances(a, b)) {
|
|
315
|
+
const line = h('div', { class: `m-line ${l.axis}` });
|
|
316
|
+
const label = h('div', { class: 'm-label' }, [String(Math.round(l.to - l.from))]);
|
|
317
|
+
const mid = (l.from + l.to) / 2;
|
|
318
|
+
if (l.axis === 'h') {
|
|
319
|
+
Object.assign(line.style, { left: px(l.from), top: px(l.at), width: px(l.to - l.from) });
|
|
320
|
+
Object.assign(label.style, { left: px(mid), top: px(l.at) });
|
|
321
|
+
}
|
|
322
|
+
else {
|
|
323
|
+
Object.assign(line.style, { left: px(l.at), top: px(l.from), height: px(l.to - l.from) });
|
|
324
|
+
Object.assign(label.style, { left: px(l.at), top: px(mid) });
|
|
325
|
+
}
|
|
326
|
+
parts.push(line, label);
|
|
327
|
+
}
|
|
328
|
+
measure.replaceChildren(...parts);
|
|
329
|
+
measure.hidden = false;
|
|
330
|
+
}
|
|
178
331
|
function drawPicks() {
|
|
179
332
|
for (const p of state.picks) {
|
|
180
333
|
preview(elementOf(p), p.style ?? []);
|
|
@@ -185,6 +338,7 @@ function drawPicks() {
|
|
|
185
338
|
boxes.replaceChildren(...state.picks.map((p, i) => {
|
|
186
339
|
const box = framed(h('div', { class: 'box selected' }, [
|
|
187
340
|
state.picks.length > 1 ? h('div', { class: 'badge' }, [String(i + 1)]) : null,
|
|
341
|
+
h('div', { class: 'size' }),
|
|
188
342
|
]), true);
|
|
189
343
|
place(box, state.mode === 'select' ? elementOf(p) : null);
|
|
190
344
|
return box;
|
|
@@ -203,6 +357,7 @@ function frame() {
|
|
|
203
357
|
}
|
|
204
358
|
if (hovered && (state.mode === 'select' || forced))
|
|
205
359
|
place(hover, hovered);
|
|
360
|
+
drawMeasure();
|
|
206
361
|
agent.frame();
|
|
207
362
|
requestAnimationFrame(frame);
|
|
208
363
|
}
|
|
@@ -272,12 +427,13 @@ function listen(target) {
|
|
|
272
427
|
return;
|
|
273
428
|
const mouse = event;
|
|
274
429
|
const clicked = markedFrom(event);
|
|
275
|
-
const el =
|
|
430
|
+
const el = clicked;
|
|
276
431
|
if (!el)
|
|
277
432
|
return;
|
|
278
433
|
select({ id: el.getAttribute('data-hz') ?? '', index: indexOf(el), visible: visibleOf(el) }, mouse.shiftKey);
|
|
279
434
|
}, { capture: true, signal });
|
|
280
435
|
target.addEventListener('pointermove', (event) => {
|
|
436
|
+
measuring = event.altKey;
|
|
281
437
|
if (state.mode !== 'select')
|
|
282
438
|
return;
|
|
283
439
|
const el = ours(event) ? null : markedFrom(event);
|
|
@@ -285,30 +441,60 @@ function listen(target) {
|
|
|
285
441
|
void showHover(el);
|
|
286
442
|
}, { capture: true, passive: true, signal });
|
|
287
443
|
target.addEventListener('keydown', onKey, { capture: true, signal });
|
|
444
|
+
target.addEventListener('keyup', (event) => {
|
|
445
|
+
measuring = event.altKey;
|
|
446
|
+
}, { capture: true, signal });
|
|
447
|
+
target.addEventListener('blur', () => {
|
|
448
|
+
measuring = false;
|
|
449
|
+
}, { signal });
|
|
288
450
|
return stop;
|
|
289
451
|
}
|
|
452
|
+
/** The marked parts directly inside `parent` (or the page), in document order. */
|
|
453
|
+
function partsIn(parent) {
|
|
454
|
+
const all = [...(parent ?? doc).querySelectorAll('[data-hz]')];
|
|
455
|
+
return all.filter((e) => (e.parentElement?.closest('[data-hz]') ?? null) === parent);
|
|
456
|
+
}
|
|
290
457
|
function walk(direction) {
|
|
291
458
|
const active = state.picks[state.active];
|
|
292
459
|
if (!active)
|
|
293
460
|
return;
|
|
294
461
|
const el = elementOf(active);
|
|
295
462
|
const own = el?.getAttribute('data-hz') === active.id;
|
|
463
|
+
const beside = () => {
|
|
464
|
+
if (!el || !own)
|
|
465
|
+
return null;
|
|
466
|
+
const list = partsIn(el.parentElement?.closest('[data-hz]') ?? null).filter((e) => {
|
|
467
|
+
const b = e.getBoundingClientRect();
|
|
468
|
+
return e === el || (b.width > 0 && b.height > 0);
|
|
469
|
+
});
|
|
470
|
+
const at = list.indexOf(el);
|
|
471
|
+
return list[(at + (direction === 'next' ? 1 : -1) + list.length) % list.length] ?? null;
|
|
472
|
+
};
|
|
296
473
|
const next = direction === 'up'
|
|
297
474
|
? own
|
|
298
475
|
? el?.parentElement?.closest('[data-hz]')
|
|
299
476
|
: el
|
|
300
|
-
:
|
|
301
|
-
?
|
|
302
|
-
|
|
303
|
-
|
|
477
|
+
: direction === 'down'
|
|
478
|
+
? own
|
|
479
|
+
? el?.querySelector('[data-hz]')
|
|
480
|
+
: null
|
|
481
|
+
: beside();
|
|
482
|
+
if (!next || ((direction === 'next' || direction === 'previous') && next === el))
|
|
304
483
|
return;
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
484
|
+
const moved = { id: next.getAttribute('data-hz') ?? '', index: indexOf(next), visible: visibleOf(next) };
|
|
485
|
+
if (hasContent(active)) {
|
|
486
|
+
const existing = state.picks.findIndex((p) => p.id === moved.id && p.index === moved.index);
|
|
487
|
+
if (existing >= 0)
|
|
488
|
+
state.active = existing;
|
|
489
|
+
else {
|
|
490
|
+
state.picks.push({ ...moved, note: '', scope: null });
|
|
491
|
+
state.active = state.picks.length - 1;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
else {
|
|
495
|
+
preview(el, []);
|
|
496
|
+
state.picks[state.active] = { ...moved, note: '', scope: null };
|
|
497
|
+
}
|
|
312
498
|
persist();
|
|
313
499
|
drawPicks();
|
|
314
500
|
void renderPanel();
|
|
@@ -322,16 +508,19 @@ function setMode(mode) {
|
|
|
322
508
|
persist();
|
|
323
509
|
renderDock();
|
|
324
510
|
}
|
|
511
|
+
const typingIn = (event) => event.composedPath().some((t) => ['TEXTAREA', 'INPUT', 'SELECT'].includes(t.tagName));
|
|
325
512
|
function onKey(event) {
|
|
326
|
-
const typing = event
|
|
327
|
-
|
|
328
|
-
|
|
513
|
+
const typing = typingIn(event);
|
|
514
|
+
if (event.key === 'Escape' && assets.isOpen())
|
|
515
|
+
return assets.close();
|
|
329
516
|
if (event.altKey && event.shiftKey && event.code === 'KeyS') {
|
|
330
517
|
event.preventDefault();
|
|
331
518
|
return setMode(state.mode === 'select' ? 'browse' : 'select');
|
|
332
519
|
}
|
|
333
520
|
if (typing)
|
|
334
521
|
return;
|
|
522
|
+
if (event.key === 'Escape' && bench.classList.contains('layers-open'))
|
|
523
|
+
return closeLayers();
|
|
335
524
|
if (event.key === 'Escape' && state.mode === 'select')
|
|
336
525
|
return setMode('browse');
|
|
337
526
|
if (event.key === 'Escape' && state.panel) {
|
|
@@ -339,12 +528,27 @@ function onKey(event) {
|
|
|
339
528
|
persist();
|
|
340
529
|
return void renderPanel();
|
|
341
530
|
}
|
|
342
|
-
if (
|
|
343
|
-
|
|
344
|
-
state.
|
|
345
|
-
|
|
346
|
-
|
|
531
|
+
if (event.key === 'Alt') {
|
|
532
|
+
measuring = true;
|
|
533
|
+
if (state.mode === 'select')
|
|
534
|
+
event.preventDefault();
|
|
535
|
+
return;
|
|
347
536
|
}
|
|
537
|
+
if (state.mode !== 'select' || !state.picks.length || event.composedPath().includes(host))
|
|
538
|
+
return;
|
|
539
|
+
const step = event.key === 'ArrowUp' || (event.key === 'Enter' && event.shiftKey)
|
|
540
|
+
? 'up'
|
|
541
|
+
: event.key === 'ArrowDown' || event.key === 'Enter'
|
|
542
|
+
? 'down'
|
|
543
|
+
: event.key === 'Tab'
|
|
544
|
+
? event.shiftKey
|
|
545
|
+
? 'previous'
|
|
546
|
+
: 'next'
|
|
547
|
+
: null;
|
|
548
|
+
if (!step)
|
|
549
|
+
return;
|
|
550
|
+
event.preventDefault();
|
|
551
|
+
walk(step);
|
|
348
552
|
}
|
|
349
553
|
let listening = listen(window);
|
|
350
554
|
const reload = () => win.location.reload();
|
|
@@ -439,6 +643,12 @@ function renderDock() {
|
|
|
439
643
|
void renderPanel();
|
|
440
644
|
},
|
|
441
645
|
}, ['Layers']),
|
|
646
|
+
h('button', {
|
|
647
|
+
class: 'act',
|
|
648
|
+
type: 'button',
|
|
649
|
+
title: 'Every component of the app, its variants, previews and uses; and the design tokens',
|
|
650
|
+
onclick: () => void assets.open(),
|
|
651
|
+
}, ['Assets']),
|
|
442
652
|
h('button', {
|
|
443
653
|
class: 'act',
|
|
444
654
|
type: 'button',
|
|
@@ -454,7 +664,7 @@ function renderDock() {
|
|
|
454
664
|
state.panel = state.picks.length ? 'inspector' : null;
|
|
455
665
|
openBench();
|
|
456
666
|
},
|
|
457
|
-
}, ['
|
|
667
|
+
}, ['Frame']),
|
|
458
668
|
h('button', {
|
|
459
669
|
class: 'act gear',
|
|
460
670
|
type: 'button',
|
|
@@ -466,6 +676,17 @@ function renderDock() {
|
|
|
466
676
|
void renderPanel();
|
|
467
677
|
},
|
|
468
678
|
}, ['⚙']),
|
|
679
|
+
screenOf()
|
|
680
|
+
? h('button', {
|
|
681
|
+
class: 'previewing',
|
|
682
|
+
type: 'button',
|
|
683
|
+
title: 'Exit the preview from previews.ts',
|
|
684
|
+
onclick: () => {
|
|
685
|
+
setScreen(null);
|
|
686
|
+
win.location.reload();
|
|
687
|
+
},
|
|
688
|
+
}, [`Screen: ${screenOf().slice(screenOf().indexOf(':') + 1)}`, ' · Exit'])
|
|
689
|
+
: null,
|
|
469
690
|
held()
|
|
470
691
|
? h('button', {
|
|
471
692
|
class: 'previewing',
|
|
@@ -478,7 +699,8 @@ function renderDock() {
|
|
|
478
699
|
? h('div', { class: 'tip', role: 'note' }, [
|
|
479
700
|
h('span', {}, [h('kbd', {}, ['Click']), ' a part to select it']),
|
|
480
701
|
h('span', {}, [h('kbd', {}, ['Shift']), '+click adds another']),
|
|
481
|
-
h('span', {}, [h('kbd', {}, ['
|
|
702
|
+
h('span', {}, [h('kbd', {}, ['⇧ Enter']), ' picks its parent']),
|
|
703
|
+
h('span', {}, [h('kbd', {}, ['Alt']), ' measures']),
|
|
482
704
|
h('span', {}, [h('kbd', {}, ['Esc']), ' stops']),
|
|
483
705
|
])
|
|
484
706
|
: null,
|
|
@@ -978,8 +1200,13 @@ async function renderPanel() {
|
|
|
978
1200
|
return renderSettings();
|
|
979
1201
|
if (state.panel === 'notes')
|
|
980
1202
|
return agent.render(panel);
|
|
981
|
-
if (state.panel === 'layers')
|
|
982
|
-
|
|
1203
|
+
if (state.panel === 'layers') {
|
|
1204
|
+
await renderLayers(panel, await tree(win.location.pathname), layersHost(() => open(state.picks.length ? 'inspector' : null)));
|
|
1205
|
+
const screens = await screensSection();
|
|
1206
|
+
if (screens)
|
|
1207
|
+
panel.append(screens);
|
|
1208
|
+
return;
|
|
1209
|
+
}
|
|
983
1210
|
const active = state.picks[state.active];
|
|
984
1211
|
if (state.panel !== 'inspector' || !active) {
|
|
985
1212
|
panel.hidden = state.view !== 'workbench';
|
|
@@ -1064,7 +1291,7 @@ async function renderChanges() {
|
|
|
1064
1291
|
const done = h('button', {
|
|
1065
1292
|
class: 'link done',
|
|
1066
1293
|
type: 'button',
|
|
1067
|
-
title: '
|
|
1294
|
+
title: 'Resolved or not needed: remove it',
|
|
1068
1295
|
onclick: async () => {
|
|
1069
1296
|
if (!armed) {
|
|
1070
1297
|
armed = true;
|
|
@@ -1074,7 +1301,7 @@ async function renderChanges() {
|
|
|
1074
1301
|
await remove(r.number);
|
|
1075
1302
|
void renderPanel();
|
|
1076
1303
|
},
|
|
1077
|
-
}, ['
|
|
1304
|
+
}, ['Resolve']);
|
|
1078
1305
|
return h('div', { class: 'req' }, [
|
|
1079
1306
|
h('span', { class: 'n' }, [r.number]),
|
|
1080
1307
|
h('button', { class: 't open-req', type: 'button', onclick: () => open('changes', r.number, 'saved') }, [r.title]),
|
|
@@ -1087,7 +1314,7 @@ async function renderChanges() {
|
|
|
1087
1314
|
message ? h('div', { class: 'notice' }, message) : null,
|
|
1088
1315
|
...(list.length
|
|
1089
1316
|
? [...list].reverse().map(row)
|
|
1090
|
-
: [h('div', { class: 'empty' }, ['No open requests.
|
|
1317
|
+
: [h('div', { class: 'empty' }, ['No open requests. Resolved ones are removed.'])]),
|
|
1091
1318
|
list.length
|
|
1092
1319
|
? h('div', { class: 'sec' }, [
|
|
1093
1320
|
h('div', { class: 'choice' }, [
|
|
@@ -1199,7 +1426,7 @@ async function renderRequest(number) {
|
|
|
1199
1426
|
renderDock();
|
|
1200
1427
|
open('changes', null, 'saved');
|
|
1201
1428
|
},
|
|
1202
|
-
}, ['
|
|
1429
|
+
}, ['Resolve · remove']);
|
|
1203
1430
|
panel.replaceChildren(h('div', { class: 'head' }, [
|
|
1204
1431
|
h('button', { class: 'back', type: 'button', onclick: () => open('changes', null, 'saved') }, [
|
|
1205
1432
|
'← Saved',
|
|
@@ -1298,7 +1525,10 @@ function renderSettings() {
|
|
|
1298
1525
|
['Alt+Shift+S', 'Select on or off'],
|
|
1299
1526
|
['Click', 'Select'],
|
|
1300
1527
|
['Shift+click', 'Add to the request'],
|
|
1301
|
-
['
|
|
1528
|
+
['Shift+Enter', 'Select the area around it'],
|
|
1529
|
+
['Enter', 'Select inside it'],
|
|
1530
|
+
['Tab / Shift+Tab', 'Next / previous part beside it'],
|
|
1531
|
+
['Alt (hold)', 'Measure: W × H and the distance to the part under the pointer'],
|
|
1302
1532
|
['Double-click', 'Select a text'],
|
|
1303
1533
|
['↑ ↓', 'Around it / inside it'],
|
|
1304
1534
|
['Esc', 'Back to Browse'],
|
|
@@ -1315,6 +1545,10 @@ const left = h('div', { class: 'bench-left' });
|
|
|
1315
1545
|
const stage = h('div', { class: 'stage' });
|
|
1316
1546
|
const sizer = h('div', { class: 'sizer' });
|
|
1317
1547
|
const bar = h('div', { class: 'bench-bar' });
|
|
1548
|
+
addEventListener('keydown', (event) => {
|
|
1549
|
+
if (state.view === 'workbench' && win !== window)
|
|
1550
|
+
onKey(event);
|
|
1551
|
+
}, true);
|
|
1318
1552
|
function retarget(frame) {
|
|
1319
1553
|
listening.abort();
|
|
1320
1554
|
const w = frame?.contentWindow ?? null;
|
|
@@ -1375,6 +1609,15 @@ function renderBar() {
|
|
|
1375
1609
|
bar.replaceChildren(...present([
|
|
1376
1610
|
h('div', { class: 'grip' }, [h('img', { src: logo, alt: 'Hozu', width: '18', height: '18' })]),
|
|
1377
1611
|
h('div', { class: 'seg' }, [mode('Browse', 'browse'), mode('Select', 'select')]),
|
|
1612
|
+
h('button', {
|
|
1613
|
+
class: 'act layers-toggle',
|
|
1614
|
+
type: 'button',
|
|
1615
|
+
'aria-pressed': String(bench.classList.contains('layers-open')),
|
|
1616
|
+
onclick: () => {
|
|
1617
|
+
bench.classList.toggle('layers-open');
|
|
1618
|
+
renderBar();
|
|
1619
|
+
},
|
|
1620
|
+
}, ['Layers']),
|
|
1378
1621
|
select,
|
|
1379
1622
|
h('button', {
|
|
1380
1623
|
class: 'act',
|
|
@@ -1408,11 +1651,21 @@ function renderBar() {
|
|
|
1408
1651
|
'aria-pressed': api.isOpen() ? 'true' : 'false',
|
|
1409
1652
|
onclick: () => api.toggle(),
|
|
1410
1653
|
}, ['API']),
|
|
1411
|
-
h('button', {
|
|
1654
|
+
h('button', {
|
|
1655
|
+
class: 'act',
|
|
1656
|
+
type: 'button',
|
|
1657
|
+
title: 'Every component of the app, its variants, previews and uses; and the design tokens',
|
|
1658
|
+
onclick: () => void assets.open(),
|
|
1659
|
+
}, ['Assets']),
|
|
1660
|
+
h('button', { class: 'act exit', type: 'button', onclick: () => closeBench() }, ['Exit frame']),
|
|
1412
1661
|
]));
|
|
1413
1662
|
}
|
|
1414
1663
|
async function renderLeft() {
|
|
1415
|
-
await renderLayers(left, await tree(win.location.pathname), layersHost(
|
|
1664
|
+
await renderLayers(left, await tree(win.location.pathname), layersHost(closeLayers));
|
|
1665
|
+
}
|
|
1666
|
+
function closeLayers() {
|
|
1667
|
+
bench.classList.remove('layers-open');
|
|
1668
|
+
renderBar();
|
|
1416
1669
|
}
|
|
1417
1670
|
function openBench() {
|
|
1418
1671
|
state.view = 'workbench';
|
|
@@ -1421,7 +1674,7 @@ function openBench() {
|
|
|
1421
1674
|
bench.hidden = false;
|
|
1422
1675
|
const frame = h('iframe', {
|
|
1423
1676
|
src: location.href,
|
|
1424
|
-
title: 'The page in
|
|
1677
|
+
title: 'The page in Frame',
|
|
1425
1678
|
'data-hozu-bench': true,
|
|
1426
1679
|
});
|
|
1427
1680
|
frame.addEventListener('load', () => {
|
|
@@ -1489,6 +1742,10 @@ function closeBench() {
|
|
|
1489
1742
|
void renderPanel();
|
|
1490
1743
|
}
|
|
1491
1744
|
addEventListener('resize', layout);
|
|
1745
|
+
addEventListener('keydown', (event) => {
|
|
1746
|
+
if (event.key === 'Escape' && !typingIn(event) && bench.classList.contains('layers-open'))
|
|
1747
|
+
closeLayers();
|
|
1748
|
+
}, true);
|
|
1492
1749
|
renderDock();
|
|
1493
1750
|
drawPicks();
|
|
1494
1751
|
void renderPanel();
|