@kerfjs/ui 5.0.0-beta.53 → 5.0.0-beta.54

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 (69) hide show
  1. package/README.md +1 -0
  2. package/ai/application-ui-diagnostic-ids-v1.json +1 -0
  3. package/ai/component-catalog-v2.json +82 -6
  4. package/ai/component-catalog-v2.schema.json +2 -1
  5. package/ai/component-catalog.json +105 -10
  6. package/ai/public-api-signatures-v1.md +204 -18
  7. package/ai/skill.md +23 -17
  8. package/ai/webawesome-jsx-signatures-v1.md +1 -1
  9. package/analyzer/index.mjs +48 -0
  10. package/analyzer/loud-pairs.d.mts +49 -0
  11. package/analyzer/loud-pairs.mjs +357 -0
  12. package/dist/app-tab.d.ts +7 -5
  13. package/dist/app-tab.js +1 -1
  14. package/dist/{chunk-EKYZCRCX.js → chunk-3CGQ7ZNO.js} +54 -22
  15. package/dist/chunk-3CGQ7ZNO.js.map +1 -0
  16. package/dist/{chunk-UKKSMZYT.js → chunk-46263QLK.js} +51 -1
  17. package/dist/chunk-46263QLK.js.map +1 -0
  18. package/dist/{chunk-NDUGJ4TU.js → chunk-5NDQABWK.js} +1 -1
  19. package/dist/{chunk-NDUGJ4TU.js.map → chunk-5NDQABWK.js.map} +1 -1
  20. package/dist/{chunk-CCMKJWLX.js → chunk-NE5JAOQS.js} +7 -7
  21. package/dist/chunk-NE5JAOQS.js.map +1 -0
  22. package/dist/chunk-PSHUSWQH.js +18 -0
  23. package/dist/chunk-PSHUSWQH.js.map +1 -0
  24. package/dist/{chunk-VOCPXGQN.js → chunk-VHADGP3B.js} +9 -8
  25. package/dist/chunk-VHADGP3B.js.map +1 -0
  26. package/dist/chunk-XQV2B4IL.js +14 -0
  27. package/dist/chunk-XQV2B4IL.js.map +1 -0
  28. package/dist/collapsible-panel.d.ts +6 -2
  29. package/dist/collapsible-panel.js +1 -0
  30. package/dist/collapsible-panel.js.map +1 -1
  31. package/dist/floating-toolbar.d.ts +5 -2
  32. package/dist/floating-toolbar.js +1 -1
  33. package/dist/index.js +6 -5
  34. package/dist/resizable-region.d.ts +4 -1
  35. package/dist/resizable-region.js +1 -1
  36. package/dist/split-view.js +1 -1
  37. package/dist/styles/collapsible-panel.css +88 -10
  38. package/dist/styles/floating-toolbar.css +11 -6
  39. package/dist/styles/resizable-region.css +81 -13
  40. package/dist/styles/workbench.css +472 -10
  41. package/dist/wire-resizable-regions.d.ts +2 -1
  42. package/dist/wire-resizable-regions.js +2 -1
  43. package/dist/wire-tab-bars.js +1 -1
  44. package/dist/wire-workbench.d.ts +109 -0
  45. package/dist/wire-workbench.js +355 -0
  46. package/dist/wire-workbench.js.map +1 -0
  47. package/dist/workbench.d.ts +65 -6
  48. package/dist/workbench.js +180 -76
  49. package/dist/workbench.js.map +1 -1
  50. package/docs/accessibility.md +54 -9
  51. package/docs/app-layouts.md +12 -1
  52. package/docs/collapsible-panel.md +59 -1
  53. package/docs/component-selection.md +16 -6
  54. package/docs/document-baseline.md +4 -0
  55. package/docs/layout.md +5 -1
  56. package/docs/recipes.md +5 -3
  57. package/docs/type-contracts.md +2 -1
  58. package/docs/ui-analyzer.md +94 -19
  59. package/docs/ui-doctor.md +6 -0
  60. package/docs/ux-demo.md +1 -1
  61. package/docs/webawesome-theme.md +11 -2
  62. package/docs/workbench.md +302 -7
  63. package/llms.txt +7 -3
  64. package/package.json +5 -1
  65. package/ux-demo/recipes/collapsible-sidebar.tsx +8 -7
  66. package/dist/chunk-CCMKJWLX.js.map +0 -1
  67. package/dist/chunk-EKYZCRCX.js.map +0 -1
  68. package/dist/chunk-UKKSMZYT.js.map +0 -1
  69. package/dist/chunk-VOCPXGQN.js.map +0 -1
