@bison-lab/payload-core 3.22.0 → 3.25.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/admin.mjs CHANGED
@@ -4,8 +4,11 @@ import { ArrayField, Button, CheckboxInput, FieldDescription, FieldError, FieldL
4
4
  import { GREY_SCALES, SHADE_STEPS, contrastForeground, contrastRatio, createColorScale, deriveDarkPalette, deriveLightPalette, hexToHSL, hslToString, includedShadeSteps, isThemeHex, presetHints, setColorScaleInclude } from "@bison-lab/tokens";
5
5
  import { useCallback, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
6
6
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
7
+ import { Badge, Button as Button$1, Dialog, Field, Input, KitProvider, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@bison-lab/admin-ui";
8
+ import { HexColorPicker } from "react-colorful";
7
9
  import { createPortal } from "react-dom";
8
10
  import { findFont, fontFaceCss, isFontId } from "@bison-lab/fonts";
11
+ import "payload";
9
12
  //#region src/admin/color-field.tsx
10
13
  const HEX_ERROR = "Enter a six-digit hex colour like #1e3a5f";
11
14
  /**
@@ -41,7 +44,7 @@ const ColorField = ({ field, path, readOnly }) => {
41
44
  /* @__PURE__ */ jsx("style", {
42
45
  href: "bl-color-field",
43
46
  precedence: "default",
44
- children: CSS$4
47
+ children: CSS$5
45
48
  }),
46
49
  /* @__PURE__ */ jsx(FieldLabel, {
47
50
  htmlFor: hexId,
@@ -100,7 +103,7 @@ const ColorField = ({ field, path, readOnly }) => {
100
103
  ]
101
104
  });
102
105
  };
103
- const CSS$4 = `
106
+ const CSS$5 = `
104
107
  .bl-color-field__control{display:flex;align-items:center;gap:calc(var(--base) * .4)}
105
108
  .bl-color-field__swatch{flex:none;width:calc(var(--base) * 1.6);height:calc(var(--base) * 1.6);border:1px solid var(--theme-elevation-150);border-radius:var(--style-radius-s);background:var(--theme-elevation-50)}
106
109
  .bl-color-field__picker{flex:none;width:calc(var(--base) * 2);height:calc(var(--base) * 2);padding:0;border:1px solid var(--theme-elevation-150);border-radius:var(--style-radius-s);background:transparent;cursor:pointer}
@@ -345,6 +348,123 @@ function readabilityRatioLine(sample, target = 7) {
345
348
  const READABILITY_FIX = "Darken or lighten the hex, then regenerate the scale. The site picks light or dark type for you.";
346
349
  const READABILITY_PUBLISH_NOTE = "You can still publish. To improve a color, darken or lighten it and regenerate.";
347
350
  //#endregion
351
+ //#region src/admin/portal.tsx
352
+ /** Theme overlays must leave Payload's transformed/clipped admin tree. */
353
+ function ThemePortal({ children }) {
354
+ if (typeof document === "undefined") return null;
355
+ return createPortal(children, document.body);
356
+ }
357
+ //#endregion
358
+ //#region src/admin/brand-hex-field.tsx
359
+ /** Keep a leading # and at most six hex digits. Drop everything else. */
360
+ function filterThemeHexInput(raw) {
361
+ const trimmed = raw.trim();
362
+ if (trimmed === "") return "";
363
+ return `#${trimmed.replace(/^#/, "").replace(/[^0-9a-fA-F]/g, "").slice(0, 6)}`;
364
+ }
365
+ function BrandHexField({ name, value, onChange, readOnly, placeholder = "#DB0082", picker = "dialog" }) {
366
+ const [draft, setDraft] = useState(value);
367
+ const [open, setOpen] = useState(false);
368
+ const [pickerDraft, setPickerDraft] = useState(value);
369
+ useEffect(() => {
370
+ setDraft(value);
371
+ }, [value]);
372
+ function typeHex(next) {
373
+ const filtered = filterThemeHexInput(next);
374
+ setDraft(filtered);
375
+ if (isThemeHex(filtered)) onChange(filtered);
376
+ }
377
+ function openPicker() {
378
+ setPickerDraft(isThemeHex(draft) ? draft : value);
379
+ setOpen(true);
380
+ }
381
+ function applyPicker() {
382
+ if (!isThemeHex(pickerDraft)) return;
383
+ onChange(pickerDraft);
384
+ setDraft(pickerDraft);
385
+ setOpen(false);
386
+ }
387
+ const input = /* @__PURE__ */ jsx(Input, {
388
+ "aria-label": `${name} hex`,
389
+ value: draft,
390
+ placeholder,
391
+ spellCheck: false,
392
+ autoComplete: "off",
393
+ disabled: readOnly,
394
+ onChange: (event) => typeHex(event.target.value),
395
+ onBlur: () => {
396
+ if (!isThemeHex(draft)) setDraft(value);
397
+ }
398
+ });
399
+ return /* @__PURE__ */ jsxs(KitProvider, { children: [
400
+ /* @__PURE__ */ jsx("style", {
401
+ href: "bl-brand-hex",
402
+ precedence: "default",
403
+ children: CSS$4
404
+ }),
405
+ picker === "inline" ? /* @__PURE__ */ jsxs("div", {
406
+ className: "bl-brand-hex bl-brand-hex--inline",
407
+ children: [/* @__PURE__ */ jsx(HexColorPicker, {
408
+ color: isThemeHex(draft) ? draft : "#000000",
409
+ onChange: (hex) => {
410
+ setDraft(hex);
411
+ onChange(hex);
412
+ }
413
+ }), /* @__PURE__ */ jsxs(Field, {
414
+ as: "div",
415
+ children: [/* @__PURE__ */ jsx(Label, { children: "Hex" }), input]
416
+ })]
417
+ }) : /* @__PURE__ */ jsxs("div", {
418
+ className: "bl-brand-hex",
419
+ children: [/* @__PURE__ */ jsx("button", {
420
+ type: "button",
421
+ className: "bl-brand-hex__swatch",
422
+ "aria-label": `${name} picker`,
423
+ disabled: readOnly,
424
+ style: { background: isThemeHex(draft) ? draft : "transparent" },
425
+ onClick: openPicker
426
+ }), input]
427
+ }),
428
+ picker === "dialog" ? /* @__PURE__ */ jsx(ThemePortal, { children: /* @__PURE__ */ jsx("div", {
429
+ className: "bl-theme-dialog",
430
+ children: /* @__PURE__ */ jsx(Dialog, {
431
+ open,
432
+ title: `Pick ${name}`,
433
+ onCancel: () => setOpen(false),
434
+ confirmLabel: "Apply",
435
+ onConfirm: applyPicker,
436
+ confirmDisabled: !isThemeHex(pickerDraft),
437
+ children: /* @__PURE__ */ jsxs("div", {
438
+ className: "bl-brand-hex bl-brand-hex--inline",
439
+ children: [/* @__PURE__ */ jsx(HexColorPicker, {
440
+ color: isThemeHex(pickerDraft) ? pickerDraft : "#000000",
441
+ onChange: setPickerDraft
442
+ }), /* @__PURE__ */ jsxs(Field, {
443
+ as: "div",
444
+ children: [/* @__PURE__ */ jsx(Label, { children: "Hex" }), /* @__PURE__ */ jsx(Input, {
445
+ "aria-label": `${name} picker hex`,
446
+ value: pickerDraft,
447
+ placeholder,
448
+ spellCheck: false,
449
+ autoComplete: "off",
450
+ onChange: (event) => setPickerDraft(filterThemeHexInput(event.target.value))
451
+ })]
452
+ })]
453
+ })
454
+ })
455
+ }) }) : null
456
+ ] });
457
+ }
458
+ const CSS$4 = `
459
+ .bl-brand-hex{display:flex;align-items:stretch;gap:8px;min-width:0}
460
+ .bl-brand-hex--inline{flex-direction:column;gap:12px}
461
+ .bl-brand-hex .react-colorful{width:100%;height:200px}
462
+ .bl-brand-hex__swatch{flex:none;width:38px;border:1px solid #3a3d46;border-radius:8px;padding:0;cursor:pointer;background:#1d1f24}
463
+ .bl-brand-hex__swatch:disabled{opacity:.5;cursor:not-allowed}
464
+ .bl-brand-hex .bl-nav-kit__input{font-family:var(--font-mono,ui-monospace,monospace)}
465
+ .bl-theme-dialog .bl-nav-kit__modal{z-index:10000}
466
+ `;
467
+ //#endregion
348
468
  //#region src/admin/colors-layout.ts
349
469
  /**
350
470
  * Theme Colors tab chrome. Payload group-in-group draws a left gutter;
@@ -386,13 +506,6 @@ const THEME_COLORS_LAYOUT_CSS = `
386
506
  }
387
507
  `;
388
508
  //#endregion
389
- //#region src/admin/portal.tsx
390
- /** Theme overlays must leave Payload's transformed/clipped admin tree. */
391
- function ThemePortal({ children }) {
392
- if (typeof document === "undefined") return null;
393
- return createPortal(children, document.body);
394
- }
395
- //#endregion
396
509
  //#region src/admin/preview-mode.tsx
397
510
  const ATTR = "data-bl-theme-preview";
398
511
  const EVENT = "bl-theme-preview";
@@ -410,7 +523,11 @@ const PREVIEW_BRAND = {
410
523
  secondary: "#111111",
411
524
  success: "#111111"
412
525
  };
413
- let currentMode = "light";
526
+ function readDocumentTheme() {
527
+ if (typeof document === "undefined") return "light";
528
+ return document.documentElement.getAttribute("data-theme") === "dark" ? "dark" : "light";
529
+ }
530
+ let currentMode = readDocumentTheme();
414
531
  function readMode() {
415
532
  return currentMode;
416
533
  }
@@ -450,7 +567,10 @@ function previewVarsFor(grey, mode) {
450
567
  "--bl-preview-border": `hsl(${palette.border})`,
451
568
  "--bl-preview-text": `hsl(${palette.foreground})`,
452
569
  "--bl-preview-muted": `hsl(${palette["muted-foreground"]})`,
453
- "--bl-preview-input": `hsl(${palette.input})`
570
+ "--bl-preview-input": `hsl(${palette.input})`,
571
+ "--theme-elevation-0": `hsl(${palette.card})`,
572
+ "--theme-elevation-150": `hsl(${palette.border})`,
573
+ "--theme-elevation-800": `hsl(${palette.foreground})`
454
574
  };
455
575
  }
456
576
  function useGreyScale() {
@@ -471,30 +591,6 @@ function usePreviewCanvas() {
471
591
  style: previewVarsFor(useGreyScale(), mode)
472
592
  };
473
593
  }
474
- function ThemePreviewToggle() {
475
- const mode = useThemePreviewMode();
476
- return /* @__PURE__ */ jsxs("div", {
477
- className: "bl-preview-toggle",
478
- role: "group",
479
- "aria-label": "Preview theme in",
480
- children: [/* @__PURE__ */ jsx("button", {
481
- type: "button",
482
- "data-on": mode === "light" ? "" : void 0,
483
- onClick: () => setThemePreviewMode("light"),
484
- children: "Light"
485
- }), /* @__PURE__ */ jsx("button", {
486
- type: "button",
487
- "data-on": mode === "dark" ? "" : void 0,
488
- onClick: () => setThemePreviewMode("dark"),
489
- children: "Dark"
490
- })]
491
- });
492
- }
493
- const THEME_PREVIEW_TOGGLE_CSS = `
494
- .bl-preview-toggle{display:inline-flex;padding:3px;border:1px solid var(--theme-elevation-150,#e2e8f0);border-radius:10px;background:var(--theme-elevation-0,#fff)}
495
- .bl-preview-toggle button{min-height:2rem;padding:.25rem .75rem;border:0;border-radius:7px;background:transparent;color:var(--theme-elevation-600,#475569);font:inherit;font-size:13px;font-weight:600;cursor:pointer}
496
- .bl-preview-toggle button[data-on]{background:var(--theme-text,#0f172a);color:var(--theme-bg,#fff)}
497
- `;
498
594
  //#endregion
499
595
  //#region src/admin/readability-list.tsx
500
596
  function ReadabilityDetail({ item, target = 7 }) {
@@ -614,13 +710,9 @@ function ColorScaleCard({ id, name, hint, required, custom, state, onChange, onD
614
710
  const canvas = usePreviewCanvas();
615
711
  const [attentionOpen, setAttentionOpen] = useState(false);
616
712
  const [shakeStep, setShakeStep] = useState(null);
617
- const [hexDraft, setHexDraft] = useState(state.hex);
618
713
  const titleId = useId();
619
714
  const included = new Set(includedShadeSteps(state));
620
715
  const hueDrift = role === "success" ? Boolean(successHueWarning(state.hex)) : role === "destructive" ? Boolean(destructiveHueWarning(state.hex)) : false;
621
- useEffect(() => {
622
- setHexDraft(state.hex);
623
- }, [state.hex]);
624
716
  function commitHex(hex) {
625
717
  onChange(createColorScale(hex, state.sourceStep, state.include));
626
718
  }
@@ -638,211 +730,182 @@ function ColorScaleCard({ id, name, hint, required, custom, state, onChange, onD
638
730
  else next.add(step);
639
731
  onChange(setColorScaleInclude(state, SHADE_STEPS.filter((item) => next.has(item))));
640
732
  }
641
- return /* @__PURE__ */ jsxs("article", {
642
- className: `bl-scale-card ${canvas.className}`,
643
- "data-preview": canvas.mode,
644
- style: canvas.style,
645
- children: [
646
- /* @__PURE__ */ jsxs("header", {
647
- className: "bl-scale-card__head",
648
- children: [/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsxs("div", {
649
- className: "bl-scale-card__title-row",
650
- children: [
651
- /* @__PURE__ */ jsx("h3", {
652
- className: "bl-scale-card__title",
653
- children: name
733
+ return /* @__PURE__ */ jsx(KitProvider, {
734
+ accent: id === "primary" && isThemeHex(state.hex) ? state.hex : void 0,
735
+ children: /* @__PURE__ */ jsxs("article", {
736
+ className: `bl-scale-card ${canvas.className}`,
737
+ "data-preview": canvas.mode,
738
+ style: canvas.style,
739
+ children: [
740
+ /* @__PURE__ */ jsxs("header", {
741
+ className: "bl-scale-card__head",
742
+ children: [/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsxs("div", {
743
+ className: "bl-scale-card__title-row",
744
+ children: [
745
+ /* @__PURE__ */ jsx("h3", {
746
+ className: "bl-scale-card__title",
747
+ children: name
748
+ }),
749
+ required ? /* @__PURE__ */ jsx(Badge, { children: "Required" }) : null,
750
+ custom ? /* @__PURE__ */ jsx(Badge, { children: "Optional" }) : null,
751
+ warning && !warning.meets ? /* @__PURE__ */ jsx(Badge, {
752
+ as: "button",
753
+ variant: "danger",
754
+ "aria-haspopup": "dialog",
755
+ onClick: () => setAttentionOpen(true),
756
+ children: "Needs attention"
757
+ }) : null
758
+ ]
759
+ }), hint ? /* @__PURE__ */ jsx("p", {
760
+ className: "bl-scale-card__hint",
761
+ children: hint
762
+ }) : null] }), onDelete ? /* @__PURE__ */ jsx("button", {
763
+ type: "button",
764
+ className: "bl-scale-card__delete",
765
+ disabled: readOnly,
766
+ onClick: onDelete,
767
+ children: "Delete"
768
+ }) : null]
769
+ }),
770
+ /* @__PURE__ */ jsxs("div", {
771
+ className: "bl-scale-card__row",
772
+ children: [/* @__PURE__ */ jsxs(Field, {
773
+ as: "div",
774
+ children: [/* @__PURE__ */ jsx(Label, { children: "Brand hex" }), /* @__PURE__ */ jsx(BrandHexField, {
775
+ name,
776
+ value: state.hex,
777
+ onChange: commitHex,
778
+ readOnly
779
+ })]
780
+ }), /* @__PURE__ */ jsxs(Field, {
781
+ as: "div",
782
+ children: [/* @__PURE__ */ jsx(Label, { children: "This color is" }), /* @__PURE__ */ jsxs(Select, {
783
+ value: String(state.sourceStep),
784
+ onValueChange: (value) => commitStep(Number(value)),
785
+ disabled: readOnly,
786
+ children: [/* @__PURE__ */ jsx(SelectTrigger, {
787
+ "aria-label": "This color is",
788
+ children: /* @__PURE__ */ jsx(SelectValue, {})
789
+ }), /* @__PURE__ */ jsx(SelectContent, { children: SHADE_STEPS.map((step) => /* @__PURE__ */ jsx(SelectItem, {
790
+ value: String(step),
791
+ children: step
792
+ }, step)) })]
793
+ })]
794
+ })]
795
+ }),
796
+ role ? /* @__PURE__ */ jsxs("div", {
797
+ className: "bl-scale-card__family",
798
+ "data-role": role,
799
+ "data-drift": hueDrift ? "" : void 0,
800
+ children: [/* @__PURE__ */ jsx("div", {
801
+ className: "bl-scale-card__family-mark",
802
+ "aria-hidden": "true",
803
+ children: "!"
804
+ }), /* @__PURE__ */ jsxs("div", { children: [
805
+ /* @__PURE__ */ jsx("div", {
806
+ className: "bl-scale-card__family-title",
807
+ children: STATUS_COPY[role].title
654
808
  }),
655
- required ? /* @__PURE__ */ jsx("span", {
656
- className: "bl-scale-card__badge",
657
- children: "Required"
658
- }) : null,
659
- custom ? /* @__PURE__ */ jsx("span", {
660
- className: "bl-scale-card__badge",
661
- children: "Optional"
662
- }) : null,
663
- warning && !warning.meets ? /* @__PURE__ */ jsx("button", {
664
- type: "button",
665
- className: "bl-scale-card__badge bl-scale-card__badge--attention",
666
- "aria-haspopup": "dialog",
667
- onClick: () => setAttentionOpen(true),
668
- children: "Needs attention"
809
+ /* @__PURE__ */ jsx("p", { children: STATUS_COPY[role].body }),
810
+ hueDrift ? /* @__PURE__ */ jsx("p", {
811
+ className: "bl-scale-card__family-drift",
812
+ children: STATUS_COPY[role].drift
669
813
  }) : null
670
- ]
671
- }), hint ? /* @__PURE__ */ jsx("p", {
672
- className: "bl-scale-card__hint",
673
- children: hint
674
- }) : null] }), onDelete ? /* @__PURE__ */ jsx("button", {
675
- type: "button",
676
- className: "bl-scale-card__delete",
677
- disabled: readOnly,
678
- onClick: onDelete,
679
- children: "Delete"
680
- }) : null]
681
- }),
682
- /* @__PURE__ */ jsxs("div", {
683
- className: "bl-scale-card__row",
684
- children: [/* @__PURE__ */ jsxs("div", {
685
- className: "bl-scale-card__field",
686
- children: [/* @__PURE__ */ jsx("span", { children: "Brand hex" }), /* @__PURE__ */ jsxs("span", {
687
- className: "bl-scale-card__hex-wrap",
688
- children: [/* @__PURE__ */ jsx("input", {
689
- type: "color",
690
- "aria-label": `${name} picker`,
814
+ ] })]
815
+ }) : null,
816
+ /* @__PURE__ */ jsxs("div", {
817
+ className: "bl-scale-card__scale-head",
818
+ children: [/* @__PURE__ */ jsx("span", { children: "Generated scale" }), /* @__PURE__ */ jsxs("span", { children: [SHADE_STEPS.length, " shades"] })]
819
+ }),
820
+ /* @__PURE__ */ jsx("div", {
821
+ className: "bl-scale-card__steps",
822
+ role: "group",
823
+ "aria-label": `${name} scale steps`,
824
+ children: SHADE_STEPS.map((step) => {
825
+ const source = step === state.sourceStep;
826
+ const on = included.has(step);
827
+ return /* @__PURE__ */ jsxs("button", {
828
+ type: "button",
829
+ className: "bl-scale-card__step",
830
+ "data-source": source ? "" : void 0,
831
+ "data-excluded": on ? void 0 : "",
832
+ "data-shake": shakeStep === step ? "" : void 0,
691
833
  disabled: readOnly,
692
- value: isThemeHex(state.hex) ? state.hex : "#000000",
693
- onPointerDown: (event) => {
694
- const input = event.currentTarget;
695
- if (typeof input.showPicker !== "function") return;
834
+ "aria-pressed": on,
835
+ "aria-label": source ? `Source step ${step} cannot be excluded` : on ? `Shade ${step}, available — click to exclude from the palette` : `Shade ${step}, excluded — click to include in the palette`,
836
+ title: source ? `Source step ${step} cannot be excluded` : on ? "Available to page editors — click to exclude" : "Excluded from page editors — click to include",
837
+ onClick: (event) => {
696
838
  event.preventDefault();
697
- try {
698
- input.showPicker();
699
- } catch {
700
- input.click();
701
- }
839
+ event.stopPropagation();
840
+ toggleStep(step);
702
841
  },
703
- onChange: (event) => commitHex(event.target.value)
704
- }), /* @__PURE__ */ jsx("input", {
705
- type: "text",
706
- spellCheck: false,
707
- autoComplete: "off",
708
- disabled: readOnly,
709
- "aria-label": `${name} hex`,
710
- value: hexDraft,
711
- onChange: (event) => {
712
- const next = event.target.value.trim();
713
- setHexDraft(next);
714
- if (isThemeHex(next)) commitHex(next);
715
- },
716
- onBlur: () => {
717
- if (!isThemeHex(hexDraft)) setHexDraft(state.hex);
718
- }
719
- })]
720
- })]
721
- }), /* @__PURE__ */ jsxs("label", {
722
- className: "bl-scale-card__field",
723
- children: [/* @__PURE__ */ jsx("span", { children: "This color is" }), /* @__PURE__ */ jsx("select", {
842
+ children: [/* @__PURE__ */ jsxs("span", {
843
+ className: "bl-scale-card__chip-wrap",
844
+ children: [/* @__PURE__ */ jsx("span", {
845
+ className: "bl-scale-card__chip",
846
+ style: { backgroundColor: hslCss(state.scale[step]) }
847
+ }), /* @__PURE__ */ jsx(IncludeMark, { on })]
848
+ }), /* @__PURE__ */ jsx("span", {
849
+ className: "bl-scale-card__step-label",
850
+ children: step
851
+ })]
852
+ }, step);
853
+ })
854
+ }),
855
+ /* @__PURE__ */ jsxs("div", {
856
+ className: "bl-scale-card__editors",
857
+ children: [/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("div", { children: "Available to page editors" }), /* @__PURE__ */ jsxs("p", { children: [
858
+ included.size,
859
+ " of ",
860
+ SHADE_STEPS.length,
861
+ " shades available · Choose which shades editors can select when building pages."
862
+ ] })] }), /* @__PURE__ */ jsxs("span", { children: [/* @__PURE__ */ jsx("button", {
863
+ type: "button",
724
864
  disabled: readOnly,
725
- value: String(state.sourceStep),
726
- onChange: (event) => commitStep(Number(event.target.value)),
727
- children: SHADE_STEPS.map((step) => /* @__PURE__ */ jsx("option", {
728
- value: step,
729
- children: step
730
- }, step))
731
- })]
732
- })]
733
- }),
734
- role ? /* @__PURE__ */ jsxs("div", {
735
- className: "bl-scale-card__family",
736
- "data-role": role,
737
- "data-drift": hueDrift ? "" : void 0,
738
- children: [/* @__PURE__ */ jsx("div", {
739
- className: "bl-scale-card__family-mark",
740
- "aria-hidden": "true",
741
- children: "!"
742
- }), /* @__PURE__ */ jsxs("div", { children: [
743
- /* @__PURE__ */ jsx("div", {
744
- className: "bl-scale-card__family-title",
745
- children: STATUS_COPY[role].title
746
- }),
747
- /* @__PURE__ */ jsx("p", { children: STATUS_COPY[role].body }),
748
- hueDrift ? /* @__PURE__ */ jsx("p", {
749
- className: "bl-scale-card__family-drift",
750
- children: STATUS_COPY[role].drift
751
- }) : null
752
- ] })]
753
- }) : null,
754
- /* @__PURE__ */ jsxs("div", {
755
- className: "bl-scale-card__scale-head",
756
- children: [/* @__PURE__ */ jsx("span", { children: "Generated scale" }), /* @__PURE__ */ jsxs("span", { children: [SHADE_STEPS.length, " shades"] })]
757
- }),
758
- /* @__PURE__ */ jsx("div", {
759
- className: "bl-scale-card__steps",
760
- role: "group",
761
- "aria-label": `${name} scale steps`,
762
- children: SHADE_STEPS.map((step) => {
763
- const source = step === state.sourceStep;
764
- const on = included.has(step);
765
- return /* @__PURE__ */ jsxs("button", {
865
+ onClick: () => onChange(setColorScaleInclude(state, "all")),
866
+ children: "Include all"
867
+ }), /* @__PURE__ */ jsx("button", {
766
868
  type: "button",
767
- className: "bl-scale-card__step",
768
- "data-source": source ? "" : void 0,
769
- "data-excluded": on ? void 0 : "",
770
- "data-shake": shakeStep === step ? "" : void 0,
771
869
  disabled: readOnly,
772
- "aria-pressed": on,
773
- "aria-label": source ? `Source step ${step} cannot be excluded` : on ? `Shade ${step}, available — click to exclude from the palette` : `Shade ${step}, excluded — click to include in the palette`,
774
- title: source ? `Source step ${step} cannot be excluded` : on ? "Available to page editors — click to exclude" : "Excluded from page editors — click to include",
775
- onClick: (event) => {
776
- event.preventDefault();
777
- event.stopPropagation();
778
- toggleStep(step);
779
- },
780
- children: [/* @__PURE__ */ jsxs("span", {
781
- className: "bl-scale-card__chip-wrap",
782
- children: [/* @__PURE__ */ jsx("span", {
783
- className: "bl-scale-card__chip",
784
- style: { backgroundColor: hslCss(state.scale[step]) }
785
- }), /* @__PURE__ */ jsx(IncludeMark, { on })]
786
- }), /* @__PURE__ */ jsx("span", {
787
- className: "bl-scale-card__step-label",
788
- children: step
789
- })]
790
- }, step);
791
- })
792
- }),
793
- /* @__PURE__ */ jsxs("div", {
794
- className: "bl-scale-card__editors",
795
- children: [/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("div", { children: "Available to page editors" }), /* @__PURE__ */ jsxs("p", { children: [
796
- included.size,
797
- " of ",
798
- SHADE_STEPS.length,
799
- " shades available · Choose which shades editors can select when building pages."
800
- ] })] }), /* @__PURE__ */ jsxs("span", { children: [/* @__PURE__ */ jsx("button", {
801
- type: "button",
802
- disabled: readOnly,
803
- onClick: () => onChange(setColorScaleInclude(state, "all")),
804
- children: "Include all"
805
- }), /* @__PURE__ */ jsx("button", {
806
- type: "button",
807
- disabled: readOnly,
808
- onClick: () => onChange(setColorScaleInclude(state, "source")),
809
- children: "Source only"
810
- })] })]
811
- }),
812
- attentionOpen && warning ? /* @__PURE__ */ jsx(ThemePortal, { children: /* @__PURE__ */ jsx("div", {
813
- className: "bl-scale-card__modal",
814
- role: "dialog",
815
- "aria-modal": "true",
816
- "aria-labelledby": titleId,
817
- onClick: (event) => {
818
- if (event.target === event.currentTarget) setAttentionOpen(false);
819
- },
820
- children: /* @__PURE__ */ jsxs("div", {
821
- className: "bl-scale-card__panel",
822
- children: [
823
- /* @__PURE__ */ jsxs("div", {
824
- className: "bl-scale-card__panel-head",
825
- children: [/* @__PURE__ */ jsx("h4", {
826
- id: titleId,
827
- children: readabilityHeadline(warning)
828
- }), /* @__PURE__ */ jsx("button", {
870
+ onClick: () => onChange(setColorScaleInclude(state, "source")),
871
+ children: "Source only"
872
+ })] })]
873
+ }),
874
+ attentionOpen && warning ? /* @__PURE__ */ jsx(ThemePortal, { children: /* @__PURE__ */ jsx("div", {
875
+ className: "bl-scale-card__modal",
876
+ role: "dialog",
877
+ "aria-modal": "true",
878
+ "aria-labelledby": titleId,
879
+ onClick: (event) => {
880
+ if (event.target === event.currentTarget) setAttentionOpen(false);
881
+ },
882
+ children: /* @__PURE__ */ jsxs("div", {
883
+ className: "bl-scale-card__panel",
884
+ children: [
885
+ /* @__PURE__ */ jsxs("div", {
886
+ className: "bl-scale-card__panel-head",
887
+ children: [/* @__PURE__ */ jsx("h4", {
888
+ id: titleId,
889
+ children: readabilityHeadline(warning)
890
+ }), /* @__PURE__ */ jsx("button", {
891
+ type: "button",
892
+ onClick: () => setAttentionOpen(false),
893
+ "aria-label": "Close",
894
+ children: "×"
895
+ })]
896
+ }),
897
+ /* @__PURE__ */ jsx(ReadabilityDetail, { item: warning }),
898
+ /* @__PURE__ */ jsx("button", {
829
899
  type: "button",
900
+ className: "bl-scale-card__panel-done",
830
901
  onClick: () => setAttentionOpen(false),
831
- "aria-label": "Close",
832
- children: "×"
833
- })]
834
- }),
835
- /* @__PURE__ */ jsx(ReadabilityDetail, { item: warning }),
836
- /* @__PURE__ */ jsx("button", {
837
- type: "button",
838
- className: "bl-scale-card__panel-done",
839
- onClick: () => setAttentionOpen(false),
840
- children: "Keep editing this color"
841
- })
842
- ]
843
- })
844
- }) }) : null
845
- ]
902
+ children: "Keep editing this color"
903
+ })
904
+ ]
905
+ })
906
+ }) }) : null
907
+ ]
908
+ })
846
909
  });
847
910
  }
