@dev-lines/core 0.1.0 → 0.2.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.ts CHANGED
@@ -7,13 +7,18 @@
7
7
  * - optional padding edges (paddingX in from each side)
8
8
  * - depth-colored outlines around layout containers (flex/grid)
9
9
  * - labels: per-box chips (tag · size · layout · custom name) and guide readouts
10
- * - hold Alt: spacing overlay from the hovered box to its containing box
10
+ * - box model: padding + flex/grid gap bands on the hovered/locked box
11
+ * - inspect: lock onto a box and walk the tree with the arrow keys
12
+ * - hold Alt: spacing overlay from the hovered/locked box to its containing box
11
13
  *
12
14
  * Everything lives in one fixed overlay layer with pointer-events: none, so the
13
- * host app is never touched and teardown is total.
15
+ * host app is never touched and teardown is total. The optional toolbar pill
16
+ * (`toolbar: true`) is the one interactive element, mounted beside the overlay.
14
17
  *
15
18
  * Commands (when enabled): O toggle outlines · G toggle guides · L cycle labels ·
16
- * Esc disable · hold Alt for distances. Toggle the overlay with mod+shift+L.
19
+ * I lock/unlock inspect (arrows walk the tree) · B toggle box-model bands ·
20
+ * C copy handle · Esc unlock/disable · hold Alt for distances.
21
+ * Toggle the overlay with mod+shift+L.
17
22
  */
18
23
  type LabelField = "name" | "tag" | "size" | "display" | "depth";
19
24
  type LabelMode = "off" | "hover" | "all";
@@ -28,6 +33,8 @@ interface DevLinesOptions {
28
33
  lineColor?: string;
29
34
  paddingColor?: string;
30
35
  measureColor?: string;
36
+ /** Color for flex/grid gap bands. Default "138 224 108" (green). */
37
+ gapColor?: string;
31
38
  depthColors?: string[];
32
39
  sections?: boolean;
33
40
  sectionSelector?: string;
@@ -36,8 +43,16 @@ interface DevLinesOptions {
36
43
  nameAttr?: string;
37
44
  /** Derive a name (id → aria-label → data-component/testid → heading) when none is set. Default true. */
38
45
  autoName?: boolean;
39
- /** Key to copy the hovered box's handle (name · selector · tag) for handing to an agent. Default "c". */
46
+ /** Key to copy the hovered box's handle (name · selector · tag · size · display · padding · gap). Default "c". */
40
47
  copyKey?: string | null;
48
+ /** Key to lock/unlock inspect on the hovered box. Arrow keys then walk the tree. Default "i". */
49
+ inspectKey?: string | null;
50
+ /** Show padding + gap bands on the hovered/locked box. Default true. */
51
+ boxModel?: boolean;
52
+ /** Key to toggle the box-model bands. Default "b". */
53
+ boxModelKey?: string | null;
54
+ /** Mount the on-page toolbar pill (power · guides · outlines · labels · copy · settings). Default false. */
55
+ toolbar?: boolean;
41
56
  shortcut?: string | null;
42
57
  labelKey?: string | null;
43
58
  persist?: boolean;
@@ -51,15 +66,21 @@ interface DevLinesController {
51
66
  cycleLabels(mode?: LabelMode): void;
52
67
  toggleOutlines(on?: boolean): void;
53
68
  toggleGuides(on?: boolean): void;
54
- /** Live-update visual options (colors, contentWidth, paddingX) and redraw. */
69
+ /** Toggle the padding/gap bands on the hovered/locked box. */
70
+ toggleBoxModel(on?: boolean): void;
71
+ /** Lock inspect onto an element (null unlocks; no argument toggles on the hovered box). */
72
+ inspect(el?: Element | null): void;
73
+ /** Live-update visual options (colors, contentWidth, paddingX, labels) and redraw. */
55
74
  update(options: Partial<DevLinesOptions>): void;
56
- /** Copy the hovered (or last-hovered) box's handle for agent handoff. */
75
+ /** Copy the locked/hovered (or last-hovered) box's handle for agent handoff. */
57
76
  copy(): void;
58
77
  getState(): {
59
78
  enabled: boolean;
60
79
  outlines: boolean;
61
80
  guides: boolean;
62
81
  labels: LabelMode;
82
+ boxModel: boolean;
83
+ inspected: Element | null;
63
84
  };
64
85
  refresh(): void;
65
86
  destroy(): void;
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createDevLines
3
- } from "./chunk-MUK3OXWT.js";
3
+ } from "./chunk-6KQDYHPD.js";
4
4
  export {
5
5
  createDevLines
6
6
  };
package/dist/react.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import {
3
3
  createDevLines
4
- } from "./chunk-MUK3OXWT.js";
4
+ } from "./chunk-6KQDYHPD.js";
5
5
 