@@ -1,5 +1,8 @@
1
1
  .kui-workbench {
2
2
  position: relative;
3
+ /* The rail and drawer overlays and the restore controls stack within the
4
+ Workbench, never against the page around it. */
5
+ isolation: isolate;
3
6
  display: flex;
4
7
  min-width: 0;
5
8
  min-height: 0;
@@ -39,6 +42,9 @@
39
42
  var(--kui-workbench-rail-width, 17.5rem) +
40
43
  var(--_kui-workbench-rail-safe-inset)
41
44
  );
45
+ /* The size is the whole track, separator border included, in or out of
46
+ flow and whatever box model the app defaults to. */
47
+ box-sizing: border-box;
42
48
  flex: 0 0 var(--_kui-workbench-rail-extent);
43
49
  min-width: 0;
44
50
  overflow: hidden;
@@ -69,7 +75,10 @@
69
75
  border-color: transparent;
70
76
  }
71
77
 
78
+ /* The center is the overlay drawer's containing block, so a drawer that
79
+ leaves the flow still covers exactly the work-area column it docks under. */
72
80
  .kui-workbench__center {
81
+ position: relative;
73
82
  --kui-edge-inset-block-start: var(--_kui-workbench-safe-block-start);
74
83
  --kui-edge-inset-block-end: var(--_kui-workbench-safe-block-end);
75
84
  --kui-edge-inset-inline-start: var(--_kui-workbench-safe-inline-start);
@@ -185,6 +194,7 @@
185
194
  var(--kui-workbench-drawer-height, 13.75rem) +
186
195
  var(--_kui-workbench-safe-block-end)
187
196
  );
197
+ box-sizing: border-box;
188
198
  position: relative;
189
199
  flex: 0 0 var(--_kui-workbench-drawer-extent);
190
200
  min-height: 0;
@@ -253,16 +263,197 @@
253
263
  transform: translateY(100%);
254
264
  }
255
265
 
