efficient-easel 0.99.2 → 0.101.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.
Files changed (108) hide show
  1. package/README.md +233 -17
  2. package/dist/autosave/index.d.ts +11 -5
  3. package/dist/blocks/index.d.ts +1 -1
  4. package/dist/canvas/index.d.ts +2 -2
  5. package/dist/canvas/index.js +1 -1
  6. package/dist/chrome/chrome.css +96 -29
  7. package/dist/chrome/index.d.ts +67 -18
  8. package/dist/chrome/index.js +25 -21
  9. package/dist/{chunk-JYPOOHII.js → chunk-2FZJWH24.js} +3 -3
  10. package/dist/chunk-3EDSDPIP.js +1 -0
  11. package/dist/chunk-546BBOPE.js +1 -0
  12. package/dist/chunk-67GNT6PF.js +1 -0
  13. package/dist/chunk-EOJNJCMJ.js +16 -0
  14. package/dist/chunk-EWVJSRRY.js +6 -0
  15. package/dist/chunk-FPOKPNO4.js +1 -0
  16. package/dist/chunk-H24N5TMZ.js +11 -0
  17. package/dist/chunk-IG3U5NWJ.js +1 -0
  18. package/dist/chunk-JUQ6YJCG.js +1 -0
  19. package/dist/chunk-KTVH2ZRL.js +15 -0
  20. package/dist/{chunk-PI26YXYD.js → chunk-L5KW4HG5.js} +16 -8
  21. package/dist/chunk-M6F6LS3H.js +1 -0
  22. package/dist/chunk-P6O363EE.js +6 -0
  23. package/dist/chunk-PAELDYYS.js +1 -0
  24. package/dist/chunk-QTLAVDJE.js +1 -0
  25. package/dist/chunk-UH7IGZKF.js +1 -0
  26. package/dist/chunk-WZ7TGAAT.js +3 -0
  27. package/dist/chunk-ZFDCDJNZ.js +16 -0
  28. package/dist/chunk-ZJEBF4NB.js +1 -0
  29. package/dist/chunk-ZNS7727Y.js +12 -0
  30. package/dist/copy/copy.css +442 -5
  31. package/dist/copy/index.d.ts +302 -5
  32. package/dist/copy/index.js +32 -10
  33. package/dist/drag/drag.css +10 -1
  34. package/dist/drag/index.d.ts +99 -8
  35. package/dist/drag/index.js +18 -17
  36. package/dist/easel/easel.css +15 -0
  37. package/dist/easel/index.d.ts +104 -33
  38. package/dist/easel/index.js +9 -1
  39. package/dist/edit-lock/index.d.ts +1 -3
  40. package/dist/edit-lock/index.js +1 -1
  41. package/dist/fields/fields.css +270 -38
  42. package/dist/fields/index.d.ts +209 -26
  43. package/dist/fields/index.js +22 -53
  44. package/dist/gold/index.js +1 -1
  45. package/dist/icons/index.d.ts +13 -1
  46. package/dist/icons/index.js +1 -1
  47. package/dist/index.d.ts +2 -2
  48. package/dist/index.js +1 -1
  49. package/dist/markup/index.d.ts +111 -10
  50. package/dist/markup/index.js +1 -1
  51. package/dist/menu/index.d.ts +58 -5
  52. package/dist/menu/index.js +1 -15
  53. package/dist/menu/menu.css +17 -6
  54. package/dist/node-ops/index.d.ts +1 -1
  55. package/dist/node-ops/index.js +1 -1
  56. package/dist/panel/index.d.ts +83 -4
  57. package/dist/panel/index.js +18 -16
  58. package/dist/panel/panel.css +135 -62
  59. package/dist/pick/index.d.ts +78 -2
  60. package/dist/pick/index.js +7 -14
  61. package/dist/pick/pick.css +197 -10
  62. package/dist/rail/index.d.ts +3 -7
  63. package/dist/rail/index.js +1 -1
  64. package/dist/strings/catalogue.d.ts +13 -35
  65. package/dist/strings/catalogue.js +1 -1
  66. package/dist/strings/index.d.ts +12 -18
  67. package/dist/strings/index.js +2 -2
  68. package/dist/style-clipboard/index.d.ts +1 -1
  69. package/dist/style-clipboard/index.js +1 -1
  70. package/dist/text-styles/index.d.ts +328 -0
  71. package/dist/text-styles/index.js +38 -0
  72. package/dist/text-styles/text-styles.css +309 -0
  73. package/dist/tree/index.d.ts +43 -2
  74. package/dist/tree/index.js +1 -1
  75. package/dist/tree/tree.css +71 -0
  76. package/dist/{types-p9j2Fac7.d.ts → types-DGcCsxs8.d.ts} +72 -7
  77. package/dist/{types-Bs_gVkVI.d.ts → types-DWS-s9z-.d.ts} +1 -9
  78. package/dist/{types-DO-b0LNj.d.ts → types-Djdq6Xkd.d.ts} +9 -8
  79. package/dist/{types-BOSGNvil.d.ts → types-E3HrTPg2.d.ts} +7 -13
  80. package/dist/{types-C7HFvdtk.d.ts → types-VqL6t5HH.d.ts} +82 -5
  81. package/dist/{watch-B5RTR-WX.d.ts → watch-BTYOJpQU.d.ts} +18 -1
  82. package/dist/{words-DagJdwIA.d.ts → words-AHHfiaRY.d.ts} +31 -2
  83. package/dist/words-BGrHZAJJ.d.ts +46 -0
  84. package/dist/{words-c-3h9xQQ.d.ts → words-BMzV04b1.d.ts} +1 -1
  85. package/dist/words-CRYvAiHX.d.ts +105 -0
  86. package/dist/words-D8kf071T.d.ts +93 -0
  87. package/dist/{words-N4ebT88_.d.ts → words-DgHcY5qB.d.ts} +9 -3
  88. package/dist/{words-C74EUpFr.d.ts → words-XH0TvZPg.d.ts} +60 -4
  89. package/package.json +8 -8
  90. package/dist/chunk-66CKSTXP.js +0 -14
  91. package/dist/chunk-672FAEKU.js +0 -1
  92. package/dist/chunk-67VZRQLG.js +0 -13
  93. package/dist/chunk-6PS6STGA.js +0 -1
  94. package/dist/chunk-7A7O2JWQ.js +0 -1
  95. package/dist/chunk-7JTL3P6P.js +0 -1
  96. package/dist/chunk-F2PIG3AA.js +0 -10
  97. package/dist/chunk-H4B72XCJ.js +0 -1
  98. package/dist/chunk-KDIVA6WD.js +0 -1
  99. package/dist/chunk-MCLBHENY.js +0 -1
  100. package/dist/chunk-QB3BZIRA.js +0 -5
  101. package/dist/chunk-SJQG5ENJ.js +0 -1
  102. package/dist/chunk-SUO5627P.js +0 -1
  103. package/dist/chunk-XUE5O62R.js +0 -3
  104. package/dist/chunk-ZT7M24XD.js +0 -1
  105. package/dist/editor/index.d.ts +0 -10
  106. package/dist/editor/index.js +0 -1
  107. package/dist/words-CD3WADyb.d.ts +0 -51
  108. /package/dist/{chunk-2PHD4L6A.js → chunk-OWBO4WKA.js} +0 -0
