@nuvio/overlay 0.5.3 → 0.5.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.css CHANGED
@@ -1012,21 +1012,16 @@ select.nuvio-control {
1012
1012
  border: 1px solid rgba(255, 255, 255, 0.2);
1013
1013
  }
1014
1014
  .nuvio-color-popover {
1015
- position: absolute;
1016
- left: 0;
1017
- right: 0;
1018
- z-index: 20;
1019
- margin-top: 4px;
1020
- max-height: min(20rem, 50vh);
1021
1015
  overflow: auto;
1022
1016
  border-radius: var(--nuvio-radius-inner);
1023
1017
  border: 1px solid var(--nuvio-border-strong);
1024
- background: rgba(3, 7, 18, 0.94);
1025
- -webkit-backdrop-filter: var(--nuvio-inner-blur);
1026
- backdrop-filter: var(--nuvio-inner-blur);
1018
+ background: rgb(15, 23, 42);
1027
1019
  padding: 10px;
1028
1020
  box-shadow: 0 28px 80px rgba(0, 0, 0, 0.42);
1029
1021
  }
1022
+ .nuvio-color-popover--fixed {
1023
+ pointer-events: auto;
1024
+ }
1030
1025
  .nuvio-color-specials {
1031
1026
  display: flex;
1032
1027
  flex-wrap: wrap;
@@ -1136,7 +1131,10 @@ select.nuvio-control {
1136
1131
  color: var(--nuvio-text-muted);
1137
1132
  }
1138
1133
  .nuvio-card--actions {
1134
+ position: relative;
1135
+ z-index: 1;
1139
1136
  border-color: rgba(56, 189, 248, 0.25);
1137
+ background: rgb(15, 23, 42);
1140
1138
  }
1141
1139
  .nuvio-action-stack {
1142
1140
  display: flex;
package/dist/index.js CHANGED
@@ -28,12 +28,12 @@ function loadOverlayStyles() {
28
28
  import {
29
29
  useCallback as useCallback3,
30
30
  useEffect as useEffect9,
31
- useLayoutEffect as useLayoutEffect2,
31
+ useLayoutEffect as useLayoutEffect3,
32
32
  useMemo as useMemo6,
33
33
  useRef as useRef5,
34
34
  useState as useState9
35
35
  } from "react";
36
- import { createPortal } from "react-dom";
36
+ import { createPortal as createPortal2 } from "react-dom";
37
37
  import {
38
38
  NUVIO_WS_PATH,
39
39
  PROTOCOL_VERSION,
@@ -409,7 +409,7 @@ function useNuvioShadowMount() {
409
409
  import {
410
410
  useCallback as useCallback2,
411
411
  useEffect as useEffect8,
412
- useLayoutEffect,
412
+ useLayoutEffect as useLayoutEffect2,
413
413
  useMemo as useMemo5,
414
414
  useRef as useRef4,
415
415
  useState as useState8
@@ -641,6 +641,23 @@ function parseClassNameByBreakpoint(className) {
641
641
  }
642
642
  return buckets;
643
643
  }
644
+ function classNameHasResponsiveUtilities(className) {
645
+ const buckets = parseClassNameByBreakpoint(className);
646
+ return BREAKPOINT_ORDER.some((bp) => bp !== "base" && buckets[bp].length > 0);
647
+ }
648
+ var INTERACTIVE_VARIANT_RE = /^(?:hover|focus|focus-within|active|disabled|group-hover|peer-hover|first|last|odd|even):/;
649
+ function isInteractiveVariantToken(token) {
650
+ return INTERACTIVE_VARIANT_RE.test(token);
651
+ }
652
+ function isDarkVariantToken(token) {
653
+ return /^dark:/.test(token);
654
+ }
655
+ function includeDarkVariantsInRead() {
656
+ if (typeof document === "undefined") {
657
+ return false;
658
+ }
659
+ return document.documentElement.classList.contains("dark");
660
+ }
644
661
  function stripVariantPrefixes(token) {
645
662
  let t = token;
646
663
  for (; ; ) {
@@ -657,18 +674,26 @@ function flattenTokensAtBreakpoint(className, activeBreakpoint) {
657
674
  const buckets = parseClassNameByBreakpoint(className);
658
675
  const idx = BREAKPOINT_ORDER.indexOf(activeBreakpoint);
659
676
  const out = [];
677
+ const includeDark = includeDarkVariantsInRead();
660
678
  for (const tok of buckets.passthrough) {
661
- if (/^dark:/.test(tok)) {
662
- out.push(stripVariantPrefixes(tok));
679
+ if (isInteractiveVariantToken(tok)) {
680
+ continue;
681
+ }
682
+ if (isDarkVariantToken(tok)) {
683
+ if (includeDark) {
684
+ out.push(stripVariantPrefixes(tok));
685
+ }
686
+ continue;
663
687
  }
664
688
  }
665
689
  for (let i = 0; i <= idx; i++) {
666
690
  out.push(...buckets[BREAKPOINT_ORDER[i]]);
667
691
  }
668
692
  for (const tok of buckets.passthrough) {
669
- if (!/^dark:/.test(tok)) {
670
- out.push(stripVariantPrefixes(tok));
693
+ if (isInteractiveVariantToken(tok) || isDarkVariantToken(tok)) {
694
+ continue;
671
695
  }
696
+ out.push(stripVariantPrefixes(tok));
672
697
  }
673
698
  return out;
674
699
  }
@@ -3801,7 +3826,14 @@ function dismissGuide(id) {
3801
3826
  }
3802
3827
 
3803
3828
  // src/ColorPickerRow.tsx
3804
- import { useEffect as useEffect7, useId, useRef as useRef3, useState as useState7 } from "react";
3829
+ import {
3830
+ useEffect as useEffect7,
3831
+ useId,
3832
+ useLayoutEffect,
3833
+ useRef as useRef3,
3834
+ useState as useState7
3835
+ } from "react";
3836
+ import { createPortal } from "react-dom";
3805
3837
  import { jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
3806
3838
  function hexForUtility(value, options) {
3807
3839
  if (!value) {
@@ -3831,6 +3863,29 @@ function hexForUtility(value, options) {
3831
3863
  function familyLabel(family) {
3832
3864
  return family.charAt(0).toUpperCase() + family.slice(1);
3833
3865
  }
3866
+ function getOverlayPortalRoot() {
3867
+ const host = document.getElementById(NUVIO_SHADOW_HOST_ID);
3868
+ const mount = host?.shadowRoot?.querySelector(".nuvio-shadow-mount");
3869
+ return mount instanceof HTMLElement ? mount : null;
3870
+ }
3871
+ function measurePopoverBox(trigger) {
3872
+ const rect = trigger.getBoundingClientRect();
3873
+ const margin = 8;
3874
+ const preferredMax = 320;
3875
+ const minWidth = 300;
3876
+ const spaceBelow = window.innerHeight - rect.bottom - margin;
3877
+ const spaceAbove = rect.top - margin;
3878
+ const placeAbove = spaceBelow < 200 && spaceAbove > spaceBelow;
3879
+ const maxHeight = Math.max(120, Math.min(preferredMax, placeAbove ? spaceAbove : spaceBelow));
3880
+ const width = Math.min(Math.max(rect.width, minWidth), window.innerWidth - margin * 2);
3881
+ let left = rect.left;
3882
+ if (left + width > window.innerWidth - margin) {
3883
+ left = window.innerWidth - margin - width;
3884
+ }
3885
+ left = Math.max(margin, left);
3886
+ const top = placeAbove ? Math.max(margin, rect.top - maxHeight - 4) : rect.bottom + 4;
3887
+ return { top, left, width, maxHeight };
3888
+ }
3834
3889
  function ColorPickerRow({
3835
3890
  label,
3836
3891
  value,
@@ -3840,10 +3895,36 @@ function ColorPickerRow({
3840
3895
  simpleMode = false
3841
3896
  }) {
3842
3897
  const [open, setOpen] = useState7(false);
3898
+ const [popoverBox, setPopoverBox] = useState7(null);
3843
3899
  const rootRef = useRef3(null);
3844
3900
  const listId = useId();
3845
3901
  const swatchHex = hexForUtility(value, options);
3846
3902
  const displayValue = simpleMode ? value ? options.find((o) => o.value === value)?.label ?? "Custom" : "Default" : value || "\u2014";
3903
+ const updatePopoverBox = () => {
3904
+ const row = rootRef.current;
3905
+ if (!row) {
3906
+ return;
3907
+ }
3908
+ const trigger = row.querySelector(".nuvio-color-trigger");
3909
+ if (!(trigger instanceof HTMLElement)) {
3910
+ return;
3911
+ }
3912
+ setPopoverBox(measurePopoverBox(trigger));
3913
+ };
3914
+ useLayoutEffect(() => {
3915
+ if (!open) {
3916
+ setPopoverBox(null);
3917
+ return;
3918
+ }
3919
+ updatePopoverBox();
3920
+ window.addEventListener("resize", updatePopoverBox);
3921
+ const panelBody = rootRef.current?.closest(".nuvio-panel-body");
3922
+ panelBody?.addEventListener("scroll", updatePopoverBox, { passive: true });
3923
+ return () => {
3924
+ window.removeEventListener("resize", updatePopoverBox);
3925
+ panelBody?.removeEventListener("scroll", updatePopoverBox);
3926
+ };
3927
+ }, [open]);
3847
3928
  useEffect7(() => {
3848
3929
  if (!open) {
3849
3930
  return;
@@ -3854,7 +3935,9 @@ function ColorPickerRow({
3854
3935
  return;
3855
3936
  }
3856
3937
  const path = typeof e.composedPath === "function" ? e.composedPath() : [];
3857
- const clickedInside = path.includes(root) || root.contains(e.target);
3938
+ const clickedInside = path.includes(root) || root.contains(e.target) || path.some(
3939
+ (node) => node instanceof HTMLElement && (node.id === listId || node.classList.contains("nuvio-color-popover"))
3940
+ );
3858
3941
  if (!clickedInside) {
3859
3942
  setOpen(false);
3860
3943
  }
@@ -3869,6 +3952,89 @@ function ColorPickerRow({
3869
3952
  onChange(next);
3870
3953
  setOpen(false);
3871
3954
  };
3955
+ const popoverStyle = popoverBox ? {
3956
+ position: "fixed",
3957
+ top: popoverBox.top,
3958
+ left: popoverBox.left,
3959
+ width: popoverBox.width,
3960
+ maxHeight: popoverBox.maxHeight,
3961
+ zIndex: 2147483640
3962
+ } : void 0;
3963
+ const popover = open && popoverBox ? /* @__PURE__ */ jsxs13(
3964
+ "div",
3965
+ {
3966
+ id: listId,
3967
+ role: "listbox",
3968
+ "aria-label": `${label} palette`,
3969
+ className: "nuvio-color-popover nuvio-color-popover--fixed",
3970
+ style: popoverStyle,
3971
+ children: [
3972
+ !simpleMode ? /* @__PURE__ */ jsxs13(
3973
+ "p",
3974
+ {
3975
+ className: "nuvio-text-3xs nuvio-leading-snug nuvio-text-muted",
3976
+ style: { marginBottom: 8 },
3977
+ children: [
3978
+ "Tailwind palette \u2014 picks a utility class (e.g. ",
3979
+ utilityPrefix,
3980
+ "-sky-500), not a custom hex value."
3981
+ ]
3982
+ }
3983
+ ) : null,
3984
+ /* @__PURE__ */ jsx13("div", { className: "nuvio-color-specials", children: specials.map((o) => /* @__PURE__ */ jsxs13(
3985
+ "button",
3986
+ {
3987
+ type: "button",
3988
+ role: "option",
3989
+ "aria-selected": value === o.value,
3990
+ title: o.label,
3991
+ className: `nuvio-color-special-btn ${value === o.value ? "nuvio-color-special-btn--active" : ""}`,
3992
+ onClick: () => pick(o.value),
3993
+ children: [
3994
+ /* @__PURE__ */ jsx13("span", { className: "nuvio-color-swatch--sm", style: { backgroundColor: o.hex } }),
3995
+ o.label
3996
+ ]
3997
+ },
3998
+ o.value || "__none"
3999
+ )) }),
4000
+ /* @__PURE__ */ jsxs13(
4001
+ "div",
4002
+ {
4003
+ className: "nuvio-palette-grid",
4004
+ style: {
4005
+ gridTemplateColumns: `3.25rem repeat(${TAILWIND_COLOR_SHADES.length}, minmax(0, 1fr))`
4006
+ },
4007
+ children: [
4008
+ /* @__PURE__ */ jsx13("span", {}),
4009
+ TAILWIND_COLOR_SHADES.map((s) => /* @__PURE__ */ jsx13("span", { className: "nuvio-palette-shade", children: s }, s)),
4010
+ TAILWIND_COLOR_FAMILIES.map((family) => /* @__PURE__ */ jsxs13("div", { className: "nuvio-palette-contents", children: [
4011
+ /* @__PURE__ */ jsx13("span", { className: "nuvio-palette-family", children: familyLabel(family) }),
4012
+ TAILWIND_COLOR_SHADES.map((shade) => {
4013
+ const util = `${utilityPrefix}-${family}-${shade}`;
4014
+ const hex = TAILWIND_PALETTE_HEX[family][String(shade)];
4015
+ const selected = value === util || value.replace(/\/(?:\d+|\[[\d.]+\])$/, "") === util;
4016
+ return /* @__PURE__ */ jsx13(
4017
+ "button",
4018
+ {
4019
+ type: "button",
4020
+ role: "option",
4021
+ "aria-selected": selected,
4022
+ title: simpleMode ? `${familyLabel(family)} ${shade}` : util,
4023
+ className: `nuvio-palette-swatch ${selected ? "nuvio-palette-swatch--selected" : ""}`,
4024
+ style: { backgroundColor: hex },
4025
+ onClick: () => pick(util)
4026
+ },
4027
+ util
4028
+ );
4029
+ })
4030
+ ] }, family))
4031
+ ]
4032
+ }
4033
+ )
4034
+ ]
4035
+ }
4036
+ ) : null;
4037
+ const portalRoot = open ? getOverlayPortalRoot() : null;
3872
4038
  return /* @__PURE__ */ jsxs13("div", { ref: rootRef, className: "nuvio-field-row nuvio-field-row--start", children: [
3873
4039
  /* @__PURE__ */ jsx13("span", { className: "nuvio-label nuvio-label--pad-top", children: label }),
3874
4040
  /* @__PURE__ */ jsxs13("div", { className: "nuvio-min-w-0 nuvio-relative", children: [
@@ -3899,84 +4065,15 @@ function ColorPickerRow({
3899
4065
  ]
3900
4066
  }
3901
4067
  ),
3902
- open ? /* @__PURE__ */ jsxs13(
3903
- "div",
3904
- {
3905
- id: listId,
3906
- role: "listbox",
3907
- "aria-label": `${label} palette`,
3908
- className: "nuvio-color-popover",
3909
- children: [
3910
- !simpleMode ? /* @__PURE__ */ jsxs13("p", { className: "nuvio-text-3xs nuvio-leading-snug nuvio-text-muted", style: { marginBottom: 8 }, children: [
3911
- "Tailwind palette \u2014 picks a utility class (e.g. ",
3912
- utilityPrefix,
3913
- "-sky-500), not a custom hex value."
3914
- ] }) : null,
3915
- /* @__PURE__ */ jsx13("div", { className: "nuvio-color-specials", children: specials.map((o) => /* @__PURE__ */ jsxs13(
3916
- "button",
3917
- {
3918
- type: "button",
3919
- role: "option",
3920
- "aria-selected": value === o.value,
3921
- title: o.label,
3922
- className: `nuvio-color-special-btn ${value === o.value ? "nuvio-color-special-btn--active" : ""}`,
3923
- onClick: () => pick(o.value),
3924
- children: [
3925
- /* @__PURE__ */ jsx13(
3926
- "span",
3927
- {
3928
- className: "nuvio-color-swatch--sm",
3929
- style: { backgroundColor: o.hex }
3930
- }
3931
- ),
3932
- o.label
3933
- ]
3934
- },
3935
- o.value || "__none"
3936
- )) }),
3937
- /* @__PURE__ */ jsxs13(
3938
- "div",
3939
- {
3940
- className: "nuvio-palette-grid",
3941
- style: {
3942
- gridTemplateColumns: `3.25rem repeat(${TAILWIND_COLOR_SHADES.length}, minmax(0, 1fr))`
3943
- },
3944
- children: [
3945
- /* @__PURE__ */ jsx13("span", {}),
3946
- TAILWIND_COLOR_SHADES.map((s) => /* @__PURE__ */ jsx13("span", { className: "nuvio-palette-shade", children: s }, s)),
3947
- TAILWIND_COLOR_FAMILIES.map((family) => /* @__PURE__ */ jsxs13("div", { className: "nuvio-palette-contents", children: [
3948
- /* @__PURE__ */ jsx13("span", { className: "nuvio-palette-family", children: familyLabel(family) }),
3949
- TAILWIND_COLOR_SHADES.map((shade) => {
3950
- const util = `${utilityPrefix}-${family}-${shade}`;
3951
- const hex = TAILWIND_PALETTE_HEX[family][String(shade)];
3952
- const selected = value === util || value.replace(/\/(?:\d+|\[[\d.]+\])$/, "") === util;
3953
- return /* @__PURE__ */ jsx13(
3954
- "button",
3955
- {
3956
- type: "button",
3957
- role: "option",
3958
- "aria-selected": selected,
3959
- title: simpleMode ? `${familyLabel(family)} ${shade}` : util,
3960
- className: `nuvio-palette-swatch ${selected ? "nuvio-palette-swatch--selected" : ""}`,
3961
- style: { backgroundColor: hex },
3962
- onClick: () => pick(util)
3963
- },
3964
- util
3965
- );
3966
- })
3967
- ] }, family))
3968
- ]
3969
- }
3970
- )
3971
- ]
3972
- }
3973
- ) : null
4068
+ portalRoot && popover ? createPortal(popover, portalRoot) : null
3974
4069
  ] })
3975
4070
  ] });
3976
4071
  }
3977
4072
 
3978
4073
  // src/PropertyPanelShell.tsx
3979
4074
  import { Fragment as Fragment5, jsx as jsx14, jsxs as jsxs14 } from "react/jsx-runtime";
4075
+ var NUVO_PREVIEW_CHANGES_LABEL = "Preview Changes";
4076
+ var NUVO_APPLY_TO_CODE_LABEL = "Apply to Code";
3980
4077
  function assignShellRef(shellRef, el) {
3981
4078
  shellRef.current = el;
3982
4079
  }
@@ -4015,6 +4112,77 @@ function SelectRow({
4015
4112
  )
4016
4113
  ] });
4017
4114
  }
4115
+ function DeviceBreakpointPanel({
4116
+ devicePreset,
4117
+ onDevicePresetChange,
4118
+ activeBreakpoint,
4119
+ onActiveBreakpointChange,
4120
+ developerDetails,
4121
+ variant
4122
+ }) {
4123
+ const controls = /* @__PURE__ */ jsxs14(Fragment5, { children: [
4124
+ /* @__PURE__ */ jsxs14("div", { className: "nuvio-row-wrap", children: [
4125
+ /* @__PURE__ */ jsx14(
4126
+ "button",
4127
+ {
4128
+ type: "button",
4129
+ className: `nuvio-button-chip ${devicePreset === "desktop" ? "nuvio-button-chip--active" : ""}`,
4130
+ onClick: () => onDevicePresetChange("desktop"),
4131
+ children: "Desktop"
4132
+ }
4133
+ ),
4134
+ /* @__PURE__ */ jsx14(
4135
+ "button",
4136
+ {
4137
+ type: "button",
4138
+ className: `nuvio-button-chip ${devicePreset === "tablet" ? "nuvio-button-chip--active" : ""}`,
4139
+ onClick: () => onDevicePresetChange("tablet"),
4140
+ children: "Tablet"
4141
+ }
4142
+ ),
4143
+ /* @__PURE__ */ jsx14(
4144
+ "button",
4145
+ {
4146
+ type: "button",
4147
+ className: `nuvio-button-chip ${devicePreset === "mobile" ? "nuvio-button-chip--active" : ""}`,
4148
+ onClick: () => onDevicePresetChange("mobile"),
4149
+ children: "Mobile"
4150
+ }
4151
+ )
4152
+ ] }),
4153
+ /* @__PURE__ */ jsx14(
4154
+ SelectRow,
4155
+ {
4156
+ label: developerDetails ? "Active BP" : "Applies on",
4157
+ value: activeBreakpoint,
4158
+ onChange: (v) => onActiveBreakpointChange(v),
4159
+ options: [
4160
+ { value: "base", label: developerDetails ? "base" : formatPlainBreakpointLabel("base") },
4161
+ { value: "sm", label: developerDetails ? "sm" : formatPlainBreakpointLabel("sm") },
4162
+ { value: "md", label: developerDetails ? "md" : formatPlainBreakpointLabel("md") },
4163
+ { value: "lg", label: developerDetails ? "lg" : formatPlainBreakpointLabel("lg") },
4164
+ { value: "xl", label: developerDetails ? "xl" : formatPlainBreakpointLabel("xl") }
4165
+ ],
4166
+ developerDetails
4167
+ }
4168
+ ),
4169
+ !developerDetails ? /* @__PURE__ */ jsxs14("p", { className: "nuvio-text-2xs nuvio-text-muted", children: [
4170
+ "Applies on:",
4171
+ " ",
4172
+ /* @__PURE__ */ jsx14("span", { className: "nuvio-font-medium", children: formatPlainBreakpointLabel(activeBreakpoint) })
4173
+ ] }) : null
4174
+ ] });
4175
+ if (variant === "compact") {
4176
+ return /* @__PURE__ */ jsxs14("div", { className: "nuvio-stack-1", children: [
4177
+ /* @__PURE__ */ jsx14("p", { className: "nuvio-label", children: "Responsive preview" }),
4178
+ controls
4179
+ ] });
4180
+ }
4181
+ return /* @__PURE__ */ jsxs14("section", { className: "nuvio-card nuvio-stack-2", children: [
4182
+ /* @__PURE__ */ jsx14("h3", { className: "nuvio-section-title", children: "Device + breakpoint" }),
4183
+ controls
4184
+ ] });
4185
+ }
4018
4186
  function PropertyPanelShell({
4019
4187
  devicePreset,
4020
4188
  onDevicePresetChange,
@@ -4130,8 +4298,8 @@ function PropertyPanelShell({
4130
4298
  buildHumanPreviewLines({ baselineText, draftText, baselinePicks, draftPicks: picks })
4131
4299
  );
4132
4300
  }, [baselinePicks, baselineText, developerDetails, draftText, picks]);
4133
- const previewButtonLabel = developerDetails ? "Validate" : "Preview Changes";
4134
- const applyButtonLabel = developerDetails ? "Apply" : "Apply to Code";
4301
+ const previewButtonLabel = NUVO_PREVIEW_CHANGES_LABEL;
4302
+ const applyButtonLabel = NUVO_APPLY_TO_CODE_LABEL;
4135
4303
  const simpleMode = !developerDetails;
4136
4304
  const selectionTitle = selectedId && selectedEntry ? formatSelectionTitle(selectedId, selectedEntry, indexEntries) : null;
4137
4305
  const showEditSection = selectedId && !missing && (developerDetails || !taskRouter.active || taskRouter.showTaskControls);
@@ -4197,6 +4365,20 @@ function PropertyPanelShell({
4197
4365
  }
4198
4366
  return containerStyleId ?? textStyleId ?? selectedId;
4199
4367
  }, [containerStyleId, selectedId, styleTargetMode, textStyleId]);
4368
+ const [styleHostClassName, setStyleHostClassName] = useState8("");
4369
+ useEffect8(() => {
4370
+ if (!selectedId) {
4371
+ setStyleHostClassName("");
4372
+ return;
4373
+ }
4374
+ const styleId = patchStyleId ?? selectedId;
4375
+ const el = document.querySelector(`[data-nuvio-id="${escapeAttrSelector(styleId)}"]`);
4376
+ setStyleHostClassName(el instanceof HTMLElement ? el.className : "");
4377
+ }, [selectedId, patchStyleId, stagedVersion]);
4378
+ const showResponsiveDeviceControls = useMemo5(
4379
+ () => classNameHasResponsiveUtilities(styleHostClassName),
4380
+ [styleHostClassName]
4381
+ );
4200
4382
  useEffect8(() => {
4201
4383
  if (!selectedId) {
4202
4384
  setMissing(false);
@@ -4325,7 +4507,7 @@ function PropertyPanelShell({
4325
4507
  }
4326
4508
  if (hasText && hasStyle && patchTextId !== patchStyleId) {
4327
4509
  return {
4328
- error: developerDetails ? "Text and styles target different elements. Validate and apply text first, then edit styles (or pick a single element in Edit target)." : "Text and styles apply to different parts. Preview text first, then change styles."
4510
+ error: developerDetails ? "Text and styles target different elements. Preview and apply text first, then edit styles (or pick a single element in Edit target)." : "Text and styles apply to different parts. Preview text first, then change styles."
4329
4511
  };
4330
4512
  }
4331
4513
  const id = hasText ? patchTextId : patchStyleId;
@@ -4465,7 +4647,7 @@ function PropertyPanelShell({
4465
4647
  canMoveDown: false,
4466
4648
  peerCount: 0
4467
4649
  }));
4468
- useLayoutEffect(() => {
4650
+ useLayoutEffect2(() => {
4469
4651
  if (!selectedId || missing) {
4470
4652
  setSiblingMove({ canMoveUp: false, canMoveDown: false, peerCount: 0 });
4471
4653
  return;
@@ -4973,7 +5155,7 @@ function PropertyPanelShell({
4973
5155
  " after each edit so the summary matches what you apply."
4974
5156
  ] }) : null,
4975
5157
  hasStagedOps && !displayPatchBlockedReason && previewApplyMismatch && simpleMode ? /* @__PURE__ */ jsx14("p", { className: "nuvio-text-2xs nuvio-text-muted", children: "Preview your changes before applying." }) : null,
4976
- patchTargetConflict ? /* @__PURE__ */ jsx14("p", { className: "nuvio-banner nuvio-banner--warn nuvio-text-2xs nuvio-leading-snug", children: developerDetails ? "Text and styles apply to different elements. Validate text first, then change styles \u2014 or pick one target in Edit target." : "Text and styles apply to different parts. Preview text first, then change styles." }) : null,
5158
+ patchTargetConflict ? /* @__PURE__ */ jsx14("p", { className: "nuvio-banner nuvio-banner--warn nuvio-text-2xs nuvio-leading-snug", children: developerDetails ? "Text and styles apply to different elements. Preview text first, then change styles \u2014 or pick one target in Edit target." : "Text and styles apply to different parts. Preview text first, then change styles." }) : null,
4977
5159
  patchTargetError ? /* @__PURE__ */ jsx14("p", { className: "nuvio-banner nuvio-banner--error nuvio-text-2xs", children: developerDetails ? patchTargetError : patchTargetError.startsWith("Nuvio can't") || patchTargetError.startsWith("Nothing") || patchTargetError.startsWith("No changes") || patchTargetError.startsWith("Text and styles") || patchTargetError.startsWith("This part") ? patchTargetError : getSimpleSelectErrorMessage(patchTargetError) ?? patchTargetError }) : null,
4978
5160
  patchStyleId && selectedId && patchStyleId !== selectedId && developerDetails ? /* @__PURE__ */ jsxs14("p", { className: "nuvio-text-2xs nuvio-text-muted nuvio-leading-snug", children: [
4979
5161
  "Styles apply to",
@@ -5379,7 +5561,7 @@ function PropertyPanelShell({
5379
5561
  ] })
5380
5562
  ] }) : null,
5381
5563
  previewSummary && !structuralPreviewActive && developerDetails ? /* @__PURE__ */ jsxs14("div", { className: "nuvio-preview-box", children: [
5382
- /* @__PURE__ */ jsx14("p", { className: "nuvio-preview-box-title", children: developerDetails ? "Validated change" : "Preview Changes" }),
5564
+ /* @__PURE__ */ jsx14("p", { className: "nuvio-preview-box-title", children: "Ready to apply" }),
5383
5565
  /* @__PURE__ */ jsx14("p", { className: "nuvio-preview-box-body", children: developerDetails ? previewSummary : humanPreviewBlock || previewSummary })
5384
5566
  ] }) : null,
5385
5567
  developerDetails ? /* @__PURE__ */ jsxs14("div", { className: "nuvio-row-wrap nuvio-pt-2", children: [
@@ -5506,29 +5688,17 @@ function PropertyPanelShell({
5506
5688
  simpleMode && selectedId ? /* @__PURE__ */ jsxs14("details", { className: "nuvio-card nuvio-advanced-panel", children: [
5507
5689
  /* @__PURE__ */ jsx14("summary", { className: "nuvio-section-title nuvio-advanced-summary", children: "Advanced" }),
5508
5690
  /* @__PURE__ */ jsxs14("div", { className: "nuvio-stack-2 nuvio-pt-1", children: [
5509
- /* @__PURE__ */ jsxs14("div", { className: "nuvio-stack-1", children: [
5510
- /* @__PURE__ */ jsx14("p", { className: "nuvio-label", children: "Responsive preview" }),
5511
- /* @__PURE__ */ jsxs14("div", { className: "nuvio-row-wrap", children: [
5512
- /* @__PURE__ */ jsx14(
5513
- "button",
5514
- {
5515
- type: "button",
5516
- className: `nuvio-button-chip ${devicePreset === "desktop" ? "nuvio-button-chip--active" : ""}`,
5517
- onClick: () => onDevicePresetChange("desktop"),
5518
- children: "Desktop"
5519
- }
5520
- ),
5521
- /* @__PURE__ */ jsx14(
5522
- "button",
5523
- {
5524
- type: "button",
5525
- className: `nuvio-button-chip ${devicePreset === "mobile" ? "nuvio-button-chip--active" : ""}`,
5526
- onClick: () => onDevicePresetChange("mobile"),
5527
- children: "Mobile"
5528
- }
5529
- )
5530
- ] })
5531
- ] }),
5691
+ showResponsiveDeviceControls ? /* @__PURE__ */ jsx14(
5692
+ DeviceBreakpointPanel,
5693
+ {
5694
+ variant: "compact",
5695
+ devicePreset,
5696
+ onDevicePresetChange,
5697
+ activeBreakpoint,
5698
+ onActiveBreakpointChange,
5699
+ developerDetails: false
5700
+ }
5701
+ ) : null,
5532
5702
  showQuickStyle ? /* @__PURE__ */ jsxs14(Fragment5, { children: [
5533
5703
  /* @__PURE__ */ jsx14(
5534
5704
  ColorPickerRow,
@@ -5599,58 +5769,17 @@ function PropertyPanelShell({
5599
5769
  ] })
5600
5770
  ] }) : null,
5601
5771
  !simpleMode ? /* @__PURE__ */ jsxs14(Fragment5, { children: [
5602
- /* @__PURE__ */ jsxs14("section", { className: "nuvio-card nuvio-stack-2", children: [
5603
- /* @__PURE__ */ jsx14("h3", { className: "nuvio-section-title", children: "Device + breakpoint" }),
5604
- /* @__PURE__ */ jsxs14("div", { className: "nuvio-row-wrap", children: [
5605
- /* @__PURE__ */ jsx14(
5606
- "button",
5607
- {
5608
- type: "button",
5609
- className: `nuvio-button-chip ${devicePreset === "desktop" ? "nuvio-button-chip--active" : ""}`,
5610
- onClick: () => onDevicePresetChange("desktop"),
5611
- children: "Desktop"
5612
- }
5613
- ),
5614
- /* @__PURE__ */ jsx14(
5615
- "button",
5616
- {
5617
- type: "button",
5618
- className: `nuvio-button-chip ${devicePreset === "tablet" ? "nuvio-button-chip--active" : ""}`,
5619
- onClick: () => onDevicePresetChange("tablet"),
5620
- children: "Tablet"
5621
- }
5622
- ),
5623
- /* @__PURE__ */ jsx14(
5624
- "button",
5625
- {
5626
- type: "button",
5627
- className: `nuvio-button-chip ${devicePreset === "mobile" ? "nuvio-button-chip--active" : ""}`,
5628
- onClick: () => onDevicePresetChange("mobile"),
5629
- children: "Mobile"
5630
- }
5631
- )
5632
- ] }),
5633
- /* @__PURE__ */ jsx14(
5634
- SelectRow,
5635
- {
5636
- label: developerDetails ? "Active BP" : "Applies on",
5637
- value: activeBreakpoint,
5638
- onChange: (v) => onActiveBreakpointChange(v),
5639
- options: [
5640
- { value: "base", label: developerDetails ? "base" : formatPlainBreakpointLabel("base") },
5641
- { value: "sm", label: developerDetails ? "sm" : formatPlainBreakpointLabel("sm") },
5642
- { value: "md", label: developerDetails ? "md" : formatPlainBreakpointLabel("md") },
5643
- { value: "lg", label: developerDetails ? "lg" : formatPlainBreakpointLabel("lg") },
5644
- { value: "xl", label: developerDetails ? "xl" : formatPlainBreakpointLabel("xl") }
5645
- ]
5646
- }
5647
- ),
5648
- !developerDetails ? /* @__PURE__ */ jsxs14("p", { className: "nuvio-text-2xs nuvio-text-muted", children: [
5649
- "Applies on:",
5650
- " ",
5651
- /* @__PURE__ */ jsx14("span", { className: "nuvio-font-medium", children: formatPlainBreakpointLabel(activeBreakpoint) })
5652
- ] }) : null
5653
- ] }),
5772
+ showResponsiveDeviceControls ? /* @__PURE__ */ jsx14(
5773
+ DeviceBreakpointPanel,
5774
+ {
5775
+ variant: "section",
5776
+ devicePreset,
5777
+ onDevicePresetChange,
5778
+ activeBreakpoint,
5779
+ onActiveBreakpointChange,
5780
+ developerDetails
5781
+ }
5782
+ ) : null,
5654
5783
  selectedId ? /* @__PURE__ */ jsxs14("section", { className: "nuvio-card nuvio-card--tree", children: [
5655
5784
  /* @__PURE__ */ jsx14("h3", { className: "nuvio-section-title", children: "Component tree" }),
5656
5785
  /* @__PURE__ */ jsx14(
@@ -5693,6 +5822,100 @@ function saveDeveloperDetails(enabled) {
5693
5822
  }
5694
5823
  }
5695
5824
 
5825
+ // src/telemetry.ts
5826
+ import { posthog } from "posthog-js";
5827
+
5828
+ // src/nuvio-posthog-token.ts
5829
+ var NUVIO_POSTHOG_TOKEN = "phc_CJnWrLU4hB4aA88DJrPnma2WBMQqVHxUMVvrsye3R6x2";
5830
+
5831
+ // src/telemetry.ts
5832
+ var POSTHOG_HOST = "https://us.i.posthog.com";
5833
+ var initialized = false;
5834
+ var firstSelectionSent = false;
5835
+ var overlayConnectedSent = false;
5836
+ function posthogToken() {
5837
+ return NUVIO_POSTHOG_TOKEN;
5838
+ }
5839
+ function tokenIsConfigured(token) {
5840
+ return Boolean(token && token.startsWith("phc_"));
5841
+ }
5842
+ function isOverlayTelemetryOptedOut(flags) {
5843
+ return flags.localStorageTelemetry === "0" || flags.viteTelemetry === "0";
5844
+ }
5845
+ function isOverlayTelemetryEnabled() {
5846
+ try {
5847
+ const localStorageTelemetry = typeof localStorage !== "undefined" ? localStorage.getItem("nuvio.telemetry") : null;
5848
+ const env = import.meta;
5849
+ if (isOverlayTelemetryOptedOut({
5850
+ localStorageTelemetry,
5851
+ viteTelemetry: env.env?.VITE_NUVIO_TELEMETRY
5852
+ })) {
5853
+ return false;
5854
+ }
5855
+ return true;
5856
+ } catch {
5857
+ return false;
5858
+ }
5859
+ }
5860
+ function ensureInitialized() {
5861
+ if (!isOverlayTelemetryEnabled()) return false;
5862
+ const token = posthogToken();
5863
+ if (!tokenIsConfigured(token)) return false;
5864
+ if (!initialized) {
5865
+ const debug = typeof localStorage !== "undefined" && localStorage.getItem("nuvio.telemetry.debug") === "1";
5866
+ posthog.init(token, {
5867
+ api_host: POSTHOG_HOST,
5868
+ ui_host: "https://us.posthog.com",
5869
+ autocapture: false,
5870
+ capture_pageview: false,
5871
+ disable_session_recording: true,
5872
+ person_profiles: "identified_only",
5873
+ persistence: "localStorage",
5874
+ debug
5875
+ });
5876
+ initialized = true;
5877
+ }
5878
+ return true;
5879
+ }
5880
+ function mapApplyFailureReason(errorCode, options) {
5881
+ if (options?.duplicateIdsActive && errorCode === "unknown_id") {
5882
+ return "duplicate_id";
5883
+ }
5884
+ if (errorCode === "unknown_id" || errorCode === "host_not_found") {
5885
+ return "no_patch_target";
5886
+ }
5887
+ if (errorCode === "patch_rejected") {
5888
+ return "unsupported_classname";
5889
+ }
5890
+ return "apply_error";
5891
+ }
5892
+ function captureOverlayEvent(event, props) {
5893
+ try {
5894
+ if (!ensureInitialized()) return;
5895
+ const payload = {};
5896
+ if (props?.reason) {
5897
+ payload.reason = props.reason;
5898
+ }
5899
+ posthog.capture(event, Object.keys(payload).length > 0 ? payload : void 0);
5900
+ } catch {
5901
+ }
5902
+ }
5903
+ function captureOverlayConnected() {
5904
+ if (overlayConnectedSent) return;
5905
+ overlayConnectedSent = true;
5906
+ captureOverlayEvent("overlay_connected");
5907
+ }
5908
+ function captureFirstSelection() {
5909
+ if (firstSelectionSent) return;
5910
+ firstSelectionSent = true;
5911
+ captureOverlayEvent("first_selection");
5912
+ }
5913
+ function captureApplyFailed(errorCode, options) {
5914
+ captureOverlayEvent("apply_failed", {
5915
+ reason: mapApplyFailureReason(errorCode, options)
5916
+ });
5917
+ }
5918
+
5696
5919
  // src/NuvioDevShell.tsx
5697
5920
  import { Fragment as Fragment6, jsx as jsx15, jsxs as jsxs15 } from "react/jsx-runtime";
5698
5921
  function shortDisplayPath(absPath) {
@@ -5848,7 +6071,7 @@ function NuvioDevShellInner() {
5848
6071
  },
5849
6072
  onDragEnd: commitChipDrag
5850
6073
  });
5851
- useLayoutEffect2(() => {
6074
+ useLayoutEffect3(() => {
5852
6075
  if (chipDragging) {
5853
6076
  return;
5854
6077
  }
@@ -5887,7 +6110,7 @@ function NuvioDevShellInner() {
5887
6110
  chromeLayout.panel.position,
5888
6111
  onPanelPositionChange
5889
6112
  ]);
5890
- useLayoutEffect2(() => {
6113
+ useLayoutEffect3(() => {
5891
6114
  const panelPos = chromeLayout.panel.position;
5892
6115
  if (!panelPos || !panelRef.current) {
5893
6116
  return;
@@ -5904,7 +6127,7 @@ function NuvioDevShellInner() {
5904
6127
  onPanelPositionChange(clamped);
5905
6128
  }
5906
6129
  }, [chromeLayout.panel.position, onPanelPositionChange]);
5907
- useLayoutEffect2(() => {
6130
+ useLayoutEffect3(() => {
5908
6131
  if (!chipPos || !chipRef.current || chipDragging) {
5909
6132
  return;
5910
6133
  }
@@ -5949,6 +6172,7 @@ function NuvioDevShellInner() {
5949
6172
  const resolvedFileRef = useRef5(void 0);
5950
6173
  const selectedIdRef = useRef5(null);
5951
6174
  const lastIndexEntriesRef = useRef5([]);
6175
+ const duplicateErrorsRef = useRef5([]);
5952
6176
  const lastStagedOpsFpRef = useRef5(null);
5953
6177
  const autoApplyStructuralRef = useRef5(false);
5954
6178
  const [structuralPreviewActive, setStructuralPreviewActive] = useState9(false);
@@ -5958,6 +6182,9 @@ function NuvioDevShellInner() {
5958
6182
  useEffect9(() => {
5959
6183
  selectedIdRef.current = selectedId;
5960
6184
  }, [selectedId]);
6185
+ useEffect9(() => {
6186
+ duplicateErrorsRef.current = duplicateErrors;
6187
+ }, [duplicateErrors]);
5961
6188
  const selectedEntry = useMemo6(
5962
6189
  () => selectedId ? indexEntries.find((e) => e.id === selectedId) : void 0,
5963
6190
  [indexEntries, selectedId]
@@ -5988,7 +6215,7 @@ function NuvioDevShellInner() {
5988
6215
  if (dryRun) {
5989
6216
  setPreviewBusy(false);
5990
6217
  setPreviewError(
5991
- !ws || ws.readyState !== WebSocket.OPEN ? "Dev channel is not connected \u2014 wait for \u201Cconnected\u201D in the chip, then try Validate again." : "Nothing is selected \u2014 click an element on the page first."
6218
+ !ws || ws.readyState !== WebSocket.OPEN ? "Dev channel is not connected \u2014 wait for \u201Cconnected\u201D in the chip, then try Preview Changes again." : "Nothing is selected \u2014 click an element on the page first."
5992
6219
  );
5993
6220
  } else {
5994
6221
  setLastPatchError(
@@ -6065,7 +6292,7 @@ function NuvioDevShellInner() {
6065
6292
  const fp = JSON.stringify(ops);
6066
6293
  if (fp !== previewValidatedFingerprint) {
6067
6294
  setLastPatchError(
6068
- "Run Validate first \u2014 staged edits changed since the last successful validation."
6295
+ "Run Preview Changes first \u2014 staged edits changed since the last successful preview."
6069
6296
  );
6070
6297
  return;
6071
6298
  }
@@ -6151,6 +6378,7 @@ function NuvioDevShellInner() {
6151
6378
  setLastPatchError(null);
6152
6379
  setSelectedId(id);
6153
6380
  setSelectError(null);
6381
+ captureFirstSelection();
6154
6382
  const hit = lastIndexEntriesRef.current.find((e) => e.id === id);
6155
6383
  if (hit) {
6156
6384
  setResolvedFile(shortDisplayPath(hit.file));
@@ -6184,6 +6412,7 @@ function NuvioDevShellInner() {
6184
6412
  }
6185
6413
  retryMs = 400;
6186
6414
  setChannel("ready");
6415
+ captureOverlayConnected();
6187
6416
  patchPendingMapRef.current.clear();
6188
6417
  if (previewTimeoutRef.current) {
6189
6418
  clearTimeout(previewTimeoutRef.current);
@@ -6284,6 +6513,7 @@ function NuvioDevShellInner() {
6284
6513
  setPreviewError(null);
6285
6514
  setPreviewValidatedFingerprint(savedFp);
6286
6515
  setPreviewValidatedOps(pending.ops);
6516
+ captureOverlayEvent("preview_changes");
6287
6517
  if (autoApplyStructuralRef.current && isStructuralOnlyOps(pending.ops)) {
6288
6518
  autoApplyStructuralRef.current = false;
6289
6519
  sendPatchMessage(
@@ -6314,8 +6544,12 @@ function NuvioDevShellInner() {
6314
6544
  if (msg.undoStackDepth !== void 0) {
6315
6545
  setUndoStackDepth(msg.undoStackDepth);
6316
6546
  }
6547
+ captureOverlayEvent("apply_to_code");
6317
6548
  } else {
6318
6549
  setLastPatchError(msg.errorMessage ?? msg.errorCode ?? "Apply failed");
6550
+ captureApplyFailed(msg.errorCode, {
6551
+ duplicateIdsActive: duplicateErrorsRef.current.length > 0
6552
+ });
6319
6553
  }
6320
6554
  return;
6321
6555
  }
@@ -6548,7 +6782,7 @@ function NuvioDevShellInner() {
6548
6782
  suppressTextTargetHints: !developerDetails
6549
6783
  }
6550
6784
  ),
6551
- shadowMount ? createPortal(chromeUi, shadowMount.mount) : chromeUi
6785
+ shadowMount ? createPortal2(chromeUi, shadowMount.mount) : chromeUi
6552
6786
  ] });
6553
6787
  }
6554
6788
 
package/dist/style.css CHANGED
@@ -1102,22 +1102,18 @@ select.nuvio-control {
1102
1102
  }
1103
1103
 
1104
1104
  .nuvio-color-popover {
1105
- position: absolute;
1106
- left: 0;
1107
- right: 0;
1108
- z-index: 20;
1109
- margin-top: 4px;
1110
- max-height: min(20rem, 50vh);
1111
1105
  overflow: auto;
1112
1106
  border-radius: var(--nuvio-radius-inner);
1113
1107
  border: 1px solid var(--nuvio-border-strong);
1114
- background: rgba(3, 7, 18, 0.94);
1115
- -webkit-backdrop-filter: var(--nuvio-inner-blur);
1116
- backdrop-filter: var(--nuvio-inner-blur);
1108
+ background: rgb(15, 23, 42);
1117
1109
  padding: 10px;
1118
1110
  box-shadow: 0 28px 80px rgba(0, 0, 0, 0.42);
1119
1111
  }
1120
1112
 
1113
+ .nuvio-color-popover--fixed {
1114
+ pointer-events: auto;
1115
+ }
1116
+
1121
1117
  .nuvio-color-specials {
1122
1118
  display: flex;
1123
1119
  flex-wrap: wrap;
@@ -1247,7 +1243,10 @@ select.nuvio-control {
1247
1243
  }
1248
1244
 
1249
1245
  .nuvio-card--actions {
1246
+ position: relative;
1247
+ z-index: 1;
1250
1248
  border-color: rgba(56, 189, 248, 0.25);
1249
+ background: rgb(15, 23, 42);
1251
1250
  }
1252
1251
 
1253
1252
  .nuvio-action-stack {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nuvio/overlay",
3
- "version": "0.5.3",
3
+ "version": "0.5.4",
4
4
  "description": "Nuvio dev overlay: edit mode, selection, Editor panel, Validate/Apply/Undo against the Vite dev server.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -44,7 +44,8 @@
44
44
  "react-dom": "^18.3.1 || ^19.0.0"
45
45
  },
46
46
  "dependencies": {
47
- "@nuvio/shared": "0.5.3"
47
+ "posthog-js": "^1.380.1",
48
+ "@nuvio/shared": "0.5.4"
48
49
  },
49
50
  "devDependencies": {
50
51
  "@types/react": "^19.0.8",