266
+ /* Opt-in resizable panels. The size lives in the resize wiring's track
267
+ property, which the wiring updates live during a drag, so the rail width or
268
+ drawer height follows it; collapsing leaves it alone, so the panel expands
269
+ back to the size it had. These rules precede the presentation rules below,
270
+ which must keep winning for overlay and hidden panels. */
271
+ .kui-workbench__rail[data-resizable="true"] {
272
+ --kui-workbench-rail-width: var(--kui-resizable-region-size);
273
+ position: relative;
274
+ }
275
+
276
+ .kui-workbench__drawer[data-resizable="true"] {
277
+ --kui-workbench-drawer-height: var(--kui-resizable-region-size);
278
+ }
279
+
280
+ /* The work area keeps a minimum width beside the rails (the Workbench's
281
+ `mainMinSize`) and a minimum height above the drawer (`mainMinHeight`),
282
+ never larger than the Workbench itself. Inline panels, fixed or resizable,
283
+ give way instead: when the container narrows the rails shrink in proportion
284
+ to their sizes, when it gets shorter the drawer shrinks, and their content
285
+ follows the shown size. The wiring stops a resize where the work area would
286
+ drop below the minimum. */
287
+ .kui-workbench[data-main-min-size] > .kui-workbench__center {
288
+ min-width: min(var(--_kui-workbench-main-min-width), 100%);
289
+ }
290
+
291
+ .kui-workbench[data-main-min-height]
292
+ > .kui-workbench__center
293
+ > .kui-workbench__main {
294
+ min-height: min(var(--_kui-workbench-main-min-height), 100%);
295
+ }
296
+
297
+ .kui-workbench__rail[data-presentation="inline"]:not([data-collapsed="true"]),
298
+ .kui-workbench__drawer[data-presentation="inline"]:not(
299
+ [data-collapsed="true"]
300
+ ) {
301
+ flex-shrink: 1;
302
+ }
303
+
304
+ /* Low specificity, so a responsive overlay (below) restores the fixed size. */
305
+ .kui-workbench__rail:where(
306
+ [data-presentation="inline"]:not([data-collapsed="true"])
307
+ )
308
+ > .kui-workbench__panel-content {
309
+ width: 100%;
310
+ }
311
+
312
+ .kui-workbench__drawer:where(
313
+ [data-presentation="inline"]:not([data-collapsed="true"])
314
+ )
315
+ > .kui-workbench__panel-content {
316
+ height: 100%;
317
+ }
318
+
319
+ /* The panel clips its sliding content, so a scrolling panel scrolls its
320
+ content box instead of itself: the separator handle is a child of the panel
321
+ and must not scroll away with the content. */
322
+ .kui-workbench__rail[data-resizable="true"][data-content-overflow="auto"],
323
+ .kui-workbench__drawer[data-resizable="true"][data-content-overflow="auto"] {
324
+ overflow: hidden;
325
+ }
326
+
327
+ .kui-workbench__rail[data-resizable="true"][data-content-overflow="auto"]
328
+ > .kui-workbench__panel-content,
329
+ .kui-workbench__drawer[data-resizable="true"][data-content-overflow="auto"]
330
+ > .kui-workbench__panel-content {
331
+ overflow: auto;
332
+ }
333
+
334
+ /* The separator handle: the 20px hit target and dormant grip a
335
+ ResizableRegion handle has. The panel's own border stays the visible
336
+ separator line. By default the target sits wholly inside the panel, at its
337
+ inner edge, because the panel clips its sliding content. */
338
+ .kui-workbench__handle {
339
+ position: absolute;
340
+ z-index: 3;
341
+ display: grid;
342
+ color: var(--kui-color-neutral-on-quiet);
343
+ place-items: center;
344
+ touch-action: none;
345
+ }
346
+
347
+ .kui-workbench__rail > .kui-workbench__handle {
348
+ inset-block: 0;
349
+ width: 20px;
350
+ cursor: col-resize;
351
+ }
352
+
353
+ .kui-workbench__rail--left > .kui-workbench__handle {
354
+ inset-inline-end: 0;
355
+ }
356
+
357
+ .kui-workbench__rail--right > .kui-workbench__handle {
358
+ inset-inline-start: 0;
359
+ }
360
+
361
+ .kui-workbench__drawer > .kui-workbench__handle {
362
+ inset-inline: 0;
363
+ inset-block-start: 0;
364
+ height: 20px;
365
+ cursor: row-resize;
366
+ }
367
+
368
+ .kui-workbench__rail[data-collapsed="true"] > .kui-workbench__handle,
369
+ .kui-workbench__drawer[data-collapsed="true"] > .kui-workbench__handle,
370
+ .kui-workbench__rail:not([data-presentation="inline"]) > .kui-workbench__handle,
371
+ .kui-workbench__drawer:not([data-presentation="inline"])
372
+ > .kui-workbench__handle {
373
+ display: none;
374
+ }
375
+
376
+ /* Where the engine can extend a clip past the panel's edge, an expanded
377
+ panel lets exactly the handle's outer half through, so the hit target
378
+ straddles the separator like a ResizableRegion handle does. Collapsing drops
379
+ the margin again before the content slides out. */
380
+ @supports (overflow-clip-margin: 10px) {
381
+ .kui-workbench__rail[data-resizable="true"]:not(
382
+ [data-collapsed="true"],
383
+ [data-content-overflow="visible"]
384
+ ),
385
+ .kui-workbench__drawer[data-resizable="true"]:not(
386
+ [data-collapsed="true"],
387
+ [data-content-overflow="visible"]
388
+ ) {
389
+ overflow: clip;
390
+ overflow-clip-margin: 10px;
391
+ }
392
+
393
+ .kui-workbench__rail--left[data-resizable="true"] > .kui-workbench__handle {
394
+ inset-inline-end: -10px;
395
+ }
396
+
397
+ .kui-workbench__rail--right[data-resizable="true"] > .kui-workbench__handle {
398
+ inset-inline-start: -10px;
399
+ }
400
+
401
+ .kui-workbench__drawer[data-resizable="true"] > .kui-workbench__handle {
402
+ inset-block-start: -10px;
403
+ }
404
+ }
405
+
406
+ .kui-workbench__handle:hover,
407
+ .kui-workbench__handle:focus-visible {
408
+ color: var(--kui-color-text-link);
409
+ }
410
+
411
+ /* The ring stays wholly inside the handle's box, as a ResizableRegion
412
+ handle's does. */
413
+ .kui-workbench__handle:focus-visible {
414
+ border-radius: var(--kui-radius-s);
415
+ outline: var(--kui-focus-ring);
416
+ outline-offset: -3px;
417
+ }
418
+
419
+ .kui-workbench__handle-icon {
420
+ position: relative;
421
+ display: grid;
422
+ width: 0.9rem;
423
+ height: 0.9rem;
424
+ opacity: 0;
425
+ pointer-events: none;
426
+ place-items: center;
427
+ }
428
+
429
+ .kui-workbench__handle-icon > svg {
430
+ width: 100%;
431
+ height: 100%;
432
+ }
433
+
434
+ .kui-workbench__handle:hover .kui-workbench__handle-icon,
435
+ .kui-workbench__handle:focus-visible .kui-workbench__handle-icon {
436
+ opacity: 1;
437
+ }
438
+
439
+ /* Overlay stacking order: both rails stack above the bottom drawer, one below
440
+ the overlay z-index, as inline rails span the Workbench's full height beside
441
+ the drawer's column; the right rail stacks above the left (document order).
442
+ Restore controls stack below every overlay (see .kui-workbench__restore). */
256
443
  .kui-workbench__rail[data-presentation="overlay"],