@@ -35,8 +35,13 @@
35
35
  border-color: var(--es-accent);
36
36
  box-shadow: 0 0 0 3px var(--es-accent-ring);
37
37
  }
38
+ /* A value nobody may change is still a value somebody has to READ: the grey
39
+ ground says the state, and the ink stays legible on it. It was `--es-muted`,
40
+ 4.39:1 on this ground at 12px — a hair under the 4.5:1 WCAG AA asks (second
41
+ host, axe-core, 2026-10-07). The number cell's read-only state below is the
42
+ same rule. Held by ratio in tests/contrast-small-text.test.ts. */
38
43
  .es-fin[readonly],
39
- .es-fin:disabled { background: var(--es-track-bg); color: var(--es-muted); }
44
+ .es-fin:disabled { background: var(--es-track-bg); color: var(--es-text-secondary); }
40
45
 
41
46
  .es-fin--area {
42
47
  height: auto;
@@ -75,7 +80,7 @@
75
80
  border-color: var(--es-focus-line, #60a5fa);
76
81
  box-shadow: 0 0 0 2px var(--es-focus-ring, rgba(96, 165, 250, 0.3));
77
82
  }
78
- .es-fnum.is-readonly { background: var(--es-track-bg); color: var(--es-muted); }
83
+ .es-fnum.is-readonly { background: var(--es-track-bg); color: var(--es-text-secondary); }
79
84
 
80
85
  .es-fnum-icon { display: flex; align-items: center; flex: none; color: var(--es-field-placeholder); }
81
86
  .es-fnum-icon svg { width: 14px; height: 14px; }
@@ -239,6 +244,90 @@
239
244
  .es-fsel-option.is-active { background: var(--es-hover); }
240
245
  .es-fsel-option.is-chosen { color: var(--es-accent-text); font-weight: 600; }
241
246
  .es-fsel-option[aria-disabled='true'] { color: var(--es-disabled); cursor: default; }
247
+ /* A heading over a run of options (`FieldOption.group`): a quiet label above
248
+ its options, and not an option itself — no hover, no pointer. */
249
+ .es-fsel-group-list { margin: 0; padding: 0; list-style: none; }
250
+ .es-fsel-group + .es-fsel-group,
251
+ .es-fsel-option + .es-fsel-group { margin-top: 4px; }
252
+ .es-fsel-group-head {
253
+ padding: 8px 8px 4px;
254
+ font-size: 11px;
255
+ font-weight: 600;
256
+ color: var(--es-muted);
257
+ cursor: default;
258
+ }
259
+ .es-fsel-group-count { margin-left: 4px; font-weight: 400; }
260
+
261
+ /* The editor's one pop-up list (card [MERGE] One Pop-up List For Editor And
262
+ One For Shoppers): what the website editor's own lists drew, here once.
263
+ A row with more than its label lays its parts out in a line. */
264
+ .es-fsel-option--rich { display: flex; align-items: center; gap: 8px; }
265
+ .es-fsel-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
266
+ .es-fsel-meta { flex: none; font-size: 11px; font-weight: 400; color: var(--es-muted); }
267
+ .es-fsel-thumb {
268
+ flex: none;
269
+ width: 26px;
270
+ height: 26px;
271
+ overflow: hidden;
272
+ border-radius: var(--es-field-radius);
273
+ background: var(--es-track-bg);
274
+ }
275
+ .es-fsel-thumb img { display: block; width: 100%; height: 100%; object-fit: cover; }
276
+ /* `multiple`: a tick box per row, drawn (the row is the option, not an <input>). */
277
+ .es-fsel-tick {
278
+ flex: none;
279
+ position: relative;
280
+ box-sizing: border-box;
281
+ width: 14px;
282
+ height: 14px;
283
+ border: 1.5px solid var(--es-input-border);
284
+ border-radius: 3px;
285
+ background: var(--es-field-bg);
286
+ }
287
+ .es-fsel-tick.is-on { border-color: var(--es-accent); background: var(--es-accent); }
288
+ .es-fsel-tick.is-on::after {
289
+ content: '';
290
+ position: absolute;
291
+ left: 3.5px;
292
+ top: 0.5px;
293
+ width: 3.5px;
294
+ height: 7px;
295
+ border-right: 1.6px solid var(--es-on-accent);
296
+ border-bottom: 1.6px solid var(--es-on-accent);
297
+ transform: rotate(45deg);
298
+ }
299
+ /* A tick box says "on" itself, so a ticked row keeps the ordinary ink. */
300
+ .es-fsel-option--rich.is-chosen:has(.es-fsel-tick) { color: var(--es-text); font-weight: 400; }
301
+ /* `searchable`: the box and the list float together; the list inside is plain. */
302
+ .es-fsel-pop {
303
+ position: absolute;
304
+ z-index: 20;
305
+ top: calc(100% + 4px);
306
+ left: 0;
307
+ right: 0;
308
+ display: flex;
309
+ flex-direction: column;
310
+ gap: 4px;
311
+ padding: 4px;
312
+ border: 1px solid var(--es-line);
313
+ border-radius: var(--es-row-radius);
314
+ background: var(--es-raised);
315
+ box-shadow: var(--es-panel-shadow);
316
+ }
317
+ .es-fsel-pop .es-fsel-list {
318
+ position: static;
319
+ padding: 0;
320
+ border: 0;
321
+ border-radius: 0;
322
+ background: none;
323
+ box-shadow: none;
324
+ }
325
+ .es-fsel-empty { padding: 8px; font-size: 12px; color: var(--es-muted); }
326
+ /* `inline`: the list opens in flow and pushes the rows below it down, so a
327
+ panel that scrolls never clips it. */
328
+ .es-fsel--inline .es-fsel-list { position: static; margin-top: 4px; }
329
+ .es-fsel--inline .es-fsel-pop { position: static; margin-top: 4px; }
330
+ .es-fsel--inline .es-fsel-pop .es-fsel-list { margin-top: 0; }
242
331
 
243
332
  /* ── the panel body: the tab strip and the rows (Inspector) ───────────────── */
244
333
 
@@ -260,9 +349,13 @@
260
349
  .es-insp-row:empty,
261
350
  .es-insp-row[hidden] { display: none; }
262
351
 
263
- /* ── a list (ListField, 4.2): Layers' own rows, an add button, an open item ── */
352
+ /* ── a list (ListField): ONE look in both editors (owner, 2026-10-06) ────────
353
+ The website ticker's rows: one row per item, a × at its end, "+ Add <noun>"
354
+ under the last. The whole row drags (no grip); a many-field item opens under
355
+ its own row. Sizes from the ticker (.sf-textlist): a 32px row, a 28 × 32 ×.
356
+ Gaps are the spacing scale: 8 between rows (the seam IS the gap), 4 inside. */
264
357
 
265
- .es-list { display: flex; flex-direction: column; gap: 8px; min-width: 0; margin-top: max(var(--es-row-gap), var(--es-rhythm-between)); }
358
+ .es-list { position: relative; display: flex; flex-direction: column; gap: 8px; min-width: 0; margin-top: max(var(--es-row-gap), var(--es-rhythm-between)); }
266
359
  .es-list-head {
267
360
  display: flex;
268
361
  align-items: baseline;
@@ -270,52 +363,191 @@
270
363
  gap: 8px;
271
364
  }
272
365
  .es-list-label { display: inline-flex; align-items: center; gap: 8px; font-size: 12px; font-weight: 600; color: var(--es-text); }
273
- .es-list-count { font-size: 11px; color: var(--es-muted); }
274
- /* The rows are Layers' rows (tree.css); the list lets them reach the panel's
275
- edges the way Layers' do, so a row's hover reads as the same row. */
276
- .es-list-tree { margin: 0 -8px; }
366
+ .es-list-count { font-size: 11px; color: var(--es-muted); font-variant-numeric: tabular-nums; }
277
367
 
278
- .es-list-add {
368
+ /* A host's bar for the row in use, between the name and the rows. */
369
+ .es-list-above { min-width: 0; }
370
+
371
+ /* The rows: a column with no gap of its own, because the seams between rows
372
+ are the gap (tree.css `.es-tree-seam`, one rule for both). NOT a scroll box,
373
+ and not Layers' `.es-tree` (which is one): the panel around the list scrolls.
374
+ Inside a scroll box of its own the drag hint under the last row was clipped
375
+ and the list scrolled by itself under a wheel (second host, 2026-10-07). */
376
+ .es-list-rows { display: flex; flex-direction: column; min-width: 0; --es-rhythm-between: 8px; }
377
+ .es-list-rows > .es-tree-seam:last-child { margin-top: -8px; }
378
+ .es-list-entry { display: flex; flex-direction: column; min-width: 0; }
379
+
380
+ .es-list-row {
381
+ position: relative;
382
+ display: flex;
383
+ align-items: center;
384
+ gap: 4px;
385
+ min-width: 0;
386
+ min-height: 32px;
387
+ border-radius: var(--es-row-radius);
388
+ --es-row-scrim: var(--es-panel);
389
+ }
390
+ .es-list-row.is-draggable { cursor: grab; }
391
+ .es-list-row.is-item { padding-left: 8px; }
392
+ /* A row whose own control is not a text box (a name, a picture) reads as a row. */
393
+ .es-list-row.is-inline:not(:has(input, textarea, [contenteditable])) { padding-left: 8px; }
394
+ .es-list-row.is-inline:not(:has(input, textarea, [contenteditable])):hover { --es-row-scrim: var(--es-hover); background: var(--es-row-scrim); }
395
+ .es-list-row.is-item:hover,
396
+ .es-list-row.is-open { --es-row-scrim: var(--es-hover); background: var(--es-row-scrim); }
397
+ .es-list-row.is-dragging { opacity: 0.4; }
398
+ .es-list-row.is-lifting { background: var(--es-raised); box-shadow: 0 8px 20px rgba(16, 24, 40, 0.16), 0 2px 6px rgba(16, 24, 40, 0.1); }
399
+ .es-list-row:focus-visible { outline: 2px solid var(--es-accent); outline-offset: 1px; }
400
+ /* The one drop line, in the middle of the 8px gap — where a seam draws its own. */
401
+ .es-list-row.is-drop-before::before,
402
+ .es-list-row.is-drop-after::after {
403
+ content: "";
404
+ position: absolute;
405
+ left: 8px;
406
+ right: 8px;
407
+ height: 2px;
408
+ border-radius: 2px;
409
+ background: var(--es-accent);
410
+ pointer-events: none;
411
+ }
412
+ .es-list-row.is-drop-before::before { top: -5px; }
413
+ .es-list-row.is-drop-after::after { bottom: -5px; }
414
+
415
+ /* The row's own control (renderRow): it fills the row up to the ×. */
416
+ .es-list-row-main { flex: 1 1 auto; min-width: 0; min-height: 32px; display: flex; align-items: center; font-size: 12px; color: var(--es-text); }
417
+ .es-list-row-main > * { flex: 1 1 auto; min-width: 0; }
418
+
419
+ /* A many-field item: its name, its summary, a chevron that turns when open. */
420
+ .es-list-open {
421
+ flex: 1 1 auto;
422
+ min-width: 0;
423
+ display: flex;
424
+ align-items: center;
425
+ gap: 4px;
279
426
  height: 32px;
280
- border: 1px dashed var(--es-btn-border);
281
- border-radius: var(--es-field-radius);
427
+ padding: 0;
428
+ border: 0;
282
429
  background: none;
283
- color: var(--es-text-secondary);
430
+ color: var(--es-text);
284
431
  font: inherit;
285
432
  font-size: 12px;
286
- cursor: pointer;
433
+ text-align: left;
434
+ cursor: inherit;
287
435
  }
288
- .es-list-add:hover:not(:disabled) { background: var(--es-hover); }
289
- .es-list-add:disabled { color: var(--es-disabled); cursor: default; }
290
- .es-list-add:focus-visible { outline: 2px solid var(--es-accent); outline-offset: 1px; }
436
+ .es-list-open:focus-visible { outline: 2px solid var(--es-accent); outline-offset: 1px; border-radius: 4px; }
437
+ .es-list-thumb { flex: none; display: inline-flex; align-items: center; }
438
+ .es-list-row-name { flex: none; font-weight: 600; white-space: nowrap; }
439
+ .es-list-row-summary { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--es-muted); }
440
+ .es-list-chev { flex: none; color: var(--es-icon); transition: transform 0.12s; }
441
+ .es-list-row.is-open .es-list-chev { transform: rotate(90deg); }
291
442
 
292
- /* An item opened: the way back, its name, its own settings, its two actions. */
293
- .es-list.is-open { gap: 12px; }
294
- .es-list-back {
295
- align-self: flex-start;
443
+ .es-list-x {
444
+ flex: none;
445
+ width: 28px;
446
+ height: 32px;
447
+ border: 0;
448
+ border-radius: var(--es-row-radius);
449
+ background: transparent;
450
+ color: var(--es-muted);
451
+ font: inherit;
452
+ font-size: 16px;
453
+ line-height: 1;
454
+ cursor: pointer;
455
+ }
456
+ .es-list-x:hover:not(:disabled) { background: var(--es-danger-tint); color: var(--es-danger); }
457
+ .es-list-x:disabled { color: var(--es-disabled); cursor: default; }
458
+ .es-list-x:focus-visible { outline: 2px solid var(--es-accent); outline-offset: 2px; }
459
+ .es-list-lock {
460
+ flex: none;
461
+ width: 28px;
462
+ height: 32px;
296
463
  display: inline-flex;
297
464
  align-items: center;
298
- gap: 8px;
299
- height: 28px;
300
- padding: 0 8px;
465
+ justify-content: center;
466
+ color: var(--es-disabled);
467
+ }
468
+
469
+ /* What a draggable row says after 0.7 s on it — Layers' hint, never over a text box. */
470
+ .es-list-hint {
471
+ position: absolute;
472
+ left: 8px;
473
+ top: calc(100% + 4px);
474
+ z-index: 5;
475
+ padding: 4px 8px;
476
+ border-radius: 6px;
477
+ background: var(--es-toast-bg);
478
+ color: var(--es-on-accent);
479
+ font-size: 11px;
480
+ line-height: 1.3;
481
+ white-space: nowrap;
482
+ pointer-events: none;
483
+ opacity: 0;
484
+ transition: opacity 0.1s;
485
+ }
486
+ .es-list-row.is-draggable:hover:not(:has(input:hover, textarea:hover, [contenteditable]:hover)) .es-list-hint {
487
+ opacity: 1;
488
+ transition-delay: 0.7s;
489
+ }
490
+ .es-list-row.is-dragging .es-list-hint,
491
+ .es-list-row.is-lifting .es-list-hint { display: none; }
492
+
493
+ /* The open item, under its row: its own settings, indented on a hairline. */
494
+ .es-list-item {
495
+ display: flex;
496
+ flex-direction: column;
497
+ min-width: 0;
498
+ margin: 4px 0 8px 8px;
499
+ padding: 0 8px 4px 12px;
500
+ border-left: 2px solid var(--es-line);
501
+ }
502
+ .es-list-item > :first-child { margin-top: 0; }
503
+
504
+ .es-list-add {
505
+ align-self: flex-start;
506
+ padding: 4px 2px;
301
507
  border: 0;
302
- border-radius: var(--es-field-radius);
303
- background: var(--es-hover);
304
- color: var(--es-text-secondary);
508
+ background: none;
509
+ color: var(--es-accent);
305
510
  font: inherit;
306
511
  font-size: 12px;
512
+ font-weight: 500;
307
513
  cursor: pointer;
308
514
  }
309
- .es-list-back:focus-visible { outline: 2px solid var(--es-accent); outline-offset: 1px; }
310
- .es-list-back-chev {
311
- width: 6px;
312
- height: 6px;
313
- border-left: 1.6px solid currentColor;
314
- border-bottom: 1.6px solid currentColor;
315
- transform: rotate(45deg);
316
- }
317
- .es-list-title { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
318
- .es-list-name { font-size: 14px; font-weight: 600; color: var(--es-text); }
319
- .es-list-item { min-width: 0; }
320
- .es-list-item-actions { display: flex; gap: 8px; margin-top: 8px; }
515
+ .es-list-add:hover:not(:disabled) { text-decoration: underline; }
516
+ .es-list-add:disabled { color: var(--es-disabled); cursor: default; }
517
+ .es-list-add:focus-visible { outline: 2px solid var(--es-accent); outline-offset: 2px; }
518
+
519
+ /* Read out after a move; never seen. */
520
+ .es-list-said { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
521
+
321
522
  .es-fbtn--danger { color: var(--es-danger); }
523
+
524
+ /* ── the link picker (LinkPicker, [MERGE] One Link Picker For Both Editors) ──
525
+ The mockup's stack (owner, 2026-10-06): Link type, the target, and on the
526
+ listing Filter and Value — one box under the next, the whole width of the
527
+ row, 8px apart (the spacing scale). The same stack at 320px and at 220px:
528
+ nothing beside anything, so nothing is cut off mid-word in the narrow
529
+ panel. Every box is `FieldSelect` or `.es-fin`; this adds only the stack and
530
+ the two quiet lines under it. */
531
+ .es-flink {
532
+ display: flex;
533
+ flex-direction: column;
534
+ gap: 8px;
535
+ min-width: 0;
536
+ }
537
+ /* Inside a SettingsField the slot is a flex ROW, and a column box written for
538
+ a block row sizes to its own content there (291px became 117px in the shop
539
+ when its link stack sat in the slot). Fill the slot instead. */
540
+ .es-field-ctl > .es-flink {
541
+ flex: 1 1 auto;
542
+ }
543
+ .es-flink > .es-fin[aria-invalid='true'] {
544
+ border-color: var(--es-danger);
545
+ }
546
+ .es-flink-error,
547
+ .es-flink-hint {
548
+ margin: 0;
549
+ font-size: 11px;
550
+ line-height: 1.45;
551
+ }
552
+ .es-flink-error { color: var(--es-danger); }
553
+ .es-flink-hint { color: var(--es-muted); }
@@ -1,10 +1,10 @@
1
1
  import * as react from 'react';
2
2
  import { ReactNode, KeyboardEvent } from 'react';
3
3
  import { S as SettingsBucket } from '../buckets-CuLM47h9.js';
4
- import { I as InspectorFields, a as InspectorFieldDef, F as FieldRenderer, b as FieldTextProps, c as FieldNumberProps, d as FieldSliderProps, e as FieldSelectProps, f as FieldOption } from '../types-C7HFvdtk.js';
5
- export { A as ArrayFieldDef, C as CustomFieldDef, g as CustomFieldRenderProps, N as NumberFieldDef, O as ObjectFieldDef, R as RadioFieldDef, S as SelectFieldDef, h as SlotFieldDef, T as TextFieldDef, i as TextareaFieldDef } from '../types-C7HFvdtk.js';
4
+ import { I as InspectorFields, a as InspectorFieldDef, F as FieldRenderer, b as FieldTextProps, c as FieldNumberProps, d as FieldSliderProps, e as FieldSelectManyProps, f as FieldSelectOneProps, g as FieldOption } from '../types-VqL6t5HH.js';
5
+ export { A as ArrayFieldDef, C as CustomFieldDef, h as CustomFieldRenderProps, i as FieldSelectProps, N as NumberFieldDef, O as ObjectFieldDef, R as RadioFieldDef, S as SelectFieldDef, j as SlotFieldDef, T as TextFieldDef, k as TextareaFieldDef } from '../types-VqL6t5HH.js';
6
6
  import { R as ResolvableItem } from '../resolve-D68pb3Bx.js';
7
- export { F as FieldsWords } from '../words-CD3WADyb.js';
7
+ export { F as FieldsWords } from '../words-D8kf071T.js';
8
8
  import '../types-41704JE4.js';
9
9
 
10
10
  interface InspectorProps {
@@ -98,7 +98,14 @@ interface PlannedRow {
98
98
  bucket: SettingsBucket;
99
99
  }
100
100
  interface FieldPlan {
101
- /** Every field that draws, in the order declared. */
101
+ /**
102
+ * Every field that is not a marker and not hidden, in the order declared.
103
+ *
104
+ * ⚠️ A `slot` field is among them and draws NOTHING: it is here so its name
105
+ * has a bucket (`bucketsOf`), and `Inspector` leaves it mounted but empty.
106
+ * A host that draws rows of its own, or counts them ("3 settings"), filters
107
+ * `row.field.type !== 'slot'` first. `buckets` already leaves slots out.
108
+ */
102
109
  rows: PlannedRow[];
103
110
  /** The tabs to offer: the buckets that hold a row, in the owner's order. */
104
111
  buckets: SettingsBucket[];
@@ -171,26 +178,37 @@ declare function useResolvedFields({ id, node, block, parent, appState }: Resolv
171
178
  };
172
179
 
173
180
  /**
174
- * A LIST VALUE (blueprint step 4.2) — slides, buttons, links, any `array`
175
- * field — drawn the way Layers draws a page: one row per item, the same rows,
176
- * the same drag (the whole row picks up), the same ⌥↑ / ⌥↓, and a "+" between
177
- * two rows that adds one there. Pressing a row opens that item in the panel,
178
- * with a way back at the top.
181
+ * A LIST VALUE — slides, buttons, links, lines of copy, any `array` field —
182
+ * drawn ONE way in both editors (owner, 2026-10-06: "the ticker look"). One row
183
+ * per item with a × at its end, and "+ Add <noun>" under the last row.
184
+ *
185
+ * one field `renderRow` puts the host's own control IN the row (the
186
+ * ticker's text box): nothing to open, the item is edited where
187
+ * it stands;
188
+ * many fields the row is the item's name and summary; pressing it opens the
189
+ * item IN PLACE, under its row, one at a time (the owner's pick
190
+ * A of the 2026-10-06 mockup — the drill-in page is gone).
179
191
  *
180
- * It replaces Puck's ArrayField, which drew its own look and ran its own drag.
181
- * THE DATA DOES NOT CHANGE: a list is the array Puck stored, and every write is
182
- * that array with an item added, moved, copied or taken out. No id is added to
183
- * an item (Puck's `_arrayId` lived in its UI state, never in the data, and so
184
- * does a row's key here — its position).
192
+ * REORDER IS NEVER CUT TO FIT THE LOOK. The WHOLE ROW drags — no grip (owner,
193
+ * 2026-09-29) — a text box in it too, until the caret is in that box: from
194
+ * then on a press there selects text. Alt+↑ /
195
+ * Alt+↓ move it from the keyboard, the caret in the text box included, and the
196
+ * caret goes with the item. The right-click menu (or Shift+F10) offers Move up,
197
+ * Move down, Duplicate and Delete, and only what the row can do. The count, the
198
+ * "+" between two rows and Duplicate stay, as Layers keeps them.
185
199
  *
186
- * CONTROLLED, both the list and which item is open: the Inspector holds them,
187
- * so an editor can keep the open item across a re-render of the panel.
200
+ * THE DATA DOES NOT CHANGE: a list is the array the document stored, and every
201
+ * write is that array with an item added, moved, copied, changed or taken out.
202
+ * No id is added to an item; a row's key is its position.
188
203
  *
189
- * Its words ("Add slide", "2 of 5", the rows' drag hint) are the strings
190
- * provider's — `EaselStrings.fields.list` and `.tree` — English with none.
204
+ * CONTROLLED, the list and which item is open: the Inspector holds them, so an
205
+ * editor can keep the open item across a re-render of the panel.
206
+ *
207
+ * Its words are the strings provider's — `EaselStrings.fields.list` and
208
+ * `.tree` — English with none.
191
209
  */
192
210
  interface ListFieldProps<T> {
193
- /** The list's name, e.g. "Slides". Also the back button's word. */
211
+ /** The list's name, e.g. "Slides". */
194
212
  label: string;
195
213
  /** A glyph before the name, as a settings row wears one. */
196
214
  icon?: ReactNode;
@@ -209,11 +227,37 @@ interface ListFieldProps<T> {
209
227
  min?: number;
210
228
  max?: number;
211
229
  readOnly?: boolean;
212
- /** The item open in the panel, by position, or null for the list. */
230
+ /** The item open under its row, by position, or null. */
213
231
  open: number | null;
214
232
  onOpen: (index: number | null) => void;
215
- /** The open item's own settings. `change` writes the item. */
233
+ /** The open item's own settings, drawn under its row. `change` writes the item. */
216
234
  renderItem: (item: T, index: number, change: (next: T) => void) => ReactNode;
235
+ /**
236
+ * The row's own control, for an item that is ONE value (a line of copy): the
237
+ * row holds it, and nothing opens. `change` writes the item.
238
+ */
239
+ renderRow?: (item: T, index: number, change: (next: T) => void) => ReactNode;
240
+ /** May this item be taken out? False: no ×, a lock, no Delete — it still moves (the email's Unsubscribe). */
241
+ canRemove?: (item: T, index: number) => boolean;
242
+ /** Why a locked item cannot be taken out — the lock's tooltip. Default `fields.list.locked`. */
243
+ lockedReason?: (item: T, index: number) => string;
244
+ /** A set that is never added to or taken from (the phone column order): it only moves. */
245
+ fixed?: boolean;
246
+ /**
247
+ * False: an item is deleted INSIDE (its own page, as a Locations group is),
248
+ * never from its row — no ×, no lock, no Delete in the menu. Default true.
249
+ */
250
+ removeOnRow?: boolean;
251
+ /** May this item be copied? False: no Duplicate in the menu (a copy that would merge with its original). Default: yes. */
252
+ canDuplicate?: (item: T, index: number) => boolean;
253
+ /** Drawn once, under the list's name and above its rows — a bar that acts on the row in use (the ticker's formatting bar). */
254
+ above?: ReactNode;
255
+ /**
256
+ * False: no head of the list's own — the host draws the list inside a
257
+ * settings row that already names it (a `SettingsField` with its glyph and
258
+ * hint), and shows the count in that row's `value`. Default true.
259
+ */
260
+ head?: boolean;
217
261
  }
218
262
  /** The list with `item` put at `index`. */
219
263
  declare function insertAt<T>(items: readonly T[], index: number, item: T): T[];
@@ -226,7 +270,7 @@ declare function removeAt<T>(items: readonly T[], index: number): T[];
226
270
  * writes through to the original (a list item is JSON — Puck stored it so).
227
271
  */
228
272
  declare function copyItem<T>(item: T): T;
229
- declare function ListField<T>({ label, icon, noun: ownNoun, items, onChange, newItem, summary, itemName, thumb, min, max, readOnly, open, onOpen, renderItem, }: ListFieldProps<T>): react.JSX.Element;
273
+ declare function ListField<T>({ label, icon, noun: ownNoun, items, onChange, newItem, summary, itemName, thumb, min, max, readOnly, open, onOpen, renderItem, renderRow, canRemove: removable, lockedReason, fixed, removeOnRow, canDuplicate, above, head, }: ListFieldProps<T>): react.JSX.Element;
230
274
 
231
275
  /**
232
276
  * A text value: the shell's own box, one line or several (blueprint step 4.1,
@@ -290,7 +334,7 @@ declare function useNumberEntry({ value, onChange, min, max, step, shiftStep, st
290
334
  * small stacked arrows. What the typing, the keys and the arrows do is
291
335
  * {@link useNumberEntry}'s.
292
336
  */
293
- declare function FieldNumber({ id, value, onChange, min, max, step, shiftStep, styleValue, icon, unit, steppers, placeholder, readOnly, ariaLabel, ariaLabelledBy, }: FieldNumberProps): react.JSX.Element;
337
+ declare function FieldNumber({ id, value, onChange, min, max, step, shiftStep, styleValue, icon, unit, steppers, placeholder, readOnly, ariaLabel, ariaLabelledBy, title, }: FieldNumberProps): react.JSX.Element;
294
338
 
295
339
  /**
296
340
  * A slider row (plan step E4), for Site styles and Email styles: the name on
@@ -300,8 +344,14 @@ declare function FieldNumber({ id, value, onChange, min, max, step, shiftStep, s
300
344
  * cell does — it is the same {@link useNumberEntry} — and may go past the
301
345
  * slider's own travel (`sliderMin` / `sliderMax`); only `min` / `max` hold it.
302
346
  * The slider then rests at its end. A grey value follows the style.
347
+ *
348
+ * `auto` (plan step E6, Site styles) is the word for "no number: each section
349
+ * draws its own". The design puts it ON the slider: one spot left of its
350
+ * travel is Auto, and the value reads the word, grey, with no unit. Dragged there, the row
351
+ * writes `undefined`; ↑ from Auto starts at `sliderMin` (one more than Auto is
352
+ * the slider's first number, not `min`), ↓ stays; an emptied box is Auto too.
303
353
  */
304
- declare function FieldSlider({ id, label, value, onChange, min, max, step, shiftStep, styleValue, unit, sliderMin, sliderMax, readOnly, }: FieldSliderProps): react.JSX.Element;
354
+ declare function FieldSlider({ id, label, value, onChange, min, max, step, shiftStep, styleValue, unit, sliderMin, sliderMax, auto, readOnly, }: FieldSliderProps): react.JSX.Element;
305
355
 
306
356
  /** The option a stored value names: equal by `Object.is`, so `0`, `false` and `''` are values too. */
307
357
  declare function optionFor<V>(options: readonly FieldOption<V>[], value: V | undefined): FieldOption<V> | undefined;
@@ -315,7 +365,140 @@ declare function optionFor<V>(options: readonly FieldOption<V>[], value: V | und
315
365
  * button its focus back, a letter jumps to the next option that starts with it.
316
366
  * A click outside closes it (a CLICK, not a pointerdown: closing early moves the
317
367
  * row and loses the press — the colour row learned that in 4.6).
368
+ *
369
+ * Options may sit under headings (`FieldOption.group`, `<optgroup>`'s job): a
370
+ * heading is a `role="group"`'s name, never an option, so the keys and a click
371
+ * pass it by. Without groups the list is exactly the flat one it always was.
372
+ *
373
+ * THE EDITOR'S ONE POP-UP LIST (card [MERGE] One Pop-up List For Editor And One
374
+ * For Shoppers, 2026-10-06). What the website editor's own lists did is here,
375
+ * each optional, and a list that asks for none draws what it always drew:
376
+ *
377
+ * searchable a search box at the top of the open list. It has the focus;
378
+ * letters go into it (no letter jump), ↓ ↑ walk what is left,
379
+ * Enter chooses, Escape closes. Home and End stay the box's.
380
+ * multiple several values. Enter, Space or a click ticks one and the list
381
+ * STAYS open; the save is in the options' order.
382
+ * thumb, meta a picture column and a quiet fact on the option.
383
+ * groupCount a heading says how many of its options are showing.
384
+ * renderOption / renderValue the host draws a row's or the button's content.
385
+ * inline the list opens in flow, for a panel that scrolls.
386
+ *
387
+ * THE OPEN LIST ALWAYS HAS A NAME (second host, 2026-10-07): the host's
388
+ * `ariaLabelledBy`, else its `ariaLabel`, else the BUTTON that opened it —
389
+ * which the row's `<label for>` names. Until then a list given neither was a
390
+ * `role="listbox"` with no accessible name (axe `aria-input-field-name`), and
391
+ * the engine's own settings row gave neither.
392
+ */
393
+ declare function FieldSelect<V>(props: FieldSelectManyProps<V>): ReactNode;
394
+ declare function FieldSelect<V>(props: FieldSelectOneProps<V>): ReactNode;
395
+
396
+ /** One thing a link can point at: the URL it writes and the name it shows. */
397
+ type LinkOption = {
398
+ value: string;
399
+ label: string;
400
+ };
401
+ /** Options under one heading — the anchors of one page. */
402
+ type LinkOptionGroup = {
403
+ label: string;
404
+ options: readonly LinkOption[];
405
+ };
406
+ /** One filter on a link onto the product listing, or none. */
407
+ type LinkFilter = {
408
+ param: string;
409
+ value: string;
410
+ } | null;
411
+ /** A filter a listing link may offer, with the values it may take. */
412
+ type LinkFilterField = {
413
+ param: string;
414
+ label: string;
415
+ values: readonly LinkOption[];
416
+ };
417
+ /** What a kind's target box is: a list, a list in groups, or a typed address. */
418
+ type LinkTarget = {
419
+ type: 'select';
420
+ options: readonly LinkOption[];
421
+ placeholder: string;
422
+ } | {
423
+ type: 'groups';
424
+ groups: readonly LinkOptionGroup[];
425
+ placeholder: string;
426
+ } | {
427
+ type: 'text';
428
+ };
429
+ /** One entry of the Link type list, with everything its target box offers. */
430
+ type LinkKindChoice = {
431
+ /** The kind's key — what `rules.kindOf` answers. */
432
+ value: string;
433
+ /** Its name in the list ("Page", "Blog post"), and its target box's name. */
434
+ label: string;
435
+ target: LinkTarget;
436
+ /** A quiet line under the boxes while this kind is chosen. */
437
+ hint?: string;
438
+ /** Left out of the list while its target has nothing to pick — unless the
439
+ * saved link is this kind, so a link is never shown as something it is not. */
440
+ hideWhenEmpty?: boolean;
441
+ };
442
+ /**
443
+ * Everything the picker needs to know about a URL — and it knows nothing else.
444
+ * A host hands in its own rules (the storefront's come from
445
+ * `efficient-web-builder/links`), so a new kind or a new filter never changes
446
+ * this file.
447
+ */
448
+ type LinkRules = {
449
+ /** Which kind a saved URL is. */
450
+ kindOf: (url: string) => string;
451
+ /** The target a URL points at, with any listing filter taken off. */
452
+ targetOf: (url: string) => string;
453
+ /** The filter a URL carries onto the listing, or null. */
454
+ readFilter: (url: string) => LinkFilter;
455
+ /** The URL of a target with this filter written on (or cleared). */
456
+ withFilter: (url: string, filter: LinkFilter) => string;
457
+ /** May this typed address be saved? */
458
+ isSafe: (url: string) => boolean;
459
+ };
460
+ type LinkPickerProps = {
461
+ /** The saved link, or `''`. */
462
+ value: string;
463
+ /**
464
+ * The new link, and — when a thing was PICKED — its name, so a host can fill
465
+ * its own label with it (the admin's menu item does). No name when the link
466
+ * was typed or cleared.
467
+ */
468
+ onChange: (url: string, name?: string) => void;
469
+ /** The Link type list, in order, each with what its target box offers. */
470
+ kinds: readonly LinkKindChoice[];
471
+ rules: LinkRules;
472
+ /**
473
+ * The filters a link onto this target may carry, or null when the target is
474
+ * not the product listing. Asked only for a kind whose target is a list.
475
+ */
476
+ filtersFor?: (target: string) => readonly LinkFilterField[] | null;
477
+ /** The whole control's accessible name — "Button link", "Where “Wheels” goes". */
478
+ label?: string;
479
+ readOnly?: boolean;
480
+ };
481
+ /**
482
+ * ONE LINK PICKER ([MERGE] One Link Picker For Both Editors, owner
483
+ * 2026-10-06; mockup https://claude.ai/artifact/6mVmY5787gnXdSWP3syqhu v4): a
484
+ * type — Page / Anchor / Blog post / Product / Custom URL — then which one; on
485
+ * the product listing a Filter and then a Value. The website editor's Link
486
+ * field and the admin's menu item draw this same control.
487
+ *
488
+ * IT HOLDS NO URL RULE. Which kind a link is, which page it points at, what
489
+ * filter it carries and how one is written, which typed address is unsafe —
490
+ * all of it is `rules`, and every option and every name is the host's. Only
491
+ * the order of the boxes and how they answer each other is decided here.
492
+ *
493
+ * It never writes on its own. Opening a saved link writes nothing; a target,
494
+ * filter or value the host no longer offers is shown as "(unavailable)" and
495
+ * kept, never silently replaced.
496
+ *
497
+ * Every list is `FieldSelect`, the editor's one pop-up list — the anchors
498
+ * under one heading per page (`FieldOption.group`), a long list with a search
499
+ * box — never the browser's `<select>`. The boxes stack, one under the next,
500
+ * at every panel width.
318
501
  */
319
- declare function FieldSelect<V>({ id, value, options, onChange, placeholder, readOnly, ariaLabel, ariaLabelledBy, }: FieldSelectProps<V>): react.JSX.Element;
502
+ declare function LinkPicker({ value, onChange, kinds, rules, filtersFor, label, readOnly }: LinkPickerProps): react.JSX.Element | null;
320
503
 
321
- export { FieldNumber, FieldNumberProps, FieldOption, type FieldPlan, FieldRenderer, FieldSelect, FieldSelectProps, FieldSlider, FieldSliderProps, FieldText, FieldTextProps, type FieldsBlock, Inspector, InspectorField, InspectorFieldDef, type InspectorFieldProps, InspectorFields, type InspectorProps, ListField, type ListFieldProps, type NumberEntryOptions, type PlannedRow, type ResolvedFieldsInput, SettingsBucket, bucketsOf, clampNumber, copyItem, insertAt, labelOf, moveTo, optionFor, parseTyped, planFields, removeAt, useNumberEntry, useResolvedFields };
504
+ export { FieldNumber, FieldNumberProps, FieldOption, type FieldPlan, FieldRenderer, FieldSelect, FieldSelectManyProps, FieldSelectOneProps, FieldSlider, FieldSliderProps, FieldText, FieldTextProps, type FieldsBlock, Inspector, InspectorField, InspectorFieldDef, type InspectorFieldProps, InspectorFields, type InspectorProps, type LinkFilter, type LinkFilterField, type LinkKindChoice, type LinkOption, type LinkOptionGroup, LinkPicker, type LinkPickerProps, type LinkRules, type LinkTarget, ListField, type ListFieldProps, type NumberEntryOptions, type PlannedRow, type ResolvedFieldsInput, SettingsBucket, bucketsOf, clampNumber, copyItem, insertAt, labelOf, moveTo, optionFor, parseTyped, planFields, removeAt, useNumberEntry, useResolvedFields };