848
911
  const COLOR_SCALE_CARD_CSS = `
@@ -850,18 +913,10 @@ const COLOR_SCALE_CARD_CSS = `
850
913
  .bl-scale-card__head{display:flex;justify-content:space-between;gap:1rem;align-items:flex-start}
851
914
  .bl-scale-card__title-row{position:relative;display:flex;align-items:center;flex-wrap:wrap;gap:.5rem}
852
915
  .bl-scale-card__title{margin:0;font-size:1rem}
853
- .bl-scale-card__badge{font:inherit;font-size:10px;text-transform:uppercase;letter-spacing:.04em;padding:.15rem .45rem;border-radius:999px;border:0;background:#e2e8f0;color:#0f172a}
854
- .bl-scale-card__badge--attention{background:#fff1f2;color:#be123c;cursor:pointer}
855
916
  .bl-scale-card__hint{margin:.35rem 0 0;color:var(--bl-preview-muted,var(--theme-elevation-500,#64748b));font-size:12px}
856
917
  .bl-scale-card__delete{border:0;background:none;color:#be123c;font:inherit;font-size:13px;font-weight:600;cursor:pointer}
857
- .bl-scale-card__row{display:grid;grid-template-columns:1fr 8rem;gap:.75rem;margin-top:1rem}
858
- .bl-scale-card__field{display:flex;flex-direction:column;gap:.35rem;font-size:12px;font-weight:600;color:var(--bl-preview-muted,var(--theme-elevation-600,#475569))}
859
- .bl-scale-card__hex-wrap{display:flex;border:1px solid var(--bl-preview-border,var(--theme-elevation-150,#e2e8f0));border-radius:8px;background:var(--bl-preview-card,var(--theme-input-bg,#fff))}
860
- .bl-scale-card__hex-wrap input[type=color]{width:2.75rem;height:2.25rem;padding:0;border:0;border-right:1px solid var(--bl-preview-border,var(--theme-elevation-150,#e2e8f0));border-radius:8px 0 0 8px;background:transparent;cursor:pointer;color-scheme:light;appearance:none;-webkit-appearance:none}
861
- .bl-scale-card__hex-wrap input[type=color]::-webkit-color-swatch-wrapper{padding:0}
862
- .bl-scale-card__hex-wrap input[type=color]::-webkit-color-swatch{border:0;border-radius:0;width:100%;height:100%}
863
- .bl-scale-card__hex-wrap input[type=text]{flex:1;min-width:0;border:0;padding:.4rem .7rem;font-family:var(--font-mono,ui-monospace,monospace);font-size:13px;background:transparent;color:inherit;border-radius:0 8px 8px 0}
864
- .bl-scale-card__field select{min-height:2.25rem;border:1px solid var(--bl-preview-border,var(--theme-elevation-150,#e2e8f0));border-radius:8px;padding:.35rem .5rem;background:var(--bl-preview-card,var(--theme-input-bg,#fff));color:inherit}
918
+ .bl-scale-card__row{display:grid;grid-template-columns:1fr 8rem;gap:.75rem;margin-top:1rem;align-items:start}
919
+ .bl-scale-card__row .bl-nav-kit__field{margin-bottom:0}
865
920
  .bl-scale-card__family{display:flex;gap:.75rem;align-items:flex-start;margin-top:1rem;padding:.75rem .85rem;border-radius:8px}
866
921
  .bl-scale-card__family[data-role=success]{border:1px solid #a7f3d0;background:#ecfdf5;color:#065f46}
867
922
  .bl-scale-card__family[data-role=destructive]{border:1px solid #fecdd3;background:#fff1f2;color:#9f1239}
@@ -1122,63 +1177,52 @@ const LibraryField = ({ field, path, readOnly }) => {
1122
1177
  })]
1123
1178
  }),
1124
1179
  adding ? /* @__PURE__ */ jsx(ThemePortal, { children: /* @__PURE__ */ jsx("div", {
1125
- className: "bl-library__modal",
1126
- role: "dialog",
1127
- "aria-modal": "true",
1128
- "aria-labelledby": "bl-library-add-title",
1129
- onClick: (event) => {
1130
- if (event.target === event.currentTarget) setAdding(false);
1131
- },
1132
- children: /* @__PURE__ */ jsxs("div", {
1133
- className: "bl-library__panel",
1180
+ className: "bl-theme-dialog",
1181
+ children: /* @__PURE__ */ jsx(KitProvider, { children: /* @__PURE__ */ jsxs(Dialog, {
1182
+ open: true,
1183
+ title: "New color",
1184
+ onCancel: () => {
1185
+ setAdding(false);
1186
+ setName("");
1187
+ setHex("");
1188
+ setSourceStep(500);
1189
+ },
1190
+ confirmLabel: "Create color scale",
1191
+ onConfirm: add,
1192
+ confirmDisabled: !slugify(name) || !isThemeHex(hex),
1134
1193
  children: [
1135
- /* @__PURE__ */ jsxs("div", {
1136
- className: "bl-library__panel-head",
1137
- children: [/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("h2", {
1138
- id: "bl-library-add-title",
1139
- children: "New color"
1140
- }), /* @__PURE__ */ jsx("p", { children: "Give it a useful name, enter your brand hex, then say which scale step that hex is." })] }), /* @__PURE__ */ jsx("button", {
1141
- type: "button",
1142
- onClick: () => setAdding(false),
1143
- "aria-label": "Close",
1144
- children: "×"
1145
- })]
1146
- }),
1147
- /* @__PURE__ */ jsxs("label", { children: ["Name", /* @__PURE__ */ jsx("input", {
1194
+ /* @__PURE__ */ jsx("p", { children: "Give it a useful name, enter your brand hex, then say which scale step that hex is." }),
1195
+ /* @__PURE__ */ jsxs(Field, { children: [/* @__PURE__ */ jsx(Label, { children: "Name" }), /* @__PURE__ */ jsx(Input, {
1196
+ "aria-label": "Name",
1148
1197
  value: name,
1149
1198
  placeholder: "e.g. Coral",
1150
1199
  onChange: (event) => setName(event.target.value)
1151
1200
  })] }),
1152
- /* @__PURE__ */ jsxs("label", { children: ["Brand hex", /* @__PURE__ */ jsx("input", {
1153
- value: hex,
1154
- placeholder: "#DB0082",
1155
- spellCheck: false,
1156
- autoComplete: "off",
1157
- onChange: (event) => setHex(event.target.value.trim())
1158
- })] }),
1159
- /* @__PURE__ */ jsxs("label", { children: ["This color is", /* @__PURE__ */ jsx("select", {
1160
- value: String(sourceStep),
1161
- onChange: (event) => setSourceStep(Number(event.target.value)),
1162
- children: SHADE_STEPS.map((step) => /* @__PURE__ */ jsx("option", {
1163
- value: step,
1164
- children: step
1165
- }, step))
1166
- })] }),
1167
- /* @__PURE__ */ jsxs("div", {
1168
- className: "bl-library__actions",
1169
- children: [/* @__PURE__ */ jsx("button", {
1170
- type: "button",
1171
- onClick: () => setAdding(false),
1172
- children: "Cancel"
1173
- }), /* @__PURE__ */ jsx("button", {
1174
- type: "button",
1175
- disabled: !slugify(name) || !isThemeHex(hex),
1176
- onClick: add,
1177
- children: "Create color scale"
1201
+ /* @__PURE__ */ jsxs(Field, {
1202
+ as: "div",
1203
+ children: [/* @__PURE__ */ jsx(Label, { children: "Brand hex" }), /* @__PURE__ */ jsx(BrandHexField, {
1204
+ name: "Brand",
1205
+ value: hex,
1206
+ onChange: setHex,
1207
+ picker: "inline"
1208
+ })]
1209
+ }),
1210
+ /* @__PURE__ */ jsxs(Field, {
1211
+ as: "div",
1212
+ children: [/* @__PURE__ */ jsx(Label, { children: "This color is" }), /* @__PURE__ */ jsxs(Select, {
1213
+ value: String(sourceStep),
1214
+ onValueChange: (value) => setSourceStep(Number(value)),
1215
+ children: [/* @__PURE__ */ jsx(SelectTrigger, {
1216
+ "aria-label": "This color is",
1217
+ children: /* @__PURE__ */ jsx(SelectValue, {})
1218
+ }), /* @__PURE__ */ jsx(SelectContent, { children: SHADE_STEPS.map((step) => /* @__PURE__ */ jsx(SelectItem, {
1219
+ value: String(step),
1220
+ children: step
1221
+ }, step)) })]
1178
1222
  })]
1179
1223
  })
1180
1224
  ]
1181
- })
1225
+ }) })
1182
1226
  }) }) : null,
1183
1227
  replacing ? /* @__PURE__ */ jsx(ThemePortal, { children: /* @__PURE__ */ jsx("div", {
1184
1228
  className: "bl-library__modal",
@@ -1278,26 +1322,24 @@ const CSS$3 = `
1278
1322
  .bl-library__panel-head button{border:0;background:none;font:inherit;font-size:1.25rem;line-height:1;color:#94a3b8;cursor:pointer}
1279
1323
  .bl-library__panel h2{margin:0}
1280
1324
  .bl-library__panel p{margin:.5rem 0 0;color:#475569;font-size:14px}
1281
- .bl-library__panel label{display:flex;flex-direction:column;gap:.4rem;margin-top:1rem;font-size:13px;font-weight:600}
1325
+ .bl-library__panel label,.bl-library__field{display:flex;flex-direction:column;gap:.4rem;margin-top:1rem;font-size:13px;font-weight:600}
1282
1326
  .bl-library__panel input,.bl-library__panel select{min-height:2.25rem;border:1px solid #cbd5e1;border-radius:8px;padding:.35rem .5rem;background:#fff;color:#0f172a}
1283
1327
  .bl-library__usages{margin:1rem 0 0;padding-left:1.1rem;color:#475569;font-size:13px;font-weight:400}
1284
1328
  .bl-library__usages li{margin:.25rem 0}
1285
1329
  .bl-library__actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:.5rem;margin-top:1rem}
1286
1330
  .bl-library__actions button:last-child{border:0;background:#0f172a;color:#fff;font-weight:600}
1287
1331
  .bl-library__actions button:last-child:disabled{opacity:.4;cursor:not-allowed}
1332
+ .bl-theme-dialog .bl-nav-kit__modal{z-index:10000}
1288
1333
  `;
1289
1334
  //#endregion
1290
1335
  //#region src/admin/typography-pairing.tsx
1291
1336
  function FontRoleCard({ title, hint, required, family, preview, children }) {
1292
- return /* @__PURE__ */ jsxs("article", {
1337
+ return /* @__PURE__ */ jsx(KitProvider, { children: /* @__PURE__ */ jsxs("article", {
1293
1338
  className: "bl-font-role",
1294
1339
  children: [
1295
1340
  /* @__PURE__ */ jsxs("header", {
1296
1341
  className: "bl-font-role__head",
1297
- children: [/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("h3", { children: title }), /* @__PURE__ */ jsx("p", { children: hint })] }), required ? /* @__PURE__ */ jsx("span", {
1298
- className: "bl-font-role__badge",
1299
- children: "Required"
1300
- }) : null]
1342
+ children: [/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("h3", { children: title }), /* @__PURE__ */ jsx("p", { children: hint })] }), required ? /* @__PURE__ */ jsx(Badge, { children: "Required" }) : null]
1301
1343
  }),
1302
1344
  children,
1303
1345
  /* @__PURE__ */ jsxs("div", {
@@ -1313,7 +1355,7 @@ function FontRoleCard({ title, hint, required, family, preview, children }) {
1313
1355
  })]
1314
1356
  })
1315
1357
  ]
1316
- });
1358
+ }) });
1317
1359
  }
1318
1360
  function TypographyPairing({ headingFamily, bodyFamily }) {
1319
1361
  return /* @__PURE__ */ jsxs("section", {
@@ -1358,7 +1400,6 @@ const TYPOGRAPHY_PAIRING_CSS = `
1358
1400
  .bl-font-role__head{display:flex;justify-content:space-between;align-items:flex-start;gap:1rem}
1359
1401
  .bl-font-role__head h3{margin:0;font-size:1rem}
1360
1402
  .bl-font-role__head p{margin:.3rem 0 0;font-size:12px;color:var(--bl-preview-muted,var(--theme-elevation-500,#64748b))}
1361
- .bl-font-role__badge{font-size:10px;text-transform:uppercase;letter-spacing:.04em;padding:.15rem .45rem;border-radius:999px;background:#e2e8f0;color:#0f172a}
1362
1403
  .bl-font-role__preview{margin-top:1rem;padding:.85rem .9rem;border:1px solid var(--bl-preview-border,var(--theme-elevation-150,#e2e8f0));border-radius:12px;background:var(--bl-preview-bg,var(--theme-elevation-50,#f8fafc))}
1363
1404
  .bl-font-role__preview > span{display:block;margin-bottom:.4rem;font-size:11px;color:var(--bl-preview-muted,var(--theme-elevation-500,#64748b))}
1364
1405
  .bl-font-role__heading{margin:0;font-size:1.5rem;line-height:1.2;font-weight:600}
@@ -1889,7 +1930,7 @@ const SWATCHES = [
1889
1930
  ];
1890
1931
  function GreyScaleChoices({ value, onChange, readOnly }) {
1891
1932
  const canvas = usePreviewCanvas();
1892
- return /* @__PURE__ */ jsxs("section", {
1933
+ return /* @__PURE__ */ jsx(KitProvider, { children: /* @__PURE__ */ jsxs("section", {
1893
1934
  className: `bl-grey ${canvas.className}`,
1894
1935
  "data-preview": canvas.mode,
1895
1936
  style: canvas.style,
@@ -1900,10 +1941,7 @@ function GreyScaleChoices({ value, onChange, readOnly }) {
1900
1941
  }),
1901
1942
  /* @__PURE__ */ jsxs("header", {
1902
1943
  className: "bl-grey__head",
1903
- children: [/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("h2", { children: "Gray family" }), /* @__PURE__ */ jsx("p", { children: "Choose the neutral family used for page backgrounds, cards, borders, inputs, and muted text. Readability for these system neutrals is handled automatically." })] }), /* @__PURE__ */ jsx("span", {
1904
- className: "bl-grey__badge",
1905
- children: "Required"
1906
- })]
1944
+ children: [/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("h2", { children: "Gray family" }), /* @__PURE__ */ jsx("p", { children: "Choose the neutral family used for page backgrounds, cards, borders, inputs, and muted text. Readability for these system neutrals is handled automatically." })] }), /* @__PURE__ */ jsx(Badge, { children: "Required" })]
1907
1945
  }),
1908
1946
  /* @__PURE__ */ jsx("div", {
1909
1947
  className: "bl-grey__choices",
@@ -1937,7 +1975,7 @@ function GreyScaleChoices({ value, onChange, readOnly }) {
1937
1975
  })
1938
1976
  })
1939
1977
  ]
1940
- });
1978
+ }) });
1941
1979
  }
1942
1980
  /**
1943
1981
  * Visual picker over the five grey-scale presets. Replaces the stock select
@@ -1966,7 +2004,6 @@ const GREY_SCALE_FIELD_CSS = `
1966
2004
  .bl-grey__head{display:flex;justify-content:space-between;align-items:flex-start;gap:1rem;margin:0;padding:1.1rem 1.15rem .85rem;border:1px solid var(--bl-preview-border,var(--theme-elevation-150,#e2e8f0));border-bottom:0;border-radius:12px 12px 0 0;background:var(--bl-preview-card,var(--theme-elevation-0,#fff));color:var(--bl-preview-text,inherit)}
1967
2005
  .bl-grey__head h2{margin:0;font-size:1.1rem}
1968
2006
  .bl-grey__head p{margin:.35rem 0 0;max-width:40rem;font-size:13px;color:var(--bl-preview-muted,var(--theme-elevation-500,#64748b))}
1969
- .bl-grey__badge{font:inherit;font-size:10px;text-transform:uppercase;letter-spacing:.04em;padding:.15rem .45rem;border-radius:999px;background:#e2e8f0;color:#0f172a}
1970
2007
  .bl-grey__choices{display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:.6rem;padding:0 1.15rem 1.15rem;border:1px solid var(--bl-preview-border,var(--theme-elevation-150,#e2e8f0));border-top:0;border-radius:0 0 12px 12px;background:var(--bl-preview-card,var(--theme-elevation-0,#fff));color:var(--bl-preview-text,inherit)}
1971
2008
  .bl-grey__choice{display:flex;flex-direction:column;gap:.45rem;padding:.75rem;border:1px solid var(--bl-preview-border,var(--theme-elevation-150,#e2e8f0));border-radius:12px;background:var(--bl-preview-bg,var(--theme-elevation-0,#fff));color:inherit;cursor:pointer}
1972
2009
  .bl-grey__choice[data-selected]{box-shadow:inset 0 0 0 2px var(--bl-preview-text,#0f172a);border-color:var(--bl-preview-text,#0f172a)}
@@ -2325,9 +2362,8 @@ function ThemePublishActions({ child }) {
2325
2362
  /* @__PURE__ */ jsx("style", {
2326
2363
  href: "bl-publish-child",
2327
2364
  precedence: "default",
2328
- children: `${THEME_PREVIEW_TOGGLE_CSS}.bl-publish-child{display:flex;align-items:center;justify-content:flex-end;gap:.5rem}.bl-publish-child__button{min-height:2.25rem;padding:.4rem 1rem;border:0;border-radius:8px;background:var(--theme-text,#0f172a);color:var(--theme-bg,#fff);font:inherit;font-weight:600;cursor:pointer}`
2365
+ children: `.bl-publish-child{display:flex;align-items:center;justify-content:flex-end;gap:.5rem}.bl-publish-child__button{min-height:2.25rem;padding:.4rem 1rem;border:0;border-radius:8px;background:var(--theme-text,#0f172a);color:var(--theme-bg,#fff);font:inherit;font-weight:600;cursor:pointer}`
2329
2366
  }),
2330
- child !== "identity" ? /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(ThemePreviewToggle, {}) }) : null,
2331
2367
  /* @__PURE__ */ jsxs("button", {
2332
2368
  type: "button",
2333
2369
  className: "bl-publish-child__button",
@@ -2374,16 +2410,12 @@ function readActiveThemeTab(root = typeof document === "undefined" ? null : docu
2374
2410
  //#endregion
2375
2411
  //#region src/admin/save-button.tsx
2376
2412
  /**
2377
- * Light/Dark preview for Colors, Typography, and Appearance. Lives in
2378
- * `beforeDocumentControls` so Payload's Save stays in the Save slot.
2379
- * Save writes that page's slice onto the stored Theme row.
2413
+ * Theme used to put a Light/Dark pair here. Admin chrome now follows
2414
+ * Payload's own theme from the header sun/moon, so this slot is empty
2415
+ * and Save stays in the Save slot.
2380
2416
  */
2381
2417
  function ThemeDocumentControls() {
2382
- return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("style", {
2383
- href: "bl-doc-controls",
2384
- precedence: "default",
2385
- children: THEME_PREVIEW_TOGGLE_CSS
2386
- }), /* @__PURE__ */ jsx(ThemePreviewToggle, {})] });
2418
+ return null;
2387
2419
  }
2388
2420
  /**
2389
2421
  * Import-map Save slot used by the older single-Global Theme. Theme pages
@@ -2973,6 +3005,90 @@ const RoleSlugField = ({ field, path, readOnly }) => {
2973
3005
  });
2974
3006
  };
2975
3007
  //#endregion
3008
+ //#region src/fields/slug.ts
3009
+ const SLUG_UNLOCK_WARNING = "This page is live. Changing the address will send visitors from the old URL to the new one.";
3010
+ function slugifySegment(segment) {
3011
+ return segment.normalize("NFKD").replace(/\p{M}/gu, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
3012
+ }
3013
+ /**
3014
+ * A stored slug from whatever was typed: NFKD, hyphenated, slash segments
3015
+ * kept, so `Café / About Us` becomes `cafe/about-us`.
3016
+ */
3017
+ function normalizeSlug(value) {
3018
+ return value.split("/").map(slugifySegment).filter(Boolean).join("/");
3019
+ }
3020
+ //#endregion
3021
+ //#region src/admin/slug-field.tsx
3022
+ /**
3023
+ * Slug follows the title until first publish. After that it is read-only
3024
+ * until Unlock, which warns that the old address will redirect.
3025
+ */
3026
+ const SlugField = ({ field, path, readOnly }) => {
3027
+ const { value, setValue, showError, errorMessage } = useField({ path });
3028
+ const { value: unlock, setValue: setUnlock } = useField({ path: "slugUnlock" });
3029
+ const title = useFormFields(([fields]) => fields.title?.value);
3030
+ const slugLocked = Boolean(useFormFields(([fields]) => fields.slugLocked?.value));
3031
+ const locked = Boolean(readOnly) || slugLocked && !unlock;
3032
+ const required = Boolean(field.required);
3033
+ const id = `field-${path.replace(/\./g, "__")}`;
3034
+ useEffect(() => {
3035
+ if (slugLocked || typeof title !== "string") return;
3036
+ const next = normalizeSlug(title);
3037
+ if (next !== (value ?? "")) setValue(next);
3038
+ }, [
3039
+ setValue,
3040
+ slugLocked,
3041
+ title,
3042
+ value
3043
+ ]);
3044
+ return /* @__PURE__ */ jsxs("div", {
3045
+ className: [
3046
+ fieldBaseClass,
3047
+ "text",
3048
+ locked && "read-only"
3049
+ ].filter(Boolean).join(" "),
3050
+ children: [
3051
+ /* @__PURE__ */ jsx(FieldLabel, {
3052
+ htmlFor: id,
3053
+ label: field.label,
3054
+ required
3055
+ }),
3056
+ /* @__PURE__ */ jsxs("div", {
3057
+ className: `${fieldBaseClass}__wrap`,
3058
+ children: [
3059
+ /* @__PURE__ */ jsx(FieldError, {
3060
+ message: errorMessage,
3061
+ path,
3062
+ showError
3063
+ }),
3064
+ /* @__PURE__ */ jsx("input", {
3065
+ id,
3066
+ type: "text",
3067
+ autoComplete: "off",
3068
+ spellCheck: false,
3069
+ readOnly: locked,
3070
+ disabled: locked,
3071
+ value: value ?? "",
3072
+ onChange: (event) => {
3073
+ if (!locked) setValue(event.target.value);
3074
+ }
3075
+ }),
3076
+ slugLocked && !unlock ? /* @__PURE__ */ jsx(Button$1, {
3077
+ variant: "outline",
3078
+ onClick: () => setUnlock(true),
3079
+ children: "Unlock"
3080
+ }) : null
3081
+ ]
3082
+ }),
3083
+ unlock ? /* @__PURE__ */ jsx("p", { children: SLUG_UNLOCK_WARNING }) : null,
3084
+ /* @__PURE__ */ jsx(FieldDescription, {
3085
+ description: field.admin?.description,
3086
+ path
3087
+ })
3088
+ ]
3089
+ });
3090
+ };
3091
+ //#endregion
2976
3092
  //#region src/admin/role-name-field.tsx
2977
3093
  /**
2978
3094
  * Role name. Letters and spaces only, so the hidden slug stays
@@ -3316,6 +3432,44 @@ const FeaturesMatrixField = (props) => {
3316
3432
  });
3317
3433
  };
3318
3434
  //#endregion
3435
+ //#region src/admin/theme-kit-accent.tsx
3436
+ function primaryHex(doc) {
3437
+ const hex = doc?.colors?.brand?.primary?.hex;
3438
+ return typeof hex === "string" && isThemeHex(hex) ? hex : void 0;
3439
+ }
3440
+ /**
3441
+ * Site Theme primary → `--bl-kit-accent`. Saved value for the whole
3442
+ * admin; the Primary color card also writes the live hex so pickers
3443
+ * and kit chrome update as the field is edited.
3444
+ */
3445
+ function ThemeKitAccent({ children }) {
3446
+ const { config } = useConfig();
3447
+ const [accent, setAccent] = useState();
3448
+ useEffect(() => {
3449
+ const url = `${config.serverURL}${config.routes.api}/globals/theme?depth=0`;
3450
+ let cancelled = false;
3451
+ function load() {
3452
+ fetch(url, { credentials: "include" }).then((response) => response.ok ? response.json() : null).then((doc) => {
3453
+ const hex = primaryHex(doc);
3454
+ if (!cancelled && hex) setAccent(hex);
3455
+ }).catch(() => void 0);
3456
+ }
3457
+ load();
3458
+ const onVis = () => {
3459
+ if (document.visibilityState === "visible") load();
3460
+ };
3461
+ document.addEventListener("visibilitychange", onVis);
3462
+ return () => {
3463
+ cancelled = true;
3464
+ document.removeEventListener("visibilitychange", onVis);
3465
+ };
3466
+ }, [config.serverURL, config.routes.api]);
3467
+ return /* @__PURE__ */ jsx(KitProvider, {
3468
+ accent,
3469
+ children
3470
+ });
3471
+ }
3472
+ //#endregion
3319
3473
  //#region src/admin/document-title-actions.tsx
3320
3474
  /**
3321
3475
  * Shared document header: title on the left, primary actions on the
@@ -3357,7 +3511,7 @@ function DocumentTitleActions({ children }) {
3357
3511
  });
3358
3512
  return () => observer.disconnect();
3359
3513
  }, []);
3360
- return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("style", {
3514
+ return /* @__PURE__ */ jsxs(ThemeKitAccent, { children: [/* @__PURE__ */ jsx("style", {
3361
3515
  href: "bl-doc-title-actions",
3362
3516
  precedence: "default",
3363
3517
  children: TITLE_ACTIONS_CSS
@@ -3497,6 +3651,8 @@ function resolveAdminNav({ config, doc, visibleEntities }) {
3497
3651
  //#endregion
3498
3652
  //#region src/admin/admin-nav.tsx
3499
3653
  const BASE = "nav";
3654
+ /** Survives AdminNav remounting on every admin route so the first-seen walk does not flash. */
3655
+ let cachedDoc;
3500
3656
  function isAbortError(error) {
3501
3657
  return error instanceof DOMException ? error.name === "AbortError" : error instanceof Error && error.name === "AbortError";
3502
3658
  }
@@ -3529,7 +3685,7 @@ function AdminNavShell({ children }) {
3529
3685
  */
3530
3686
  function AdminNav({ visibleEntities }) {
3531
3687
  const { config } = useConfig();
3532
- const [doc, setDoc] = useState(null);
3688
+ const [doc, setDoc] = useState(() => cachedDoc ?? null);
3533
3689
  const adminRoute = config.routes.admin ?? "/admin";
3534
3690
  const apiRoute = config.routes.api ?? "/api";
3535
3691
  useEffect(() => {
@@ -3540,9 +3696,12 @@ function AdminNav({ visibleEntities }) {
3540
3696
  signal: controller.signal
3541
3697
  }).then((response) => response.ok ? response.json() : null).then((payload) => {
3542
3698
  if (controller.signal.aborted) return;
3699
+ cachedDoc = payload;
3543
3700
  setDoc(payload);
3544
3701
  }).catch((error) => {
3545
3702
  if (isAbortError(error) || controller.signal.aborted) return;
3703
+ if (cachedDoc !== void 0) return;
3704
+ cachedDoc = null;
3546
3705
  setDoc(null);
3547
3706
  });
3548
3707
  return () => controller.abort();
@@ -3669,6 +3828,6 @@ const AdminNavEntityField = ({ field, path, permissions, readOnly, schemaPath })
3669
3828
  });
3670
3829
  };
3671
3830
  //#endregion
3672
- export { AdminNav, AdminNavEntityField, AdminNavItemRowLabel, AdminNavRowLabel, AppearanceField, ColorField, ColorScaleField, ContrastReport, DocumentCreateNew, DocumentTitleActions, FeaturesMatrixField, FontField, GreyScaleField, HiddenSaveButton, IdentityFallback, LibraryField, LookField, PairingField, PublishChild, RoleNameField, RoleSlugField, RolesField, RolesGrantsField, RolesMatrixField, RolesRowLabel, SectionHeading, ThemeDocumentControls, ThemeSaveButton };
3831
+ export { AdminNav, AdminNavEntityField, AdminNavItemRowLabel, AdminNavRowLabel, AppearanceField, ColorField, ColorScaleField, ContrastReport, DocumentCreateNew, DocumentTitleActions, FeaturesMatrixField, FontField, GreyScaleField, HiddenSaveButton, IdentityFallback, LibraryField, LookField, PairingField, PublishChild, RoleNameField, RoleSlugField, RolesField, RolesGrantsField, RolesMatrixField, RolesRowLabel, SectionHeading, SlugField, ThemeDocumentControls, ThemeSaveButton, setThemePreviewMode };
3673
3832
 
3674
3833
  //# sourceMappingURL=admin.mjs.map