257
444
  .kui-workbench__drawer[data-presentation="overlay"] {
258
445
  position: absolute;
259
446
  z-index: var(--kui-workbench-overlay-z, 41);
260
- max-width: var(--kui-workbench-overlay-max-width, 85vw);
261
- max-height: var(--kui-workbench-overlay-max-height, 85vh);
262
447
  box-shadow: var(--kui-shadow-l);
263
448
  }
264
449
 
450
+ /* An out-of-flow rail sizes to its content plus its border, so it takes its
451
+ extent explicitly, as its in-flow flex basis does, border included. Each
452
+ overlay maximum caps only the panel's own axis: a rail spans the
453
+ Workbench's full height and a drawer its full width. */
265
454
  .kui-workbench__rail[data-presentation="overlay"] {
455
+ width: var(--_kui-workbench-rail-extent);
456
+ max-width: var(--kui-workbench-overlay-max-width, 85vw);
266
457
  inset-block: 0;
267
458
  }
268
459
 
@@ -274,41 +465,312 @@
274
465
  inset-inline-end: 0;
275
466
  }
276
467
 
468
+ /* The drawer's content is absolutely positioned against its bottom edge, so
469
+ an out-of-flow drawer has no content height of its own: it takes its extent
470
+ explicitly, as the in-flow track does through its flex basis. */
277
471
  .kui-workbench__drawer[data-presentation="overlay"] {
472
+ z-index: calc(var(--kui-workbench-overlay-z, 41) - 1);
473
+ height: var(--_kui-workbench-drawer-extent);
474
+ max-height: var(--kui-workbench-overlay-max-height, 85vh);
278
475
  inset-inline: 0;
279
476
  inset-block-end: 0;
280
477
  }
281
478
 
479
+ /* A collapsed overlay keeps its box for the slide-out, so it drops its surface
480
+ and shadow; the sliding content carries the surface instead. */
282
481
  .kui-workbench__rail[data-presentation="overlay"][data-collapsed="true"],
283
482
  .kui-workbench__drawer[data-presentation="overlay"][data-collapsed="true"] {
483
+ background: transparent;
484
+ box-shadow: none;
284
485
  pointer-events: none;
285
486
  }
286
487
 
488
+ /* Overlay content fills the rail inside its separator border, so it is
489
+ clamped with the rail and carries the surface through the slide-out. */
490
+ .kui-workbench__rail[data-presentation="overlay"]
491
+ > .kui-workbench__panel-content {
492
+ width: 100%;
493
+ background: var(--kui-color-surface);
494
+ }
495
+
496
+ /* The overlay content fills the drawer box below its separator border, so it
497
+ is clamped with the box and carries the surface through the slide-out. */
498
+ .kui-workbench__drawer[data-presentation="overlay"]
499
+ > .kui-workbench__panel-content {
500
+ height: 100%;
501
+ background: var(--kui-color-surface);
502
+ }
503
+
287
504
  .kui-workbench__rail[data-presentation="hidden"],
288
505
  .kui-workbench__drawer[data-presentation="hidden"] {
289
506
  display: none;
290
507
  }
291
508
 