6
6
  // src/react.tsx
7
7
  import { useEffect } from "react";
package/package.json CHANGED
@@ -1,17 +1,17 @@
1
1
  {
2
2
  "name": "@dev-lines/core",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Framework-agnostic layout debug overlay: center/width/padding guides + depth-colored section outlines, labels, and distances. Zero dependencies.",
5
5
  "license": "MIT",
6
6
  "author": "wa-de (https://www.wa-de.org)",
7
7
  "homepage": "https://lines.wiki",
8
8
  "repository": {
9
9
  "type": "git",
10
- "url": "git+https://github.com/wwwwaaaaddddeeee/dev-lines.git",
10
+ "url": "git+https://github.com/w-ade/dev-lines.git",
11
11
  "directory": "packages/core"
12
12
  },
13
13
  "bugs": {
14
- "url": "https://github.com/wwwwaaaaddddeeee/dev-lines/issues"
14
+ "url": "https://github.com/w-ade/dev-lines/issues"
15
15
  },
16
16
  "keywords": [
17
17
  "debug",
@@ -46,6 +46,7 @@
46
46
  "sideEffects": false,
47
47
  "scripts": {
48
48
  "build": "tsup src/index.ts src/react.tsx --format esm --dts --clean",
49
+ "build:site": "tsup src/index.ts --format esm --clean false -d ../../demo && mv ../../demo/index.js ../../demo/dev-lines.js",
49
50
  "dev": "tsup src/index.ts src/react.tsx --format esm --dts --watch"
50
51
  },
51
52
  "peerDependencies": {
@@ -1,492 +0,0 @@
1
- // src/index.ts
2
- var STORAGE_KEY = "dev-lines:enabled";
3
- var Z = 2147483600;
4
- var DEFAULT_DEPTH_COLORS = [
5
- "29 29 29",
6
- // #1d1d1d neutral (graphite)
7
- "155 164 183",
8
- // #9ba4b7 neutral (gray)
9
- "167 125 255",
10
- // #a77dff purple
11
- "245 104 104",
12
- // #f56868 coral
13
- "237 139 0",
14
- // #ed8b00 orange
15
- "241 223 56",
16
- // #f1df38 yellow
17
- "138 224 108"
18
- // #8ae06c green
19
- ];
20
- var DEFAULTS = {
21
- lineColor: "245 104 104",
22
- // #f56868 coral
23
- paddingColor: "167 125 255",
24
- // #a77dff purple
25
- measureColor: "237 139 0",
26
- // #ed8b00 orange
27
- sections: true,
28
- shortcut: "mod+shift+l",
29
- labelKey: "l",
30
- copyKey: "c",
31
- nameAttr: "data-devlines-name",
32
- autoName: true,
33
- persist: true,
34
- start: false
35
- };
36
- var DEFAULT_LABELS = {
37
- mode: "hover",
38
- show: ["name", "tag", "size", "display"],
39
- guides: true
40
- };
41
- var isBrowser = typeof window !== "undefined" && typeof document !== "undefined";
42
- function createDevLines(options = {}) {
43
- if (!isBrowser) return noopController();
44
- const opts = { ...DEFAULTS, ...options };
45
- let depthColors = options.depthColors ?? DEFAULT_DEPTH_COLORS;
46
- const labels = { ...DEFAULT_LABELS, ...options.labels };
47
- let enabled = false;
48
- let root = null;
49
- let sectionsLayer = null;
50
- let guidesLayer = null;
51
- let labelsLayer = null;
52
- let measureLayer = null;
53
- let scheduled = false;
54
- let ro = null;
55
- let mo = null;
56
- let boxes = [];
57
- let pointer = null;
58
- let hoverBox = null;
59
- let lastHoverBox = null;
60
- let outlinesOn = opts.sections;
61
- let guidesOn = true;
62
- let altDown = false;
63
- const rgba = (t, a) => `rgb(${t} / ${a})`;
64
- const colorForDepth = (d) => depthColors[Math.min(d, depthColors.length - 1)];
65
- function line(css) {
66
- const el = document.createElement("div");
67
- Object.assign(el.style, { position: "absolute", ...css });
68
- return el;
69
- }
70
- function chip(text, colorTriplet, left, top) {
71
- const el = document.createElement("div");
72
- el.textContent = text;
73
- Object.assign(el.style, {
74
- position: "absolute",
75
- left: `${Math.max(0, left)}px`,
76
- top: `${Math.max(0, top)}px`,
77
- font: '500 10px/1.4 "Geist Mono", ui-monospace, monospace',
78
- letterSpacing: "0.01em",
79
- color: "#fff",
80
- background: rgba(colorTriplet, 0.92),
81
- padding: "1px 5px",
82
- borderRadius: "4px",
83
- whiteSpace: "nowrap",
84
- pointerEvents: "none",
85
- transform: "translateZ(0)"
86
- });
87
- return el;
88
- }
89
- function buildGuides() {
90
- const layer = document.createElement("div");
91
- Object.assign(layer.style, { position: "absolute", inset: "0" });
92
- const c = rgba(opts.lineColor, 0.75);
93
- layer.appendChild(line({ top: "0", bottom: "0", left: "50%", width: "1px", transform: "translateX(-50%)", background: c }));
94
- layer.appendChild(line({ left: "0", right: "0", top: "50%", height: "1px", transform: "translateY(-50%)", background: c }));
95
- if (opts.contentWidth && opts.contentWidth > 0) {
96
- const half = opts.contentWidth / 2;
97
- layer.appendChild(line({ top: "0", bottom: "0", left: "50%", width: "1px", transform: `translateX(${-half}px)`, background: c }));
98
- layer.appendChild(line({ top: "0", bottom: "0", left: "50%", width: "1px", transform: `translateX(${half}px)`, background: c }));
99
- }
100
- if (opts.paddingX && opts.paddingX > 0) {
101
- const p = rgba(opts.paddingColor, 0.75);
102
- layer.appendChild(line({ top: "0", bottom: "0", left: `${opts.paddingX}px`, width: "1px", background: p }));
103
- layer.appendChild(line({ top: "0", bottom: "0", right: `${opts.paddingX}px`, width: "1px", background: p }));
104
- }
105
- return layer;
106
- }
107
- function isContainer(el) {
108
- if (el.getAttribute("data-devlines") === "outline") return true;
109
- const display = getComputedStyle(el).display;
110
- return display.includes("flex") || display.includes("grid");
111
- }
112
- function collect() {
113
- const out = [];
114
- const walk = (el, depth) => {
115
- if (el === root || el.getAttribute("data-devlines") === "ignore") return;
116
- const hit = isContainer(el);
117
- if (hit) out.push({ el, depth });
118
- const next = hit ? depth + 1 : depth;
119
- for (const child of el.children) walk(child, next);
120
- };
121
- if (opts.sectionSelector) {
122
- document.querySelectorAll(opts.sectionSelector).forEach((el) => out.push({ el, depth: 0 }));
123
- } else {
124
- for (const child of document.body.children) walk(child, 0);
125
- }
126
- return out;
127
- }
128
- function drawSections() {
129
- if (!sectionsLayer) return;
130
- sectionsLayer.replaceChildren();
131
- boxes = [];
132
- if (outlinesOn) {
133
- for (const { el, depth } of collect()) {
134
- const rect = el.getBoundingClientRect();
135
- if (rect.width === 0 || rect.height === 0) continue;
136
- boxes.push({ el, depth, rect });
137
- const color = colorForDepth(depth);
138
- const box = document.createElement("div");
139
- Object.assign(box.style, {
140
- position: "absolute",
141
- left: `${rect.left}px`,
142
- top: `${rect.top}px`,
143
- width: `${rect.width}px`,
144
- height: `${rect.height}px`,
145
- boxShadow: `inset 0 0 0 1px ${rgba(color, 0.6)}`
146
- });
147
- sectionsLayer.appendChild(box);
148
- }
149
- }
150
- hoverBox = pointer ? boxAt(pointer.x, pointer.y) : null;
151
- drawLabels();
152
- drawMeasure();
153
- }
154
- function nameOf(el) {
155
- const explicit = el.getAttribute(opts.nameAttr);
156
- if (explicit) return explicit;
157
- if (!opts.autoName) return null;
158
- if (el.id) return el.id;
159
- const aria = el.getAttribute("aria-label");
160
- if (aria) return aria;
161
- const comp = el.getAttribute("data-component") || el.getAttribute("data-testid");
162
- if (comp) return comp;
163
- const h = el.querySelector("h1,h2,h3,h4,h5,h6");
164
- const t = h?.textContent?.trim();
165
- if (t) return t.length > 24 ? t.slice(0, 24) + "\u2026" : t;
166
- return null;
167
- }
168
- function selectorFor(el) {
169
- if (el.id) return `#${CSS.escape(el.id)}`;
170
- const parts = [];
171
- let node = el;
172
- while (node && node !== document.body) {
173
- const cur = node;
174
- if (cur.id) {
175
- parts.unshift(`#${CSS.escape(cur.id)}`);
176
- break;
177
- }
178
- let sel = cur.tagName.toLowerCase();
179
- const parent = cur.parentElement;
180
- if (parent) {
181
- const sameTag = Array.from(parent.children).filter((c) => c.tagName === cur.tagName);
182
- if (sameTag.length > 1) sel += `:nth-of-type(${sameTag.indexOf(cur) + 1})`;
183
- }
184
- parts.unshift(sel);
185
- node = parent;
186
- }
187
- return parts.join(" > ") || el.tagName.toLowerCase();
188
- }
189
- function labelText(el, depth, rect) {
190
- const parts = [];
191
- for (const f of labels.show) {
192
- if (f === "name") {
193
- const nm = nameOf(el);
194
- if (nm) parts.push(nm);
195
- } else if (f === "tag") parts.push(`<${el.tagName.toLowerCase()}>`);
196
- else if (f === "size") parts.push(`${Math.round(rect.width)}\xD7${Math.round(rect.height)}`);
197
- else if (f === "display") {
198
- const d = getComputedStyle(el).display;
199
- parts.push(d.includes("grid") ? "grid" : d.includes("flex") ? "flex" : d);
200
- } else if (f === "depth") parts.push(`d${depth}`);
201
- }
202
- return parts.join(" \xB7 ");
203
- }
204
- function boxAt(x, y) {
205
- let best = null;
206
- for (const b of boxes) {
207
- const r = b.rect;
208
- if (x >= r.left && x <= r.right && y >= r.top && y <= r.bottom) {
209
- if (!best || b.depth > best.depth) best = b;
210
- }
211
- }
212
- return best;
213
- }
214
- function ancestorOf(target) {
215
- let best = null;
216
- for (const b of boxes) {
217
- if (b === target || b.depth >= target.depth) continue;
218
- const r = b.rect, t = target.rect;
219
- if (r.left <= t.left && r.top <= t.top && r.right >= t.right && r.bottom >= t.bottom) {
220
- if (!best || b.depth > best.depth) best = b;
221
- }
222
- }
223
- return best;
224
- }
225
- function drawLabels() {
226
- if (!labelsLayer) return;
227
- labelsLayer.replaceChildren();
228
- if (labels.guides && guidesOn) {
229
- const cx = window.innerWidth / 2;
230
- labelsLayer.appendChild(chip(`${Math.round(cx)}`, opts.lineColor, cx + 4, 4));
231
- if (opts.contentWidth && opts.contentWidth > 0) {
232
- const half = opts.contentWidth / 2;
233
- labelsLayer.appendChild(chip(`\u2212${Math.round(half)}`, opts.lineColor, cx - half + 4, 22));
234
- labelsLayer.appendChild(chip(`+${Math.round(half)}`, opts.lineColor, cx + half + 4, 22));
235
- }
236
- if (opts.paddingX && opts.paddingX > 0) {
237
- labelsLayer.appendChild(chip(`${Math.round(opts.paddingX)}`, opts.paddingColor, opts.paddingX + 4, 4));
238
- labelsLayer.appendChild(chip(`${Math.round(opts.paddingX)}`, opts.paddingColor, window.innerWidth - opts.paddingX + 4, 4));
239
- }
240
- }
241
- if (!outlinesOn) return;
242
- if (labels.mode === "all") {
243
- for (const b of boxes) labelsLayer.appendChild(chip(labelText(b.el, b.depth, b.rect), colorForDepth(b.depth), b.rect.left, b.rect.top));
244
- } else if (labels.mode === "hover" && hoverBox) {
245
- labelsLayer.appendChild(chip(labelText(hoverBox.el, hoverBox.depth, hoverBox.rect), colorForDepth(hoverBox.depth), hoverBox.rect.left, hoverBox.rect.top));
246
- }
247
- }
248
- function measureSeg(x1, y1, x2, y2, value) {
249
- if (!measureLayer || value < 1) return;
250
- const horizontal = y1 === y2;
251
- measureLayer.appendChild(line({
252
- left: `${Math.min(x1, x2)}px`,
253
- top: `${Math.min(y1, y2)}px`,
254
- width: horizontal ? `${Math.abs(x2 - x1)}px` : "1px",
255
- height: horizontal ? "1px" : `${Math.abs(y2 - y1)}px`,
256
- background: rgba(opts.measureColor, 0.9)
257
- }));
258
- const mx = horizontal ? (x1 + x2) / 2 : x1 + 3;
259
- const my = horizontal ? y1 + 3 : (y1 + y2) / 2;
260
- measureLayer.appendChild(chip(`${Math.round(value)}`, opts.measureColor, mx, my));
261
- }
262
- function drawMeasure() {
263
- if (!measureLayer) return;
264
- measureLayer.replaceChildren();
265
- if (!altDown || !hoverBox || !outlinesOn) return;
266
- const t = hoverBox.rect;
267
- const anc = ancestorOf(hoverBox);
268
- const c = anc ? anc.rect : new DOMRect(0, 0, window.innerWidth, window.innerHeight);
269
- const midX = (t.left + t.right) / 2;
270
- const midY = (t.top + t.bottom) / 2;
271
- measureSeg(midX, c.top, midX, t.top, t.top - c.top);
272
- measureSeg(midX, t.bottom, midX, c.bottom, c.bottom - t.bottom);
273
- measureSeg(c.left, midY, t.left, midY, t.left - c.left);
274
- measureSeg(t.right, midY, c.right, midY, c.right - t.right);
275
- }
276
- function flashCopied(rect) {
277
- if (!root) return;
278
- const el = chip("copied \u2713", "34 197 94", rect.left, rect.top - 2);
279
- root.appendChild(el);
280
- setTimeout(() => el.remove(), 900);
281
- }
282
- function copyHandle() {
283
- const target = hoverBox ?? lastHoverBox;
284
- if (!target) return;
285
- const el = target.el;
286
- const name = nameOf(el);
287
- const text = `${name ? `"${name}" \u2014 ` : ""}${selectorFor(el)} (<${el.tagName.toLowerCase()}>)`;
288
- navigator.clipboard?.writeText(text).then(() => flashCopied(target.rect)).catch(() => {
289
- });
290
- }
291
- function schedule() {
292
- if (scheduled) return;
293
- scheduled = true;
294
- requestAnimationFrame(() => {
295
- scheduled = false;
296
- drawSections();
297
- });
298
- }
299
- function onPointerMove(e) {
300
- pointer = { x: e.clientX, y: e.clientY };
301
- hoverBox = boxAt(e.clientX, e.clientY);
302
- if (hoverBox) lastHoverBox = hoverBox;
303
- drawLabels();
304
- drawMeasure();
305
- }
306
- function enable() {
307
- if (enabled) return;
308
- enabled = true;
309
- root = document.createElement("div");
310
- root.setAttribute("data-devlines", "ignore");
311
- Object.assign(root.style, { position: "fixed", inset: "0", zIndex: String(Z), pointerEvents: "none" });
312
- sectionsLayer = document.createElement("div");
313
- guidesLayer = buildGuides();
314
- labelsLayer = document.createElement("div");
315
- measureLayer = document.createElement("div");
316
- for (const l of [sectionsLayer, labelsLayer, measureLayer]) Object.assign(l.style, { position: "absolute", inset: "0" });
317
- guidesLayer.style.display = guidesOn ? "" : "none";
318
- root.append(sectionsLayer, guidesLayer, labelsLayer, measureLayer);
319
- document.body.appendChild(root);
320
- window.addEventListener("scroll", schedule, true);
321
- window.addEventListener("resize", schedule);
322
- window.addEventListener("pointermove", onPointerMove, true);
323
- ro = new ResizeObserver(schedule);
324
- ro.observe(document.documentElement);
325
- mo = new MutationObserver(schedule);
326
- mo.observe(document.body, { childList: true, subtree: true, attributes: true });
327
- drawSections();
328
- if (opts.persist) try {
329
- localStorage.setItem(STORAGE_KEY, "1");
330
- } catch {
331
- }
332
- }
333
- function disable() {
334
- if (!enabled) return;
335
- enabled = false;
336
- altDown = false;
337
- window.removeEventListener("scroll", schedule, true);
338
- window.removeEventListener("resize", schedule);
339
- window.removeEventListener("pointermove", onPointerMove, true);
340
- ro?.disconnect();
341
- mo?.disconnect();
342
- ro = mo = null;
343
- pointer = hoverBox = lastHoverBox = null;
344
- root?.remove();
345
- root = sectionsLayer = guidesLayer = labelsLayer = measureLayer = null;
346
- boxes = [];
347
- if (opts.persist) try {
348
- localStorage.setItem(STORAGE_KEY, "0");
349
- } catch {
350
- }
351
- }
352
- function toggle() {
353
- enabled ? disable() : enable();
354
- }
355
- function cycleLabels(mode) {
356
- const order = ["off", "hover", "all"];
357
- labels.mode = mode ?? order[(order.indexOf(labels.mode) + 1) % order.length];
358
- drawLabels();
359
- }
360
- function toggleOutlines(on) {
361
- outlinesOn = on ?? !outlinesOn;
362
- drawSections();
363
- }
364
- function toggleGuides(on) {
365
- guidesOn = on ?? !guidesOn;
366
- if (guidesLayer) guidesLayer.style.display = guidesOn ? "" : "none";
367
- drawLabels();
368
- }
369
- function update(next) {
370
- if (next.lineColor !== void 0) opts.lineColor = next.lineColor;
371
- if (next.paddingColor !== void 0) opts.paddingColor = next.paddingColor;
372
- if (next.measureColor !== void 0) opts.measureColor = next.measureColor;
373
- if (next.depthColors !== void 0) depthColors = next.depthColors;
374
- if (next.contentWidth !== void 0) opts.contentWidth = next.contentWidth;
375
- if (next.paddingX !== void 0) opts.paddingX = next.paddingX;
376
- if (root && guidesLayer) {
377
- const fresh = buildGuides();
378
- fresh.style.display = guidesOn ? "" : "none";
379
- root.replaceChild(fresh, guidesLayer);
380
- guidesLayer = fresh;
381
- }
382
- drawSections();
383
- }
384
- function typingInField() {
385
- const t = document.activeElement;
386
- return !!t && (t.tagName === "INPUT" || t.tagName === "TEXTAREA" || t.isContentEditable);
387
- }
388
- function matchesShortcut(e, combo) {
389
- const parts = combo.toLowerCase().split("+");
390
- const key = parts[parts.length - 1];
391
- const mod = e.metaKey || e.ctrlKey;
392
- return e.key.toLowerCase() === key && parts.includes("mod") === mod && parts.includes("shift") === e.shiftKey && parts.includes("alt") === e.altKey;
393
- }
394
- function onKey(e) {
395
- if (typingInField()) return;
396
- if (opts.shortcut && matchesShortcut(e, opts.shortcut)) {
397
- e.preventDefault();
398
- toggle();
399
- return;
400
- }
401
- if (!enabled) return;
402
- if (e.key === "Alt" && !altDown) {
403
- altDown = true;
404
- drawMeasure();
405
- return;
406
- }
407
- if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
408
- const k = e.key.toLowerCase();
409
- if (k === "escape") {
410
- e.preventDefault();
411
- disable();
412
- } else if (k === "o") {
413
- e.preventDefault();
414
- toggleOutlines();
415
- } else if (k === "g") {
416
- e.preventDefault();
417
- toggleGuides();
418
- } else if (opts.labelKey && k === opts.labelKey.toLowerCase()) {
419
- e.preventDefault();
420
- cycleLabels();
421
- } else if (opts.copyKey && k === opts.copyKey.toLowerCase()) {
422
- e.preventDefault();
423
- copyHandle();
424
- }
425
- }
426
- function onKeyUp(e) {
427
- if (e.key === "Alt" && altDown) {
428
- altDown = false;
429
- drawMeasure();
430
- }
431
- }
432
- window.addEventListener("keydown", onKey);
433
- window.addEventListener("keyup", onKeyUp);
434
- let startOn = opts.start;
435
- if (opts.persist) {
436
- try {
437
- const saved = localStorage.getItem(STORAGE_KEY);
438
- if (saved === "1") startOn = true;
439
- else if (saved === "0") startOn = false;
440
- } catch {
441
- }
442
- }
443
- if (startOn) enable();
444
- return {
445
- enable,
446
- disable,
447
- toggle,
448
- isEnabled: () => enabled,
449
- cycleLabels,
450
- toggleOutlines,
451
- toggleGuides,
452
- update,
453
- copy: copyHandle,
454
- getState: () => ({ enabled, outlines: outlinesOn, guides: guidesOn, labels: labels.mode }),
455
- refresh: drawSections,
456
- destroy() {
457
- disable();
458
- window.removeEventListener("keydown", onKey);
459
- window.removeEventListener("keyup", onKeyUp);
460
- }
461
- };
462
- }
463
- function noopController() {
464
- return {
465
- enable() {
466
- },
467
- disable() {
468
- },
469
- toggle() {
470
- },
471
- isEnabled: () => false,
472
- cycleLabels() {
473
- },
474
- toggleOutlines() {
475
- },
476
- toggleGuides() {
477
- },
478
- update() {
479
- },
480
- copy() {
481
- },
482
- getState: () => ({ enabled: false, outlines: false, guides: false, labels: "off" }),
483
- refresh() {
484
- },
485
- destroy() {
486
- }
487
- };
488
- }
489
-
490
- export {
491
- createDevLines
492
- };