@nimblebrain/synapse 0.17.0 → 0.18.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/ui/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { ensureStyle, injectBaseReset } from '../chunk-3HQGHPNC.js';
2
- import { createContext, useRef, useId, useState, useEffect, useMemo, useContext, useLayoutEffect, Fragment as Fragment$1 } from 'react';
2
+ import { createContext, useRef, useId, useState, useMemo, useContext, useEffect, useLayoutEffect, Fragment as Fragment$1 } from 'react';
3
3
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
4
4
 
5
5
  // src/ui/tokens.ts
@@ -20,6 +20,7 @@ var tokens = {
20
20
  ring: "var(--color-ring-primary, #2563eb)",
21
21
  // ── Status / brand semantics ──
22
22
  danger: "var(--nb-color-danger, #dc2626)",
23
+ dangerFg: "var(--nb-color-danger-foreground, #ffffff)",
23
24
  success: "var(--nb-color-success, #059669)",
24
25
  warning: "var(--nb-color-warning, #f59e0b)",
25
26
  processing: "var(--nb-color-processing, #7c3aed)",
@@ -292,6 +293,10 @@ function Button({
292
293
  vars["--nb-btn-bg"] = tokens.accent;
293
294
  vars["--nb-btn-bg-hover"] = `color-mix(in oklab, ${tokens.accent} 88%, black)`;
294
295
  vars["--nb-btn-fg"] = tokens.accentFg;
296
+ } else if (variant === "danger") {
297
+ vars["--nb-btn-bg"] = tokens.danger;
298
+ vars["--nb-btn-bg-hover"] = `color-mix(in oklab, ${tokens.danger} 88%, black)`;
299
+ vars["--nb-btn-fg"] = tokens.dangerFg;
295
300
  } else if (variant === "secondary") {
296
301
  vars["--nb-btn-bg"] = tokens.bgSubtle;
297
302
  vars["--nb-btn-bg-hover"] = tokens.bgRaised;
@@ -410,8 +415,283 @@ function Card({
410
415
  }
411
416
  );
412
417
  }
413
- var STYLE_ID3 = "nb-synapse-drawer";
418
+ function inClosedDetails(el) {
419
+ const details = el.closest("details:not([open])");
420
+ if (!details) return false;
421
+ const summary = details.querySelector("summary");
422
+ return !summary || !summary.contains(el);
423
+ }
424
+ var CANDIDATES = "a[href], button, input, select, textarea, summary, [tabindex]";
425
+ function tabbables(panel) {
426
+ return Array.from(panel.querySelectorAll(CANDIDATES)).filter(
427
+ (el) => el.tabIndex >= 0 && !el.matches(":disabled") && !el.closest("[hidden]") && !inClosedDetails(el) && el.type !== "hidden"
428
+ );
429
+ }
430
+ var openOverlays = [];
431
+ var lockedOverflow = "";
432
+ var handledEscapes = /* @__PURE__ */ new WeakSet();
433
+ function isInnermost(self) {
434
+ const selfAt = openOverlays.indexOf(self);
435
+ return openOverlays.every((other, at) => {
436
+ if (other === self || !other.panel || !self.panel) return true;
437
+ if (self.panel.contains(other.panel)) return false;
438
+ if (other.panel.contains(self.panel)) return true;
439
+ return at < selfAt;
440
+ });
441
+ }
442
+ function useModal(open, panelRef, { onEscape, initialFocus }) {
443
+ const escapeRef = useRef(onEscape);
444
+ escapeRef.current = onEscape;
445
+ const initialFocusRef = useRef(initialFocus);
446
+ initialFocusRef.current = initialFocus;
447
+ useEffect(() => {
448
+ if (!open) return;
449
+ const panel = panelRef.current;
450
+ const previouslyFocused = document.activeElement;
451
+ const entry = { panel, restoreTo: previouslyFocused };
452
+ const nested = openOverlays.filter((o) => panel && o.panel && panel.contains(o.panel)).at(-1);
453
+ if (nested) {
454
+ entry.restoreTo = nested.restoreTo;
455
+ nested.restoreTo = panel;
456
+ }
457
+ const { body } = document;
458
+ if (openOverlays.length === 0) lockedOverflow = body.style.overflow;
459
+ openOverlays.push(entry);
460
+ const innermost = () => isInnermost(entry);
461
+ if (!(panel && previouslyFocused && panel.contains(previouslyFocused))) {
462
+ (initialFocusRef.current?.() ?? panel)?.focus();
463
+ }
464
+ body.style.overflow = "hidden";
465
+ const onWindowKeyDown = (e) => {
466
+ if (e.key !== "Escape" || handledEscapes.has(e) || !innermost()) return;
467
+ handledEscapes.add(e);
468
+ e.preventDefault();
469
+ escapeRef.current();
470
+ };
471
+ window.addEventListener("keydown", onWindowKeyDown);
472
+ const onPanelKeyDown = (e) => {
473
+ if (e.key !== "Tab" || !panel || !innermost()) return;
474
+ const focusables = tabbables(panel);
475
+ if (focusables.length === 0) {
476
+ e.preventDefault();
477
+ return;
478
+ }
479
+ const first = focusables[0];
480
+ const last = focusables[focusables.length - 1];
481
+ const active = document.activeElement;
482
+ if (e.shiftKey && (active === first || active === panel)) {
483
+ e.preventDefault();
484
+ last.focus();
485
+ } else if (!e.shiftKey && active === last) {
486
+ e.preventDefault();
487
+ first.focus();
488
+ }
489
+ };
490
+ panel?.addEventListener("keydown", onPanelKeyDown);
491
+ return () => {
492
+ window.removeEventListener("keydown", onWindowKeyDown);
493
+ panel?.removeEventListener("keydown", onPanelKeyDown);
494
+ const at = openOverlays.indexOf(entry);
495
+ if (at !== -1) openOverlays.splice(at, 1);
496
+ if (openOverlays.length === 0) body.style.overflow = lockedOverflow;
497
+ entry.restoreTo?.focus?.();
498
+ };
499
+ }, [open, panelRef]);
500
+ }
501
+ var STYLE_ID3 = "nb-synapse-confirm-dialog";
414
502
  var RULES3 = `
503
+ /* border-box on both, because the kit cannot assume the host resets it: under
504
+ content-box a full-width panel is its padding wider than a phone screen. */
505
+ .nb-confirm-scrim {
506
+ box-sizing: border-box;
507
+ position: fixed; inset: 0; z-index: 1010;
508
+ display: flex; align-items: center; justify-content: center;
509
+ padding: 1rem;
510
+ background: rgba(0, 0, 0, 0.4);
511
+ animation: nb-confirm-fade 160ms ease;
512
+ }
513
+ .nb-confirm {
514
+ box-sizing: border-box;
515
+ width: 100%; max-width: 28rem; max-height: 100%;
516
+ overflow-y: auto;
517
+ outline: none;
518
+ animation: nb-confirm-in 160ms cubic-bezier(0.2, 0, 0, 1);
519
+ }
520
+ .nb-confirm__actions {
521
+ display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 0.5rem;
522
+ }
523
+ @keyframes nb-confirm-fade { from { opacity: 0; } }
524
+ @keyframes nb-confirm-in { from { opacity: 0; transform: translateY(4px); } }
525
+ @media (pointer: coarse) {
526
+ .nb-confirm__actions .nb-btn { min-height: 44px; }
527
+ }
528
+ `;
529
+ function ConfirmDialog({
530
+ open,
531
+ onOpenChange,
532
+ title,
533
+ description,
534
+ children,
535
+ confirmLabel = "Confirm",
536
+ cancelLabel = "Cancel",
537
+ pendingLabel,
538
+ destructive = false,
539
+ onConfirm
540
+ }) {
541
+ ensureStyle(STYLE_ID3, RULES3);
542
+ const panelRef = useRef(null);
543
+ const titleId = useId();
544
+ const descriptionId = useId();
545
+ const [pending, setPending] = useState(false);
546
+ const [error, setError] = useState(null);
547
+ useEffect(() => {
548
+ if (open) {
549
+ setError(null);
550
+ setPending(false);
551
+ }
552
+ }, [open]);
553
+ const dismiss = () => {
554
+ if (!pending) onOpenChange(false);
555
+ };
556
+ const focusTarget = () => panelRef.current?.querySelector(
557
+ destructive ? "[data-nb-confirm-cancel]" : "[data-nb-confirm-confirm]"
558
+ );
559
+ useModal(open, panelRef, { onEscape: dismiss, initialFocus: focusTarget });
560
+ useEffect(() => {
561
+ if (error) focusTarget()?.focus();
562
+ }, [error]);
563
+ const confirm = async () => {
564
+ if (pending) return;
565
+ setPending(true);
566
+ setError(null);
567
+ try {
568
+ await onConfirm();
569
+ setPending(false);
570
+ onOpenChange(false);
571
+ } catch (err) {
572
+ const message = err instanceof Error ? err.message : String(err);
573
+ setError(message || "That didn't go through. Try again.");
574
+ setPending(false);
575
+ }
576
+ };
577
+ if (!open) return null;
578
+ const panelStyle = {
579
+ padding: "1.25rem",
580
+ borderRadius: tokens.radiusMd,
581
+ border: `${tokens.borderWidth} solid ${tokens.border}`,
582
+ background: tokens.bg,
583
+ color: tokens.fg,
584
+ boxShadow: tokens.shadowLg,
585
+ fontFamily: tokens.fontSans
586
+ };
587
+ return (
588
+ // biome-ignore lint/a11y/useKeyWithClickEvents: scrim click is a bonus mouse affordance; Escape and Cancel are the keyboard dismissal paths.
589
+ // biome-ignore lint/a11y/noStaticElementInteractions: the scrim is a decorative dismissal backdrop; the panel below owns the alertdialog role/semantics.
590
+ /* @__PURE__ */ jsx(
591
+ "div",
592
+ {
593
+ className: "nb-confirm-scrim",
594
+ onClick: (e) => {
595
+ if (e.target === e.currentTarget) dismiss();
596
+ },
597
+ children: /* @__PURE__ */ jsxs(
598
+ "div",
599
+ {
600
+ ref: panelRef,
601
+ role: "alertdialog",
602
+ "aria-modal": "true",
603
+ "aria-labelledby": titleId,
604
+ "aria-describedby": description != null ? descriptionId : void 0,
605
+ "aria-busy": pending || void 0,
606
+ tabIndex: -1,
607
+ className: "nb-confirm",
608
+ style: panelStyle,
609
+ children: [
610
+ /* @__PURE__ */ jsx(
611
+ "h2",
612
+ {
613
+ id: titleId,
614
+ style: {
615
+ margin: 0,
616
+ fontFamily: tokens.fontSans,
617
+ fontSize: tokens.textBaseSize,
618
+ lineHeight: tokens.textBaseLine,
619
+ fontWeight: tokens.weightSemibold,
620
+ color: tokens.fg
621
+ },
622
+ children: title
623
+ }
624
+ ),
625
+ description != null ? /* @__PURE__ */ jsx(
626
+ "p",
627
+ {
628
+ id: descriptionId,
629
+ style: {
630
+ margin: "0.25rem 0 0",
631
+ fontSize: tokens.textSmSize,
632
+ lineHeight: tokens.textSmLine,
633
+ color: tokens.fgMuted
634
+ },
635
+ children: description
636
+ }
637
+ ) : null,
638
+ children != null ? /* @__PURE__ */ jsx(
639
+ "div",
640
+ {
641
+ style: {
642
+ marginTop: "1rem",
643
+ fontSize: tokens.textSmSize,
644
+ lineHeight: tokens.textSmLine
645
+ },
646
+ children
647
+ }
648
+ ) : null,
649
+ error ? /* @__PURE__ */ jsx(
650
+ "p",
651
+ {
652
+ role: "alert",
653
+ style: {
654
+ margin: "0.75rem 0 0",
655
+ fontSize: tokens.textSmSize,
656
+ lineHeight: tokens.textSmLine,
657
+ color: tokens.danger
658
+ },
659
+ children: error
660
+ }
661
+ ) : null,
662
+ /* @__PURE__ */ jsxs("div", { className: "nb-confirm__actions", style: { marginTop: "1.25rem" }, children: [
663
+ /* @__PURE__ */ jsx(
664
+ Button,
665
+ {
666
+ variant: "secondary",
667
+ size: "sm",
668
+ "data-nb-confirm-cancel": "",
669
+ disabled: pending,
670
+ onClick: dismiss,
671
+ children: cancelLabel
672
+ }
673
+ ),
674
+ /* @__PURE__ */ jsx(
675
+ Button,
676
+ {
677
+ variant: destructive ? "danger" : "primary",
678
+ size: "sm",
679
+ "data-nb-confirm-confirm": "",
680
+ disabled: pending,
681
+ onClick: () => void confirm(),
682
+ children: pending ? pendingLabel ?? confirmLabel : confirmLabel
683
+ }
684
+ )
685
+ ] })
686
+ ]
687
+ }
688
+ )
689
+ }
690
+ )
691
+ );
692
+ }
693
+ var STYLE_ID4 = "nb-synapse-drawer";
694
+ var RULES4 = `
415
695
  .nb-drawer-scrim {
416
696
  position: fixed; inset: 0; z-index: 1000;
417
697
  display: flex; background: rgba(0, 0, 0, 0.32);
@@ -438,18 +718,6 @@ var RULES3 = `
438
718
  .nb-drawer-iconbtn { min-width: 44px; min-height: 44px; }
439
719
  }
440
720
  `;
441
- function inClosedDetails(el) {
442
- const details = el.closest("details:not([open])");
443
- if (!details) return false;
444
- const summary = details.querySelector("summary");
445
- return !summary || !summary.contains(el);
446
- }
447
- var CANDIDATES = "a[href], button, input, select, textarea, summary, [tabindex]";
448
- function tabbables(panel) {
449
- return Array.from(panel.querySelectorAll(CANDIDATES)).filter(
450
- (el) => el.tabIndex >= 0 && !el.matches(":disabled") && !el.closest("[hidden]") && !inClosedDetails(el) && el.type !== "hidden"
451
- );
452
- }
453
721
  var DrawerContext = createContext(null);
454
722
  function DrawerRoot({
455
723
  open,
@@ -461,54 +729,11 @@ function DrawerRoot({
461
729
  children,
462
730
  ...rest
463
731
  }) {
464
- ensureStyle(STYLE_ID3, RULES3);
732
+ ensureStyle(STYLE_ID4, RULES4);
465
733
  const panelRef = useRef(null);
466
734
  const labelId = useId();
467
735
  const [hasTitle, setHasTitle] = useState(false);
468
- useEffect(() => {
469
- if (!open) return;
470
- const onKey = (e) => {
471
- if (e.key === "Escape") {
472
- e.preventDefault();
473
- (onEscape ?? onClose)();
474
- }
475
- };
476
- window.addEventListener("keydown", onKey);
477
- return () => window.removeEventListener("keydown", onKey);
478
- }, [open, onEscape, onClose]);
479
- useEffect(() => {
480
- if (!open) return;
481
- const panel = panelRef.current;
482
- const previouslyFocused = document.activeElement;
483
- panel?.focus();
484
- const { body } = document;
485
- const prevOverflow = body.style.overflow;
486
- body.style.overflow = "hidden";
487
- const onKeyDown = (e) => {
488
- if (e.key !== "Tab" || !panel) return;
489
- const focusables = tabbables(panel);
490
- if (focusables.length === 0) {
491
- e.preventDefault();
492
- return;
493
- }
494
- const first = focusables[0];
495
- const last = focusables[focusables.length - 1];
496
- const active = document.activeElement;
497
- if (e.shiftKey && (active === first || active === panel)) {
498
- e.preventDefault();
499
- last.focus();
500
- } else if (!e.shiftKey && active === last) {
501
- e.preventDefault();
502
- first.focus();
503
- }
504
- };
505
- panel?.addEventListener("keydown", onKeyDown);
506
- return () => {
507
- panel?.removeEventListener("keydown", onKeyDown);
508
- body.style.overflow = prevOverflow;
509
- previouslyFocused?.focus?.();
510
- };
511
- }, [open]);
736
+ useModal(open, panelRef, { onEscape: onEscape ?? onClose });
512
737
  const ctxValue = useMemo(() => ({ labelId, setHasTitle }), [labelId]);
513
738
  if (!open) return null;
514
739
  const isBottom = side === "bottom";
@@ -740,8 +965,8 @@ function EmptyState({ icon, title, description, action, style, ...rest }) {
740
965
  }
741
966
  );
742
967
  }
743
- var STYLE_ID4 = "nb-synapse-listrow";
744
- var RULES4 = `
968
+ var STYLE_ID5 = "nb-synapse-listrow";
969
+ var RULES5 = `
745
970
  .nb-listrow { transition: background 140ms ease; }
746
971
  .nb-listrow--interactive { cursor: pointer; }
747
972
  .nb-listrow--interactive:hover { background: var(--nb-listrow-hover); }
@@ -756,7 +981,7 @@ function ListRow({
756
981
  className,
757
982
  ...rest
758
983
  }) {
759
- ensureStyle(STYLE_ID4, RULES4);
984
+ ensureStyle(STYLE_ID5, RULES5);
760
985
  const rowStyle = {
761
986
  "--nb-listrow-hover": tokens.bgSubtle,
762
987
  display: "grid",
@@ -839,8 +1064,8 @@ function ListRow({
839
1064
  }
840
1065
  );
841
1066
  }
842
- var STYLE_ID5 = "nb-synapse-page-header";
843
- var RULES5 = `
1067
+ var STYLE_ID6 = "nb-synapse-page-header";
1068
+ var RULES6 = `
844
1069
  .nb-page-header__desc {
845
1070
  display: -webkit-box;
846
1071
  -webkit-box-orient: vertical;
@@ -859,7 +1084,7 @@ function PageHeader({
859
1084
  style,
860
1085
  ...rest
861
1086
  }) {
862
- ensureStyle(STYLE_ID5, RULES5);
1087
+ ensureStyle(STYLE_ID6, RULES6);
863
1088
  const descVars = descriptionLines > 0 ? { "--nb-ph-desc-lines": String(descriptionLines) } : {};
864
1089
  return /* @__PURE__ */ jsxs(
865
1090
  "header",
@@ -945,8 +1170,8 @@ function Pagination({ page, pageCount, onChange, style, ...rest }) {
945
1170
  }
946
1171
  );
947
1172
  }
948
- var STYLE_ID6 = "nb-synapse-prose";
949
- var RULES6 = `
1173
+ var STYLE_ID7 = "nb-synapse-prose";
1174
+ var RULES7 = `
950
1175
  .nb-prose {
951
1176
  font-family: ${tokens.fontSans};
952
1177
  font-size: ${tokens.textBaseSize};
@@ -971,15 +1196,15 @@ var RULES6 = `
971
1196
  .nb-prose strong { font-weight: ${tokens.weightSemibold}; }
972
1197
  `;
973
1198
  function Prose({ html, children, className, ...rest }) {
974
- ensureStyle(STYLE_ID6, RULES6);
1199
+ ensureStyle(STYLE_ID7, RULES7);
975
1200
  const cls = `nb-prose ${className ?? ""}`.trim();
976
1201
  if (html !== void 0) {
977
1202
  return /* @__PURE__ */ jsx("div", { className: cls, dangerouslySetInnerHTML: { __html: html }, ...rest });
978
1203
  }
979
1204
  return /* @__PURE__ */ jsx("div", { className: cls, ...rest, children });
980
1205
  }
981
- var STYLE_ID7 = "nb-synapse-searchfield";
982
- var RULES7 = `
1206
+ var STYLE_ID8 = "nb-synapse-searchfield";
1207
+ var RULES8 = `
983
1208
  .nb-search { font-family: var(--nb-search-font); color: var(--nb-search-fg); outline: none; }
984
1209
  .nb-search::placeholder { color: var(--nb-search-placeholder); }
985
1210
  .nb-search--underline {
@@ -1009,7 +1234,7 @@ function SearchField({
1009
1234
  type = "search",
1010
1235
  ...rest
1011
1236
  }) {
1012
- ensureStyle(STYLE_ID7, RULES7);
1237
+ ensureStyle(STYLE_ID8, RULES8);
1013
1238
  const fieldStyle = {
1014
1239
  "--nb-search-font": tokens.fontSans,
1015
1240
  "--nb-search-fg": tokens.fg,
@@ -1032,8 +1257,8 @@ function SearchField({
1032
1257
  }
1033
1258
  );
1034
1259
  }
1035
- var STYLE_ID8 = "nb-synapse-segmented";
1036
- var RULES8 = `
1260
+ var STYLE_ID9 = "nb-synapse-segmented";
1261
+ var RULES9 = `
1037
1262
  .nb-seg {
1038
1263
  display: inline-flex;
1039
1264
  gap: 2px;
@@ -1068,7 +1293,7 @@ function SegmentedControl({
1068
1293
  className,
1069
1294
  ...rest
1070
1295
  }) {
1071
- ensureStyle(STYLE_ID8, RULES8);
1296
+ ensureStyle(STYLE_ID9, RULES9);
1072
1297
  const trackStyle = {
1073
1298
  "--nb-seg-radius": tokens.radiusSm,
1074
1299
  "--nb-seg-track": tokens.bgSubtle,
@@ -1097,14 +1322,14 @@ function SegmentedControl({
1097
1322
  )) })
1098
1323
  );
1099
1324
  }
1100
- var STYLE_ID9 = "nb-synapse-spinner";
1101
- var RULES9 = `
1325
+ var STYLE_ID10 = "nb-synapse-spinner";
1326
+ var RULES10 = `
1102
1327
  @keyframes nb-spin { to { transform: rotate(360deg); } }
1103
1328
  .nb-spinner { animation: nb-spin 0.7s linear infinite; }
1104
1329
  @media (prefers-reduced-motion: reduce) { .nb-spinner { animation-duration: 1.6s; } }
1105
1330
  `;
1106
1331
  function Spinner({ size = 16, color, style, className, ...rest }) {
1107
- ensureStyle(STYLE_ID9, RULES9);
1332
+ ensureStyle(STYLE_ID10, RULES10);
1108
1333
  const spinnerStyle = {
1109
1334
  display: "inline-block",
1110
1335
  flexShrink: 0,
@@ -1133,7 +1358,7 @@ var COLOR = {
1133
1358
  failed: tokens.danger,
1134
1359
  idle: tokens.fgFaint
1135
1360
  };
1136
- var STYLE_ID10 = "nb-synapse-statusdot";
1361
+ var STYLE_ID11 = "nb-synapse-statusdot";
1137
1362
  var KEYFRAMES = `
1138
1363
  @keyframes nb-statusdot-pulse {
1139
1364
  0% { box-shadow: 0 0 0 0 var(--nb-statusdot-ring); transform: scale(1); }
@@ -1155,7 +1380,7 @@ function StatusDot({
1155
1380
  className,
1156
1381
  ...rest
1157
1382
  }) {
1158
- ensureStyle(STYLE_ID10, KEYFRAMES);
1383
+ ensureStyle(STYLE_ID11, KEYFRAMES);
1159
1384
  const dotColor = color ?? COLOR[status];
1160
1385
  const working = status === "working";
1161
1386
  const dotStyle = {
@@ -1178,8 +1403,8 @@ function StatusDot({
1178
1403
  }
1179
1404
  );
1180
1405
  }
1181
- var STYLE_ID11 = "nb-synapse-table";
1182
- var RULES10 = `
1406
+ var STYLE_ID12 = "nb-synapse-table";
1407
+ var RULES11 = `
1183
1408
  .nb-table { width: 100%; border-collapse: collapse; font-family: var(--nb-table-font); }
1184
1409
  .nb-table th, .nb-table td {
1185
1410
  padding: 0.55rem 0.75rem;
@@ -1209,7 +1434,7 @@ function Table({
1209
1434
  className,
1210
1435
  ...rest
1211
1436
  }) {
1212
- ensureStyle(STYLE_ID11, RULES10);
1437
+ ensureStyle(STYLE_ID12, RULES11);
1213
1438
  const vars = {
1214
1439
  "--nb-table-font": tokens.fontSans,
1215
1440
  "--nb-table-fg": tokens.fg,
@@ -1275,8 +1500,8 @@ function Table({
1275
1500
  }
1276
1501
  );
1277
1502
  }
1278
- var STYLE_ID12 = "nb-synapse-tabs";
1279
- var RULES11 = `
1503
+ var STYLE_ID13 = "nb-synapse-tabs";
1504
+ var RULES12 = `
1280
1505
  .nb-tabs {
1281
1506
  display: flex;
1282
1507
  align-items: stretch;
@@ -1334,7 +1559,7 @@ function Tabs({
1334
1559
  onKeyDown: callerKeyDown,
1335
1560
  ...rest
1336
1561
  }) {
1337
- ensureStyle(STYLE_ID12, RULES11);
1562
+ ensureStyle(STYLE_ID13, RULES12);
1338
1563
  const barRef = useRef(null);
1339
1564
  const barStyle = {
1340
1565
  "--nb-tabs-gap": "1.25rem",
@@ -1555,41 +1780,54 @@ function PageLayout({
1555
1780
  style,
1556
1781
  ...rest
1557
1782
  }) {
1558
- return /* @__PURE__ */ jsxs(
1559
- "div",
1560
- {
1561
- style: { display: "flex", flexDirection: "column", gap: "1.1rem", minWidth: 0, ...style },
1562
- ...rest,
1563
- children: [
1564
- /* @__PURE__ */ jsx(
1565
- PageHeader,
1566
- {
1567
- crumbs,
1568
- title,
1569
- status,
1570
- actions,
1571
- description,
1572
- descriptionLines
1573
- }
1574
- ),
1575
- tabs,
1576
- toolbar ? /* @__PURE__ */ jsx(
1577
- "div",
1578
- {
1579
- style: {
1580
- display: "flex",
1581
- alignItems: "center",
1582
- gap: "0.5rem",
1583
- flexWrap: "wrap",
1584
- minWidth: 0,
1585
- marginBottom: "-0.35rem"
1586
- },
1587
- children: toolbar
1588
- }
1589
- ) : null,
1590
- children
1591
- ]
1592
- }
1783
+ return (
1784
+ // A `<section>`, not a `<div>`, and it is the header below that makes it load-bearing.
1785
+ // `<header>` maps to the `banner` landmark UNLESS it descends from sectioning content, so
1786
+ // a page inside a page — a `PageLayout` in a pane of one — produced TWO banners, both
1787
+ // claiming to head the document. That is the same defect as the app-chrome bar this
1788
+ // layout exists without, one layer down and invisible on screen: it shows up only in the
1789
+ // landmark list a screen-reader user navigates by.
1790
+ //
1791
+ // Scoping it here rather than asking callers to wrap is the point. A consuming app hit
1792
+ // this, diagnosed it, and wrapped its own instance — correct, and not a thing every app
1793
+ // should have to rediscover. An embedded app is not the document anyway: the host owns
1794
+ // the real banner, and what this renders is a section of the host's page.
1795
+ /* @__PURE__ */ jsxs(
1796
+ "section",
1797
+ {
1798
+ style: { display: "flex", flexDirection: "column", gap: "1.1rem", minWidth: 0, ...style },
1799
+ ...rest,
1800
+ children: [
1801
+ /* @__PURE__ */ jsx(
1802
+ PageHeader,
1803
+ {
1804
+ crumbs,
1805
+ title,
1806
+ status,
1807
+ actions,
1808
+ description,
1809
+ descriptionLines
1810
+ }
1811
+ ),
1812
+ tabs,
1813
+ toolbar ? /* @__PURE__ */ jsx(
1814
+ "div",
1815
+ {
1816
+ style: {
1817
+ display: "flex",
1818
+ alignItems: "center",
1819
+ gap: "0.5rem",
1820
+ flexWrap: "wrap",
1821
+ minWidth: 0,
1822
+ marginBottom: "-0.35rem"
1823
+ },
1824
+ children: toolbar
1825
+ }
1826
+ ) : null,
1827
+ children
1828
+ ]
1829
+ }
1830
+ )
1593
1831
  );
1594
1832
  }
1595
1833
  var SidebarCtx = createContext(null);
@@ -1598,8 +1836,8 @@ function useSidebar() {
1598
1836
  if (!ctx) throw new Error("useSidebar must be used within <SidebarLayout>");
1599
1837
  return ctx;
1600
1838
  }
1601
- var STYLE_ID13 = "nb-synapse-sidebarlayout";
1602
- var RULES12 = `
1839
+ var STYLE_ID14 = "nb-synapse-sidebarlayout";
1840
+ var RULES13 = `
1603
1841
  .nb-sidebar-drawer { transition: transform 220ms cubic-bezier(0.2, 0, 0, 1); }
1604
1842
  .nb-sidebar-scrim { animation: nb-sidebar-fade 180ms ease; }
1605
1843
  @keyframes nb-sidebar-fade { from { opacity: 0; } to { opacity: 1; } }
@@ -1649,7 +1887,7 @@ function SidebarLayoutRoot({
1649
1887
  }
1650
1888
  function Sidebar({ style, children, ...rest }) {
1651
1889
  const { collapsed, mode, side, width, open, setOpen } = useSidebar();
1652
- ensureStyle(STYLE_ID13, RULES12);
1890
+ ensureStyle(STYLE_ID14, RULES13);
1653
1891
  if (!collapsed) {
1654
1892
  const border = `${tokens.borderWidth} solid ${tokens.border}`;
1655
1893
  return /* @__PURE__ */ jsx(
@@ -1774,6 +2012,6 @@ function Trigger({ children, style, onClick, ...rest }) {
1774
2012
  }
1775
2013
  var SidebarLayout = Object.assign(SidebarLayoutRoot, { Sidebar, Main, Trigger });
1776
2014
 
1777
- export { AppFrame, Avatar, Badge, Breadcrumb, Button, Card, Divider, Drawer, EmptyState, Heading, Inline, ListDetailLayout, ListRow, PageHeader, PageLayout, Pagination, Prose, SearchField, SegmentedControl, SidebarLayout, Spacer, Spinner, Stack, StatusDot, Table, Tabs, Text, TextLink, headingStyle, textStyle, tokens, useBreakpoint, useListDetail, useSidebar };
2015
+ export { AppFrame, Avatar, Badge, Breadcrumb, Button, Card, ConfirmDialog, Divider, Drawer, EmptyState, Heading, Inline, ListDetailLayout, ListRow, PageHeader, PageLayout, Pagination, Prose, SearchField, SegmentedControl, SidebarLayout, Spacer, Spinner, Stack, StatusDot, Table, Tabs, Text, TextLink, headingStyle, textStyle, tokens, useBreakpoint, useListDetail, useSidebar };
1778
2016
  //# sourceMappingURL=index.js.map
1779
2017
  //# sourceMappingURL=index.js.map