509
+ /* Responsive overlay panels. A rail or the drawer with `responsiveOverlayAt`
510
+ presents as an overlay — out of flow, over the work area, without a
511
+ separator — below the Workbench's own container breakpoint (`narrow`: 704px
512
+ or less, `compact`: 448px or less, as `ResizableRegion`'s
513
+ `responsiveFillAt`), and inline above it. The Workbench becomes a named
514
+ inline-size container only when a panel opts in. The work area keeps its
515
+ safe-area inset under an overlay panel. */
516
+ .kui-workbench:has(
517
+ > .kui-workbench__rail[data-responsive-overlay-at],
518
+ > .kui-workbench__center > .kui-workbench__drawer[data-responsive-overlay-at]
519
+ ) {
520
+ container: kui-workbench / inline-size;
521
+ }
522
+
523
+ @container kui-workbench (max-width: 44rem) {
524
+ .kui-workbench__rail[data-responsive-overlay-at="narrow"] {
525
+ position: absolute;
526
+ z-index: var(--kui-workbench-overlay-z, 41);
527
+ width: var(--_kui-workbench-rail-extent);
528
+ max-width: var(--kui-workbench-overlay-max-width, 85vw);
529
+ inset-block: 0;
530
+ box-shadow: var(--kui-shadow-l);
531
+ }
532
+
533
+ .kui-workbench__rail--left[data-responsive-overlay-at="narrow"] {
534
+ inset-inline-start: 0;
535
+ }
536
+
537
+ .kui-workbench__rail--right[data-responsive-overlay-at="narrow"] {
538
+ inset-inline-end: 0;
539
+ }
540
+
541
+ .kui-workbench__rail[data-responsive-overlay-at="narrow"][data-collapsed="true"] {
542
+ background: transparent;
543
+ box-shadow: none;
544
+ pointer-events: none;
545
+ }
546
+
547
+ .kui-workbench__rail[data-responsive-overlay-at="narrow"]
548
+ > .kui-workbench__panel-content {
549
+ width: 100%;
550
+ background: var(--kui-color-surface);
551
+ }
552
+
553
+ .kui-workbench__rail[data-responsive-overlay-at="narrow"]
554
+ > .kui-workbench__handle {
555
+ display: none;
556
+ }
557
+
558
+ .kui-workbench:has(
559
+ > .kui-workbench__rail--left[data-responsive-overlay-at="narrow"][data-presentation="inline"]
560
+ )
561
+ > .kui-workbench__center {
562
+ --kui-edge-inset-inline-start: var(--_kui-workbench-safe-inline-start);
563
+ }
564
+
565
+ .kui-workbench:has(
566
+ > .kui-workbench__rail--right[data-responsive-overlay-at="narrow"][data-presentation="inline"]
567
+ )
568
+ > .kui-workbench__center {
569
+ --kui-edge-inset-inline-end: var(--_kui-workbench-safe-inline-end);
570
+ }
571
+
572
+ .kui-workbench__drawer[data-responsive-overlay-at="narrow"] {
573
+ position: absolute;
574
+ anchor-name: none;
575
+ z-index: calc(var(--kui-workbench-overlay-z, 41) - 1);
576
+ height: var(--_kui-workbench-drawer-extent);
577
+ max-height: var(--kui-workbench-overlay-max-height, 85vh);
578
+ inset-inline: 0;
579
+ inset-block-end: 0;
580
+ box-shadow: var(--kui-shadow-l);
581
+ }
582
+
583
+ .kui-workbench__drawer[data-responsive-overlay-at="narrow"][data-collapsed="true"] {
584
+ background: transparent;
585
+ box-shadow: none;
586
+ pointer-events: none;
587
+ }
588
+
589
+ .kui-workbench__drawer[data-responsive-overlay-at="narrow"]
590
+ > .kui-workbench__panel-content {
591
+ height: 100%;
592
+ background: var(--kui-color-surface);
593
+ }
594
+
595
+ .kui-workbench__drawer[data-responsive-overlay-at="narrow"]
596
+ > .kui-workbench__handle {
597
+ display: none;
598
+ }
599
+
600
+ .kui-workbench__main:has(
601
+ ~ .kui-workbench__drawer[data-responsive-overlay-at="narrow"][data-presentation="inline"]
602
+ ) {
603
+ --kui-edge-inset-block-end: var(--_kui-workbench-safe-block-end);
604
+ }
605
+ }
606
+
607
+ @container kui-workbench (max-width: 28rem) {
608
+ .kui-workbench__rail[data-responsive-overlay-at="compact"] {
609
+ position: absolute;
610
+ z-index: var(--kui-workbench-overlay-z, 41);
611
+ width: var(--_kui-workbench-rail-extent);
612
+ max-width: var(--kui-workbench-overlay-max-width, 85vw);
613
+ inset-block: 0;
614
+ box-shadow: var(--kui-shadow-l);
615
+ }
616
+
617
+ .kui-workbench__rail--left[data-responsive-overlay-at="compact"] {
618
+ inset-inline-start: 0;
619
+ }
620
+
621
+ .kui-workbench__rail--right[data-responsive-overlay-at="compact"] {
622
+ inset-inline-end: 0;
623
+ }
624
+
625
+ .kui-workbench__rail[data-responsive-overlay-at="compact"][data-collapsed="true"] {
626
+ background: transparent;
627
+ box-shadow: none;
628
+ pointer-events: none;
629
+ }
630
+
631
+ .kui-workbench__rail[data-responsive-overlay-at="compact"]
632
+ > .kui-workbench__panel-content {
633
+ width: 100%;
634
+ background: var(--kui-color-surface);
635
+ }
636
+
637
+ .kui-workbench__rail[data-responsive-overlay-at="compact"]
638
+ > .kui-workbench__handle {
639
+ display: none;
640
+ }
641
+
642
+ .kui-workbench:has(
643
+ > .kui-workbench__rail--left[data-responsive-overlay-at="compact"][data-presentation="inline"]
644
+ )
645
+ > .kui-workbench__center {
646
+ --kui-edge-inset-inline-start: var(--_kui-workbench-safe-inline-start);
647
+ }
648
+
649
+ .kui-workbench:has(
650
+ > .kui-workbench__rail--right[data-responsive-overlay-at="compact"][data-presentation="inline"]
651
+ )
652
+ > .kui-workbench__center {
653
+ --kui-edge-inset-inline-end: var(--_kui-workbench-safe-inline-end);
654
+ }
655
+
656
+ .kui-workbench__drawer[data-responsive-overlay-at="compact"] {
657
+ position: absolute;
658
+ anchor-name: none;
659
+ z-index: calc(var(--kui-workbench-overlay-z, 41) - 1);
660
+ height: var(--_kui-workbench-drawer-extent);
661
+ max-height: var(--kui-workbench-overlay-max-height, 85vh);
662
+ inset-inline: 0;
663
+ inset-block-end: 0;
664
+ box-shadow: var(--kui-shadow-l);
665
+ }
666
+
667
+ .kui-workbench__drawer[data-responsive-overlay-at="compact"][data-collapsed="true"] {
668
+ background: transparent;
669
+ box-shadow: none;
670
+ pointer-events: none;
671
+ }
672
+
673
+ .kui-workbench__drawer[data-responsive-overlay-at="compact"]
674
+ > .kui-workbench__panel-content {
675
+ height: 100%;
676
+ background: var(--kui-color-surface);
677
+ }
678
+
679
+ .kui-workbench__drawer[data-responsive-overlay-at="compact"]
680
+ > .kui-workbench__handle {
681
+ display: none;
682
+ }
683
+
684
+ .kui-workbench__main:has(
685
+ ~ .kui-workbench__drawer[data-responsive-overlay-at="compact"][data-presentation="inline"]
686
+ ) {
687
+ --kui-edge-inset-block-end: var(--_kui-workbench-safe-block-end);
688
+ }
689
+ }
690
+
691
+ /* A collapsed panel's restore control floats in a corner of the Workbench
692
+ itself, never of the viewport, so an embedded Workbench cannot float it over
693
+ page chrome outside it. It is inset from the corner plus the unsafe area of
694
+ each edge that corner reaches. The drawer's control sits in the work-area
695
+ column it restores (the center is its containing block), inset from the
696
+ edges that column still reaches, so it never lands on an expanded rail.
697
+ It belongs to the layer beneath the overlays, two below the overlay
698
+ z-index: an open overlay covers it as it covers the rest of the work area,
699
+ so no other panel's control floats over the overlay's content. A collapsed
700
+ overlay drops its pointer events, so the control stays usable beneath it. */
292
701
  .kui-workbench__restore {
293
- position: fixed;
294
- z-index: var(--kui-workbench-restore-z, 42);
702
+ --_kui-workbench-restore-inset: var(
703
+ --kui-workbench-restore-inset,
704
+ 1rem
705
+ );
706
+ /* The corner owns the inset, so a FloatingToolbar composed here floats
707
+ from this point instead of adding its own inset on top. */
708
+ --kui-floating-toolbar-inset: 0px;
709
+ position: absolute;
710
+ z-index: var(
711
+ --kui-workbench-restore-z,
712
+ calc(var(--kui-workbench-overlay-z, 41) - 2)
713
+ );
295
714
  inset-block-end: calc(
296
- var(--kui-workbench-restore-inset, 1rem) +
297
- var(--kui-safe-area-block-end, env(safe-area-inset-bottom, 0px))
715
+ var(--_kui-workbench-restore-inset) + var(--_kui-workbench-safe-block-end)
298
716
  );
299
717
  }
300
718
 
301
719
  .kui-workbench__restore[data-position="bottom-start"] {
302
720
  inset-inline-start: calc(
303
- var(--kui-workbench-restore-inset, 1rem) +
304
- var(--kui-safe-area-inline-start, env(safe-area-inset-left, 0px))
721
+ var(--_kui-workbench-restore-inset) +
722
+ var(--_kui-workbench-safe-inline-start)
305
723
  );
306
724
  }
307
725
 
308
726
  .kui-workbench__restore[data-position="bottom-end"] {
309
727
  inset-inline-end: calc(
310
- var(--kui-workbench-restore-inset, 1rem) +
311
- var(--kui-safe-area-inline-end, env(safe-area-inset-right, 0px))
728
+ var(--_kui-workbench-restore-inset) + var(--_kui-workbench-safe-inline-end)
729
+ );
730
+ }
731
+
732
+ /* An expanded inline bottom drawer owns the bottom of the work-area column,
733
+ which reaches the Workbench's bottom corners once a rail collapses, so a
734
+ collapsed rail's control floats the restore inset above the drawer's top
735
+ edge instead of over it — the standalone CollapsiblePanel rule, scoped to
736
+ this Workbench so a nested Workbench or an enclosing layout never sees its
737
+ drawer. The drawer publishes that edge only while it is expanded and in
738
+ flow: an overlay drawer, static or responsive (see the container queries
739
+ above), covers the control as every open overlay does. Without anchor
740
+ positioning the control keeps the Workbench corner. */
741
+ .kui-workbench {
742
+ anchor-scope: --kui-restore-drawer;
743
+ }
744
+
745
+ .kui-workbench__drawer:where(
746
+ [data-presentation="inline"]:not([data-collapsed="true"])
747
+ ) {
748
+ anchor-name: --kui-restore-drawer;
749
+ }
750
+
751
+ .kui-workbench > .kui-workbench__restore {
752
+ position-visibility: always;
753
+ inset-block-end: calc(
754
+ var(--_kui-workbench-restore-inset) +
755
+ anchor(--kui-restore-drawer top, var(--_kui-workbench-safe-block-end))
756
+ );
757
+ }
758
+
759
+ .kui-workbench__center > .kui-workbench__restore {
760
+ inset-block-end: calc(
761
+ var(--_kui-workbench-restore-inset) + var(--kui-edge-inset-block-end)
762
+ );
763
+ }
764
+
765
+ .kui-workbench__center > .kui-workbench__restore[data-position="bottom-start"] {
766
+ inset-inline-start: calc(
767
+ var(--_kui-workbench-restore-inset) + var(--kui-edge-inset-inline-start)
768
+ );
769
+ }
770
+
771
+ .kui-workbench__center > .kui-workbench__restore[data-position="bottom-end"] {
772
+ inset-inline-end: calc(
773
+ var(--_kui-workbench-restore-inset) + var(--kui-edge-inset-inline-end)
312
774
  );
313
775
  }
314
776
 
@@ -9,7 +9,8 @@ interface WireResizableRegionsOptions {
9
9
  onPreview?: (change: ResizeCommit) => void;
10
10
  onCommit: (change: ResizeCommit) => void;
11
11
  }
12
+
12
13
  /** Wire pointer and separator-keyboard behavior for every ResizableRegion below root. */
13
- declare function wireResizableRegions(root: HTMLElement, { step, largeStep, onPreview, onCommit, }: WireResizableRegionsOptions): () => void;
14
+ declare function wireResizableRegions(root: HTMLElement, options: WireResizableRegionsOptions): () => void;
14
15
 
15
16
  export { type ResizeCommit, type WireResizableRegionsOptions, wireResizableRegions };
@@ -1,4 +1,5 @@
1
- export { wireResizableRegions } from './chunk-EKYZCRCX.js';
1
+ export { wireResizableRegions } from './chunk-XQV2B4IL.js';
2
+
2
3
 
3
4
 
4
5
  //# sourceMappingURL=wire-resizable-regions.js.map
@@ -1,3 +1,3 @@
1
- export { reorderTabs, wireTabBars } from './chunk-UKKSMZYT.js';
1
+ export { reorderTabs, wireTabBars } from './chunk-46263QLK.js';
2
2
 
3
3
  //# sourceMappingURL=wire-tab-bars.js.map
@@ -0,0 +1,109 @@
1
+ import { Signal, ReadonlySignal } from 'kerfjs';
2
+ import { DeviceClass } from './device-class.js';
3
+
4
+ /** A Workbench panel slot, named after its `WorkbenchProps` prop. */
5
+ type WorkbenchPanelKey = 'leftRail' | 'rightRail' | 'bottomDrawer';
6
+
7
+ /** Minimal `localStorage`-shaped store, so the persistence hook is testable. */
8
+ interface WorkbenchStorage {
9
+ getItem(key: string): string | null;
10
+ setItem(key: string, value: string): void;
11
+ }
12
+ /** One Workbench panel's app-owned state. */
13
+ interface WireWorkbenchPanel {
14
+ /**
15
+ * The app-owned size signal a `resizable` panel renders as its `size`.
16
+ * `wireWorkbench` writes each committed resize here; collapsing never
17
+ * touches it, so an expanded panel returns at the size it had. Omit it for
18
+ * a panel that is not resizable.
19
+ */
20
+ size?: Signal<number>;
21
+ /**
22
+ * When set with `size`, the size is loaded from and saved to `storage`
23
+ * under this key, so the panel remembers the user's size.
24
+ */
25
+ storageKey?: string;
26
+ /**
27
+ * The app-owned signal the panel renders as its `collapsed`. With it (and
28
+ * `dismissOverlays`, on by default) `wireWorkbench` treats the panel as a
29
+ * transient overlay while it presents as one: it collapses when its
30
+ * `responsiveOverlayAt` breakpoint begins to apply and gets its inline state
31
+ * back when the breakpoint stops applying, an open overlay takes focus and
32
+ * keeps Tab inside it, and it closes on Escape or a press outside it.
33
+ */
34
+ collapsed?: Signal<boolean>;
35
+ }
36
+ /** A resize the user made, after `wireWorkbench` wrote it to the size signal. */
37
+ interface WorkbenchResize {
38
+ panel: WorkbenchPanelKey;
39
+ size: number;
40
+ source: 'keyboard' | 'pointer';
41
+ }
42
+ interface WireWorkbenchOptions {
43
+ /** The `id` the `Workbench` was rendered with. */
44
+ id: string;
45
+ /**
46
+ * The wired panels, keyed like the `Workbench` props. Render a panel given
47
+ * a `size` with `resizable` and `size={panel.size.value}`, and one given a
48
+ * `collapsed` signal with `collapsed={panel.collapsed.value}`.
49
+ */
50
+ panels: Partial<Record<WorkbenchPanelKey, WireWorkbenchPanel>>;
51
+ /**
52
+ * When provided, resizing is suspended while `deviceClass.compact` is true —
53
+ * the classes where rails become overlay drawers or are replaced.
54
+ */
55
+ deviceClass?: ReadonlySignal<DeviceClass>;
56
+ /** Persistence store (default `globalThis.localStorage`, if present). */
57
+ storage?: WorkbenchStorage;
58
+ /** Keyboard step in px (default 16). */
59
+ step?: number;
60
+ /** Shift+arrow step in px (default 64). */
61
+ largeStep?: number;
62
+ /** Called after each committed resize. */
63
+ onResize?: (change: WorkbenchResize) => void;
64
+ /**
65
+ * Treat the panels given a `collapsed` signal as transient overlays while
66
+ * they present as overlays (default `true`), like `wireSidebar`'s compact
67
+ * overlay: a panel whose `responsiveOverlayAt` breakpoint begins to apply
68
+ * starts collapsed, with no collapse motion, and gets its inline collapsed
69
+ * state back when the breakpoint stops applying (and on disposal); an open
70
+ * overlay panel, responsive or `presentation: "overlay"`, closes on Escape
71
+ * or a press that starts and ends outside it. A panel that opens as an
72
+ * overlay takes focus on its first focusable control, and while it is open
73
+ * Tab and Shift+Tab cycle through its controls instead of reaching the
74
+ * work area it covers (the ARIA dialog pattern of `wireSidebar`'s compact
75
+ * overlay); inline panels never move focus. Focus stranded in a closing
76
+ * panel — however it closed, the app's own control inside it included —
77
+ * returns to the control that had it when the panel opened, else to the
78
+ * panel's restore control, else to a control outside the panel whose
79
+ * `aria-controls` names it (each panel's `id` is its region id, e.g.
80
+ * `studio-left-rail`) — the fallback for a panel already open at wire-up.
81
+ * An opener inside a panel that has closed since is skipped. `false` leaves
82
+ * every `collapsed` write and all focus handling to the app.
83
+ */
84
+ dismissOverlays?: boolean;
85
+ /**
86
+ * Keep overlays exclusive (default `true`), like `wireSidebar`'s
87
+ * `exclusiveCompact`: when a panel opens while it presents as an overlay,
88
+ * every other open overlay panel — rails and the bottom drawer alike —
89
+ * closes, so one overlay never covers another's controls. Panels presenting
90
+ * inline are never closed by it. Applies only with `dismissOverlays`.
91
+ */
92
+ exclusiveOverlays?: boolean;
93
+ }
94
+ /**
95
+ * Wire a `Workbench`'s panels. For `resizable` panels given a `size` signal:
96
+ * pointer drags and arrow / Shift+arrow / Home / End on each panel's
97
+ * separator, clamped to the panel's limits and to the room that leaves the
98
+ * work area its minimum width and height, committed to the app-owned size signals.
99
+ * Optional persistence loads and saves each size; optional `deviceClass`
100
+ * suspends resizing on compact classes. Collapse stays the app's `collapsed`
101
+ * flag and never changes a size. For panels given a `collapsed` signal,
102
+ * overlays are transient (`dismissOverlays`): a responsive overlay starts
103
+ * collapsed, an open overlay takes focus and keeps Tab inside it, it closes
104
+ * on Escape or an outside press, and
105
+ * opening one overlay closes the others (`exclusiveOverlays`). Returns a disposer. See `docs/23-app-layouts.md` §3.3.
106
+ */
107
+ declare function wireWorkbench(root: HTMLElement, { id, panels, deviceClass, storage, step, largeStep, onResize, dismissOverlays, exclusiveOverlays, }: WireWorkbenchOptions): () => void;
108
+
109
+ export { type WireWorkbenchOptions, type WireWorkbenchPanel, type WorkbenchPanelKey, type WorkbenchResize, type WorkbenchStorage, wireWorkbench };