@ohhwells/bridge 0.1.23 → 0.1.25

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.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  // src/OhhwellsBridge.tsx
4
- import React3, { useCallback, useEffect as useEffect2, useLayoutEffect, useRef as useRef2, useState as useState3 } from "react";
4
+ import React5, { useCallback as useCallback2, useEffect as useEffect4, useLayoutEffect, useRef as useRef3, useState as useState5 } from "react";
5
5
  import { createRoot } from "react-dom/client";
6
6
  import { flushSync } from "react-dom";
7
7
 
@@ -216,10 +216,10 @@ function buildTimezoneLabel(tz) {
216
216
  return tz;
217
217
  }
218
218
  }
219
- function EmptyState({ inEditor, insertAfter }) {
219
+ function EmptyState({ inEditor }) {
220
220
  const handleAddSchedule = () => {
221
221
  if (inEditor) {
222
- window.parent.postMessage({ type: "ow:scheduling-not-connected", insertAfter }, "*");
222
+ window.parent.postMessage({ type: "ow:scheduling-not-connected" }, "*");
223
223
  }
224
224
  };
225
225
  return /* @__PURE__ */ jsxs2("div", { className: "flex flex-col items-center justify-center gap-6 p-12 bg-[#FAFAF9] border-2 border-dashed border-[#E7E5E4] rounded-[10px] h-[264px] w-full box-border", children: [
@@ -444,21 +444,18 @@ function ScheduleView({ schedule, dates, selectedIdx, onSelectDate, onOpenModal
444
444
  const booked = getBookingsOnDate(cls, selectedDate);
445
445
  const available = cls.maxParticipants - booked;
446
446
  const isFull = available <= 0;
447
- const isPrivate = cls.maxParticipants === 1;
448
447
  return /* @__PURE__ */ jsxs2("div", { children: [
449
448
  /* @__PURE__ */ jsxs2("div", { className: "flex gap-4 py-4 sm:items-center sm:gap-[60px] box-border", children: [
450
449
  /* @__PURE__ */ jsxs2("div", { className: "flex flex-col gap-2 shrink-0 sm:contents", children: [
451
- /* @__PURE__ */ jsxs2("div", { className: "flex flex-col gap-2 sm:w-[120px] sm:shrink-0", children: [
452
- /* @__PURE__ */ jsx2("span", { className: "font-body text-base font-bold text-(--color-dark,#200C02) block", children: formatClassTime(cls) }),
453
- /* @__PURE__ */ jsx2(
454
- "span",
455
- {
456
- className: "inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium w-fit",
457
- style: isPrivate ? { background: "#DDFBE3", color: "#199130", border: "1px solid #199130", fontWeight: 500 } : { background: "#EEEDFF", color: "#5953FF", border: "1px solid #5953FF", fontWeight: 500 },
458
- children: isPrivate ? "PRIVATE" : "GROUP"
459
- }
460
- )
461
- ] }),
450
+ /* @__PURE__ */ jsx2("div", { className: "sm:w-[120px] sm:shrink-0", children: /* @__PURE__ */ jsx2("span", { className: "font-body text-base font-bold text-(--color-dark,#200C02) block", children: formatClassTime(cls) }) }),
451
+ /* @__PURE__ */ jsx2(
452
+ "span",
453
+ {
454
+ className: "inline-flex items-center px-2 py-0.5 rounded-full border text-xs font-medium sm:hidden",
455
+ style: { borderColor: "#0885FE", color: "#0885FE" },
456
+ children: "GROUP"
457
+ }
458
+ ),
462
459
  /* @__PURE__ */ jsx2("div", { className: "sm:hidden flex flex-col gap-px", children: isFull ? /* @__PURE__ */ jsx2("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "Full" }) : /* @__PURE__ */ jsxs2(Fragment, { children: [
463
460
  /* @__PURE__ */ jsxs2("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: [
464
461
  available,
@@ -677,7 +674,6 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
677
674
  window.parent.postMessage({ type: "ow:scheduling-not-connected", insertAfter }, "*");
678
675
  }
679
676
  };
680
- if (!inEditor && !loading && !schedule) return null;
681
677
  const sectionId = `scheduling-${insertAfter}`;
682
678
  return /* @__PURE__ */ jsxs2(
683
679
  "section",
@@ -723,7 +719,7 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
723
719
  ] }),
724
720
  /* @__PURE__ */ jsxs2("div", { className: "w-full flex flex-col items-end gap-3", children: [
725
721
  timezoneLabel && /* @__PURE__ */ jsx2("span", { className: "font-body text-sm font-normal text-(--color-accent,#A89B83)", children: timezoneLabel }),
726
- /* @__PURE__ */ jsx2("div", { className: "w-full p-0 sm:p-10 box-border", children: loading ? /* @__PURE__ */ jsx2(LoadingSkeleton, {}) : !schedule ? /* @__PURE__ */ jsx2(EmptyState, { inEditor, insertAfter }) : /* @__PURE__ */ jsx2(
722
+ /* @__PURE__ */ jsx2("div", { className: "w-full p-0 sm:p-10 box-border", children: loading ? /* @__PURE__ */ jsx2(LoadingSkeleton, {}) : !schedule ? /* @__PURE__ */ jsx2(EmptyState, { inEditor }) : /* @__PURE__ */ jsx2(
727
723
  ScheduleView,
728
724
  {
729
725
  schedule,
@@ -4179,8 +4175,844 @@ function isEditSessionActive() {
4179
4175
  return new URLSearchParams(q).get("mode") === "edit";
4180
4176
  }
4181
4177
 
4182
- // src/ui/badge.tsx
4178
+ // src/ui/link-modal/calcPopoverPos.ts
4179
+ function calcPopoverPos(rect, parentScroll, approxW = 483, approxH = 320) {
4180
+ const GAP = 8;
4181
+ const canvasTopInIframe = parentScroll != null ? parentScroll.headerH - parentScroll.iframeOffsetTop : 0;
4182
+ const visibleTop = rect.top - canvasTopInIframe;
4183
+ let top = rect.top - GAP;
4184
+ let transform = "translateX(-50%) translateY(-100%)";
4185
+ if (visibleTop < approxH + GAP) {
4186
+ top = rect.bottom + GAP;
4187
+ transform = "translateX(-50%)";
4188
+ }
4189
+ const rawLeft = rect.left + rect.width / 2;
4190
+ const left = Math.max(GAP + approxW / 2, Math.min(rawLeft, window.innerWidth - GAP - approxW / 2));
4191
+ return { top, left, transform };
4192
+ }
4193
+
4194
+ // src/ui/button.tsx
4195
+ import * as React3 from "react";
4196
+ import { Slot } from "radix-ui";
4183
4197
  import { jsx as jsx5 } from "react/jsx-runtime";
4198
+ var buttonVariants = cva(
4199
+ "inline-flex items-center justify-center gap-1 whitespace-nowrap rounded-md text-sm font-medium transition-colors outline-none disabled:pointer-events-none disabled:opacity-50 min-w-[80px] px-3 py-2",
4200
+ {
4201
+ variants: {
4202
+ variant: {
4203
+ default: "bg-primary text-primary-foreground hover:opacity-90",
4204
+ outline: "border border-border bg-background text-foreground shadow-sm hover:bg-muted/80",
4205
+ ghost: "min-w-0 px-3 py-2 text-foreground hover:bg-muted/50"
4206
+ },
4207
+ size: {
4208
+ default: "h-9",
4209
+ sm: "h-8 min-w-[64px] px-2 py-1.5 text-sm"
4210
+ }
4211
+ },
4212
+ defaultVariants: {
4213
+ variant: "default",
4214
+ size: "default"
4215
+ }
4216
+ }
4217
+ );
4218
+ var Button = React3.forwardRef(
4219
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
4220
+ const Comp = asChild ? Slot.Root : "button";
4221
+ return /* @__PURE__ */ jsx5(
4222
+ Comp,
4223
+ {
4224
+ ref,
4225
+ "data-slot": "button",
4226
+ className: cn(buttonVariants({ variant, size, className })),
4227
+ ...props
4228
+ }
4229
+ );
4230
+ }
4231
+ );
4232
+ Button.displayName = "Button";
4233
+
4234
+ // src/ui/icons.tsx
4235
+ import { jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
4236
+ function IconX({ className, ...props }) {
4237
+ return /* @__PURE__ */ jsxs3("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
4238
+ /* @__PURE__ */ jsx6("path", { d: "M18 6 6 18" }),
4239
+ /* @__PURE__ */ jsx6("path", { d: "m6 6 12 12" })
4240
+ ] });
4241
+ }
4242
+ function IconChevronDown({ className, ...props }) {
4243
+ return /* @__PURE__ */ jsx6("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: /* @__PURE__ */ jsx6("path", { d: "m6 9 6 6 6-6" }) });
4244
+ }
4245
+ function IconFile({ className, ...props }) {
4246
+ return /* @__PURE__ */ jsxs3("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
4247
+ /* @__PURE__ */ jsx6("path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" }),
4248
+ /* @__PURE__ */ jsx6("path", { d: "M14 2v4a2 2 0 0 0 2 2h4" })
4249
+ ] });
4250
+ }
4251
+ function IconInfo({ className, ...props }) {
4252
+ return /* @__PURE__ */ jsxs3("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
4253
+ /* @__PURE__ */ jsx6("circle", { cx: "12", cy: "12", r: "10" }),
4254
+ /* @__PURE__ */ jsx6("path", { d: "M12 16v-4" }),
4255
+ /* @__PURE__ */ jsx6("path", { d: "M12 8h.01" })
4256
+ ] });
4257
+ }
4258
+ function IconArrowRight({ className, ...props }) {
4259
+ return /* @__PURE__ */ jsxs3("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
4260
+ /* @__PURE__ */ jsx6("path", { d: "M5 12h14" }),
4261
+ /* @__PURE__ */ jsx6("path", { d: "m12 5 7 7-7 7" })
4262
+ ] });
4263
+ }
4264
+ function IconSection({ className, ...props }) {
4265
+ return /* @__PURE__ */ jsxs3("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
4266
+ /* @__PURE__ */ jsx6("rect", { width: "18", height: "7", x: "3", y: "3", rx: "1" }),
4267
+ /* @__PURE__ */ jsx6("rect", { width: "9", height: "7", x: "3", y: "14", rx: "1" }),
4268
+ /* @__PURE__ */ jsx6("rect", { width: "5", height: "7", x: "16", y: "14", rx: "1" })
4269
+ ] });
4270
+ }
4271
+
4272
+ // src/ui/link-modal/DestinationBreadcrumb.tsx
4273
+ import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
4274
+ function DestinationBreadcrumb({ pageTitle, sectionLabel }) {
4275
+ return /* @__PURE__ */ jsxs4("div", { className: "flex w-full flex-col gap-2", children: [
4276
+ /* @__PURE__ */ jsx7("p", { className: "text-sm font-medium text-foreground", children: "Destination" }),
4277
+ /* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-3", children: [
4278
+ /* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-2", children: [
4279
+ /* @__PURE__ */ jsx7(IconFile, { className: "shrink-0 text-foreground", "aria-hidden": true }),
4280
+ /* @__PURE__ */ jsx7("span", { className: "text-sm font-medium leading-none text-foreground", children: pageTitle })
4281
+ ] }),
4282
+ /* @__PURE__ */ jsx7(IconArrowRight, { className: "shrink-0 text-muted-foreground", "aria-hidden": true }),
4283
+ /* @__PURE__ */ jsxs4("div", { className: "flex min-w-0 flex-1 items-center gap-2", children: [
4284
+ /* @__PURE__ */ jsx7(IconSection, { className: "shrink-0 text-foreground", "aria-hidden": true }),
4285
+ /* @__PURE__ */ jsx7("span", { className: "truncate text-sm font-medium leading-none text-foreground", children: sectionLabel })
4286
+ ] })
4287
+ ] })
4288
+ ] });
4289
+ }
4290
+
4291
+ // src/ui/link-modal/SectionTreeItem.tsx
4292
+ import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
4293
+ function SectionTreeItem({ section, onSelect, selected }) {
4294
+ const interactive = Boolean(onSelect);
4295
+ return /* @__PURE__ */ jsxs5("div", { className: "flex h-9 w-full items-end pl-3", children: [
4296
+ /* @__PURE__ */ jsx8(
4297
+ "div",
4298
+ {
4299
+ className: "mr-[-1px] h-9 w-2 shrink-0 rounded-bl-sm border-b border-l border-border mb-4",
4300
+ "aria-hidden": true
4301
+ }
4302
+ ),
4303
+ /* @__PURE__ */ jsxs5(
4304
+ "div",
4305
+ {
4306
+ role: interactive ? "button" : void 0,
4307
+ tabIndex: interactive ? 0 : void 0,
4308
+ onClick: interactive ? () => onSelect?.(section) : void 0,
4309
+ onKeyDown: interactive ? (e) => {
4310
+ if (e.key === "Enter" || e.key === " ") {
4311
+ e.preventDefault();
4312
+ onSelect?.(section);
4313
+ }
4314
+ } : void 0,
4315
+ className: cn(
4316
+ "flex h-9 min-w-0 flex-1 items-center gap-2 rounded-md border border-border bg-background p-3",
4317
+ interactive && "cursor-pointer hover:bg-muted/30",
4318
+ interactive && selected && "border-primary"
4319
+ ),
4320
+ children: [
4321
+ /* @__PURE__ */ jsx8(IconSection, { className: "shrink-0 text-foreground", "aria-hidden": true }),
4322
+ /* @__PURE__ */ jsx8("span", { className: "truncate text-sm font-normal leading-5 text-foreground", children: section.label })
4323
+ ]
4324
+ }
4325
+ )
4326
+ ] });
4327
+ }
4328
+ function SectionPickerList({ sections, onSelect }) {
4329
+ if (sections.length === 0) {
4330
+ return /* @__PURE__ */ jsx8("p", { className: "text-sm text-muted-foreground", children: "No sections found on this page." });
4331
+ }
4332
+ return /* @__PURE__ */ jsxs5("div", { className: "flex flex-col gap-1", children: [
4333
+ /* @__PURE__ */ jsx8("p", { className: "text-sm text-muted-foreground", children: "Pick a section this link should scroll to." }),
4334
+ sections.map((section) => /* @__PURE__ */ jsx8(SectionTreeItem, { section, onSelect }, section.id))
4335
+ ] });
4336
+ }
4337
+
4338
+ // src/ui/link-modal/UrlOrPageInput.tsx
4339
+ import { useEffect as useEffect2, useId, useRef as useRef2, useState as useState3 } from "react";
4340
+
4341
+ // src/ui/input.tsx
4342
+ import * as React4 from "react";
4343
+ import { jsx as jsx9 } from "react/jsx-runtime";
4344
+ var Input = React4.forwardRef(
4345
+ ({ className, type, ...props }, ref) => {
4346
+ return /* @__PURE__ */ jsx9(
4347
+ "input",
4348
+ {
4349
+ type,
4350
+ ref,
4351
+ "data-slot": "input",
4352
+ className: cn(
4353
+ "flex h-full w-full min-w-0 bg-transparent text-sm text-foreground outline-none placeholder:text-muted-foreground",
4354
+ className
4355
+ ),
4356
+ ...props
4357
+ }
4358
+ );
4359
+ }
4360
+ );
4361
+ Input.displayName = "Input";
4362
+
4363
+ // src/ui/label.tsx
4364
+ import { Label as LabelPrimitive } from "radix-ui";
4365
+ import { jsx as jsx10 } from "react/jsx-runtime";
4366
+ function Label({ className, ...props }) {
4367
+ return /* @__PURE__ */ jsx10(
4368
+ LabelPrimitive.Root,
4369
+ {
4370
+ "data-slot": "label",
4371
+ className: cn("text-sm font-medium leading-5 text-foreground", className),
4372
+ ...props
4373
+ }
4374
+ );
4375
+ }
4376
+
4377
+ // src/ui/popover.tsx
4378
+ import { Popover as PopoverPrimitive } from "radix-ui";
4379
+ import { jsx as jsx11 } from "react/jsx-runtime";
4380
+ function Popover({ ...props }) {
4381
+ return /* @__PURE__ */ jsx11(PopoverPrimitive.Root, { "data-slot": "popover", ...props });
4382
+ }
4383
+ function PopoverTrigger({ ...props }) {
4384
+ return /* @__PURE__ */ jsx11(PopoverPrimitive.Trigger, { "data-slot": "popover-trigger", ...props });
4385
+ }
4386
+ function PopoverContent({
4387
+ className,
4388
+ align = "start",
4389
+ sideOffset = 6,
4390
+ ...props
4391
+ }) {
4392
+ return /* @__PURE__ */ jsx11(PopoverPrimitive.Portal, { children: /* @__PURE__ */ jsx11(
4393
+ PopoverPrimitive.Content,
4394
+ {
4395
+ "data-slot": "popover-content",
4396
+ align,
4397
+ sideOffset,
4398
+ className: cn(
4399
+ "z-[2147483647] w-[var(--radix-popover-trigger-width)] overflow-auto rounded-lg border border-border bg-popover py-1 shadow-lg outline-none",
4400
+ className
4401
+ ),
4402
+ ...props
4403
+ }
4404
+ ) });
4405
+ }
4406
+
4407
+ // src/ui/link-modal/UrlOrPageInput.tsx
4408
+ import { jsx as jsx12, jsxs as jsxs6 } from "react/jsx-runtime";
4409
+ function FieldChevron({ onClick }) {
4410
+ return /* @__PURE__ */ jsx12(
4411
+ "button",
4412
+ {
4413
+ type: "button",
4414
+ className: "flex shrink-0 items-center justify-end border-0 bg-transparent p-0 pl-4 text-muted-foreground outline-none",
4415
+ onClick,
4416
+ "aria-label": "Open page list",
4417
+ tabIndex: -1,
4418
+ children: /* @__PURE__ */ jsx12(IconChevronDown, {})
4419
+ }
4420
+ );
4421
+ }
4422
+ function UrlOrPageInput({
4423
+ value,
4424
+ selectedPage,
4425
+ placeholder = "Paste a URL or choose a page",
4426
+ dropdownOpen,
4427
+ onDropdownOpenChange,
4428
+ filteredPages,
4429
+ onInputChange,
4430
+ onPageSelect,
4431
+ readOnly = false,
4432
+ urlError
4433
+ }) {
4434
+ const inputId = useId();
4435
+ const inputRef = useRef2(null);
4436
+ const [isEditing, setIsEditing] = useState3(false);
4437
+ const [isFocused, setIsFocused] = useState3(false);
4438
+ const showFilledDisplay = Boolean(selectedPage) && !isEditing && !readOnly;
4439
+ useEffect2(() => {
4440
+ if (selectedPage) setIsEditing(false);
4441
+ }, [selectedPage]);
4442
+ const openDropdown = () => {
4443
+ if (readOnly || filteredPages.length === 0) return;
4444
+ onDropdownOpenChange(true);
4445
+ };
4446
+ const toggleDropdown = (e) => {
4447
+ e.stopPropagation();
4448
+ e.preventDefault();
4449
+ if (readOnly || filteredPages.length === 0) return;
4450
+ onDropdownOpenChange(!dropdownOpen);
4451
+ };
4452
+ const enterEditMode = () => {
4453
+ if (readOnly) return;
4454
+ setIsEditing(true);
4455
+ requestAnimationFrame(() => inputRef.current?.focus());
4456
+ };
4457
+ const fieldClassName = cn(
4458
+ "data-ohw-link-field flex h-10 w-full items-center overflow-hidden rounded-md border bg-background pl-3 pr-3 py-2 outline-none transition-[border-color,box-shadow]",
4459
+ urlError ? "border-destructive shadow-[0_0_0_1px_var(--ohw-destructive)]" : isFocused ? "border-primary shadow-[0_0_0_1px_var(--ohw-primary)]" : "border-input"
4460
+ );
4461
+ return /* @__PURE__ */ jsxs6("div", { className: "flex w-full flex-col gap-2 p-0", children: [
4462
+ /* @__PURE__ */ jsx12(Label, { htmlFor: inputId, className: cn(urlError && "text-destructive"), children: "Destination" }),
4463
+ /* @__PURE__ */ jsxs6(Popover, { open: dropdownOpen && !readOnly, onOpenChange: onDropdownOpenChange, children: [
4464
+ /* @__PURE__ */ jsx12(PopoverTrigger, { asChild: true, children: showFilledDisplay ? /* @__PURE__ */ jsxs6(
4465
+ "button",
4466
+ {
4467
+ type: "button",
4468
+ "data-ohw-link-field": true,
4469
+ className: cn(fieldClassName, "m-0 cursor-pointer border-solid text-left"),
4470
+ onClick: openDropdown,
4471
+ onFocus: () => setIsFocused(true),
4472
+ onBlur: () => setIsFocused(false),
4473
+ onKeyDown: (e) => {
4474
+ if (e.key === "Backspace" || e.key === "Delete" || e.key.length === 1) {
4475
+ enterEditMode();
4476
+ }
4477
+ },
4478
+ children: [
4479
+ /* @__PURE__ */ jsx12("div", { className: "flex shrink-0 items-center pr-2", children: /* @__PURE__ */ jsx12(IconFile, { className: "text-foreground", "aria-hidden": true }) }),
4480
+ /* @__PURE__ */ jsx12("span", { className: "min-w-0 flex-1 truncate text-sm font-normal text-foreground", children: selectedPage.title }),
4481
+ /* @__PURE__ */ jsx12(FieldChevron, { onClick: toggleDropdown })
4482
+ ]
4483
+ }
4484
+ ) : /* @__PURE__ */ jsxs6(
4485
+ "div",
4486
+ {
4487
+ "data-ohw-link-field": true,
4488
+ className: fieldClassName,
4489
+ onMouseDown: (e) => {
4490
+ if (readOnly) return;
4491
+ if (e.target === e.currentTarget) enterEditMode();
4492
+ },
4493
+ children: [
4494
+ selectedPage ? /* @__PURE__ */ jsx12("div", { className: "flex shrink-0 items-center pr-2", children: /* @__PURE__ */ jsx12(IconFile, { className: "text-foreground", "aria-hidden": true }) }) : null,
4495
+ readOnly ? /* @__PURE__ */ jsx12("span", { className: "min-w-0 flex-1 truncate text-sm leading-5 text-foreground", children: selectedPage?.title ?? value }) : /* @__PURE__ */ jsx12(
4496
+ Input,
4497
+ {
4498
+ ref: inputRef,
4499
+ id: inputId,
4500
+ value,
4501
+ onChange: (e) => onInputChange(e.target.value),
4502
+ onFocus: () => {
4503
+ setIsFocused(true);
4504
+ setIsEditing(true);
4505
+ openDropdown();
4506
+ },
4507
+ onBlur: () => setIsFocused(false),
4508
+ placeholder,
4509
+ className: cn(
4510
+ "min-w-0 flex-1 truncate border-0 bg-transparent p-0 text-sm font-normal leading-5 shadow-none outline-none ring-0",
4511
+ value ? "text-foreground" : "text-muted-foreground placeholder:text-muted-foreground"
4512
+ )
4513
+ }
4514
+ ),
4515
+ !readOnly ? /* @__PURE__ */ jsx12(FieldChevron, { onClick: toggleDropdown }) : null
4516
+ ]
4517
+ }
4518
+ ) }),
4519
+ /* @__PURE__ */ jsx12(PopoverContent, { "data-ohw-link-popover-root": "", onOpenAutoFocus: (e) => e.preventDefault(), children: filteredPages.map((page) => /* @__PURE__ */ jsxs6(
4520
+ "button",
4521
+ {
4522
+ type: "button",
4523
+ className: "flex h-9 w-full items-center gap-2 border-0 bg-transparent px-3 text-left text-sm leading-5 text-foreground outline-none hover:bg-muted",
4524
+ onClick: () => onPageSelect(page),
4525
+ children: [
4526
+ /* @__PURE__ */ jsx12(IconFile, { className: "shrink-0", "aria-hidden": true }),
4527
+ /* @__PURE__ */ jsx12("span", { className: "truncate", children: page.title })
4528
+ ]
4529
+ },
4530
+ page.path
4531
+ )) })
4532
+ ] }),
4533
+ urlError ? /* @__PURE__ */ jsx12("p", { className: "text-sm font-medium text-destructive", children: urlError }) : null
4534
+ ] });
4535
+ }
4536
+
4537
+ // src/ui/link-modal/useLinkModalState.ts
4538
+ import { useCallback, useEffect as useEffect3, useMemo as useMemo2, useState as useState4 } from "react";
4539
+
4540
+ // src/ui/link-modal/linkModal.utils.ts
4541
+ function parseTarget(target) {
4542
+ if (!target) return { pageRoute: "", sectionId: null };
4543
+ const hashIndex = target.indexOf("#");
4544
+ if (hashIndex === -1) return { pageRoute: target, sectionId: null };
4545
+ return {
4546
+ pageRoute: target.slice(0, hashIndex),
4547
+ sectionId: target.slice(hashIndex + 1) || null
4548
+ };
4549
+ }
4550
+ function normalizePath(path) {
4551
+ const trimmed = path.trim();
4552
+ if (!trimmed) return "/";
4553
+ try {
4554
+ if (/^https?:\/\//i.test(trimmed)) {
4555
+ return new URL(trimmed).pathname || "/";
4556
+ }
4557
+ } catch {
4558
+ }
4559
+ const withSlash = trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
4560
+ if (withSlash.length > 1 && withSlash.endsWith("/")) return withSlash.slice(0, -1);
4561
+ return withSlash;
4562
+ }
4563
+ function findPageByPath(pages, path) {
4564
+ const normalized = normalizePath(path);
4565
+ return pages.find((p) => normalizePath(p.path) === normalized);
4566
+ }
4567
+ function inferPageTitleFromPath(path) {
4568
+ const normalized = normalizePath(path);
4569
+ if (normalized === "/") return "Home";
4570
+ const segment = normalized.split("/").filter(Boolean).pop() ?? "";
4571
+ if (!segment) return "Home";
4572
+ return segment.split("-").filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
4573
+ }
4574
+ function isInternalPath(route) {
4575
+ const trimmed = route.trim();
4576
+ if (!trimmed) return false;
4577
+ if (/^https?:\/\//i.test(trimmed)) return false;
4578
+ if (trimmed.startsWith("/")) return true;
4579
+ if (trimmed.includes(" ") || trimmed.includes(".")) return false;
4580
+ return true;
4581
+ }
4582
+ function resolvePage(route, pages) {
4583
+ const found = findPageByPath(pages, route);
4584
+ if (found) return { path: found.path, title: found.title };
4585
+ return { path: normalizePath(route), title: inferPageTitleFromPath(route) };
4586
+ }
4587
+ function getSectionsForPath(sectionsByPath, pageRoute, fallback) {
4588
+ if (!sectionsByPath) return fallback;
4589
+ const normalized = normalizePath(pageRoute);
4590
+ return sectionsByPath[normalized] ?? sectionsByPath[pageRoute] ?? fallback;
4591
+ }
4592
+ function buildTarget(page, section) {
4593
+ if (section) return `${page.path}#${section.id}`;
4594
+ return page.path;
4595
+ }
4596
+ function isValidUrl(urlString) {
4597
+ try {
4598
+ const url = new URL(urlString);
4599
+ if (url.protocol !== "http:" && url.protocol !== "https:") return false;
4600
+ const hostname = url.hostname;
4601
+ if (!hostname || hostname.length === 0) return false;
4602
+ if (hostname.includes(" ")) return false;
4603
+ return hostname.includes(".") || hostname === "localhost";
4604
+ } catch {
4605
+ return false;
4606
+ }
4607
+ }
4608
+ function normalizeUrl(value) {
4609
+ const trimmed = value.trim();
4610
+ if (trimmed.startsWith("http://") || trimmed.startsWith("https://")) return trimmed;
4611
+ return `https://${trimmed}`;
4612
+ }
4613
+ function validateUrlInput(value, pages, allPages) {
4614
+ if (!value.trim()) return "";
4615
+ const filtered = pages.filter((p) => p.title.toLowerCase().startsWith(value.toLowerCase()));
4616
+ if (filtered.length > 0) return "";
4617
+ const matchingPage = allPages.find((p) => p.title.toLowerCase() === value.toLowerCase());
4618
+ if (matchingPage) return "";
4619
+ if (!isValidUrl(normalizeUrl(value))) {
4620
+ return "Please enter a valid URL (e.g., https://example.com)";
4621
+ }
4622
+ return "";
4623
+ }
4624
+ function getEditModeInitialState(target, pages, sections) {
4625
+ const empty = {
4626
+ searchValue: "",
4627
+ selectedPage: null,
4628
+ selectedSection: null,
4629
+ step: "input"
4630
+ };
4631
+ if (!target) return empty;
4632
+ if (isValidUrl(normalizeUrl(target))) {
4633
+ return { searchValue: target, selectedPage: null, selectedSection: null, step: "input" };
4634
+ }
4635
+ const { pageRoute, sectionId } = parseTarget(target);
4636
+ if (sectionId) {
4637
+ if (isInternalPath(pageRoute)) {
4638
+ const page = resolvePage(pageRoute, pages);
4639
+ const sec = sections.find((s) => s.id === sectionId);
4640
+ return {
4641
+ searchValue: page.title,
4642
+ selectedPage: page,
4643
+ selectedSection: sec ? { id: sec.id, label: sec.label } : null,
4644
+ step: "input"
4645
+ };
4646
+ }
4647
+ return { searchValue: pageRoute || target, selectedPage: null, selectedSection: null, step: "input" };
4648
+ }
4649
+ if (isInternalPath(pageRoute)) {
4650
+ const page = resolvePage(pageRoute, pages);
4651
+ return {
4652
+ searchValue: page.title,
4653
+ selectedPage: page,
4654
+ selectedSection: null,
4655
+ step: "input"
4656
+ };
4657
+ }
4658
+ return { searchValue: target, selectedPage: null, selectedSection: null, step: "input" };
4659
+ }
4660
+ function filterAvailablePages(pages, existingTargets) {
4661
+ const taken = new Set(existingTargets);
4662
+ return pages.filter((p) => !taken.has(p.path));
4663
+ }
4664
+
4665
+ // src/ui/link-modal/useLinkModalState.ts
4666
+ function useLinkModalState({
4667
+ open,
4668
+ mode,
4669
+ pages,
4670
+ sections,
4671
+ sectionsByPath,
4672
+ initialTarget,
4673
+ existingTargets,
4674
+ onClose,
4675
+ onSubmit
4676
+ }) {
4677
+ const availablePages = useMemo2(
4678
+ () => mode === "create" ? filterAvailablePages(pages, existingTargets) : pages,
4679
+ [mode, pages, existingTargets]
4680
+ );
4681
+ const [searchValue, setSearchValue] = useState4("");
4682
+ const [selectedPage, setSelectedPage] = useState4(null);
4683
+ const [selectedSection, setSelectedSection] = useState4(null);
4684
+ const [step, setStep] = useState4("input");
4685
+ const [dropdownOpen, setDropdownOpen] = useState4(false);
4686
+ const [urlError, setUrlError] = useState4("");
4687
+ const reset = useCallback(() => {
4688
+ setSearchValue("");
4689
+ setSelectedPage(null);
4690
+ setSelectedSection(null);
4691
+ setStep("input");
4692
+ setDropdownOpen(false);
4693
+ setUrlError("");
4694
+ }, []);
4695
+ useEffect3(() => {
4696
+ if (!open) return;
4697
+ if (mode === "edit" && initialTarget) {
4698
+ const { pageRoute } = parseTarget(initialTarget);
4699
+ const targetSections = getSectionsForPath(sectionsByPath, pageRoute, sections);
4700
+ const init = getEditModeInitialState(initialTarget, pages, targetSections);
4701
+ setSearchValue(init.searchValue);
4702
+ setSelectedPage(init.selectedPage);
4703
+ setSelectedSection(init.selectedSection);
4704
+ setStep(init.step);
4705
+ } else {
4706
+ reset();
4707
+ }
4708
+ setDropdownOpen(false);
4709
+ setUrlError("");
4710
+ }, [open, mode, initialTarget, pages, sections, sectionsByPath, reset]);
4711
+ const filteredPages = useMemo2(() => {
4712
+ if (!searchValue.trim()) return availablePages;
4713
+ return availablePages.filter((p) => p.title.toLowerCase().startsWith(searchValue.toLowerCase()));
4714
+ }, [availablePages, searchValue]);
4715
+ const activeSections = useMemo2(() => {
4716
+ if (selectedPage && sectionsByPath) {
4717
+ return getSectionsForPath(sectionsByPath, selectedPage.path, sections);
4718
+ }
4719
+ return sections;
4720
+ }, [selectedPage, sectionsByPath, sections]);
4721
+ const showBreadcrumb = step === "confirmed" && selectedPage && selectedSection;
4722
+ const showSectionPicker = step === "sectionPicker" && selectedPage;
4723
+ const showSectionRow = mode === "edit" && selectedPage && selectedSection && step === "input";
4724
+ const showChooseSection = step === "input" && selectedPage && !selectedSection && !showSectionRow;
4725
+ const handleInputChange = (value) => {
4726
+ setSearchValue(value);
4727
+ setSelectedPage(null);
4728
+ setSelectedSection(null);
4729
+ setStep("input");
4730
+ setUrlError("");
4731
+ const filtered = value.trim() ? availablePages.filter((p) => p.title.toLowerCase().startsWith(value.toLowerCase())) : availablePages;
4732
+ setDropdownOpen(filtered.length > 0);
4733
+ if (value.trim() && filtered.length === 0) {
4734
+ const error = validateUrlInput(value, availablePages, pages);
4735
+ if (error) setUrlError(error);
4736
+ }
4737
+ };
4738
+ const handlePageSelect = (page) => {
4739
+ setSelectedPage({ path: page.path, title: page.title });
4740
+ setSelectedSection(null);
4741
+ setSearchValue(page.title);
4742
+ setStep("input");
4743
+ setDropdownOpen(false);
4744
+ setUrlError("");
4745
+ };
4746
+ const handleChooseSection = () => {
4747
+ setStep("sectionPicker");
4748
+ };
4749
+ const handleSectionSelect = (section) => {
4750
+ setSelectedSection({ id: section.id, label: section.label });
4751
+ if (mode === "create") {
4752
+ setStep("confirmed");
4753
+ } else {
4754
+ setStep("input");
4755
+ }
4756
+ };
4757
+ const handleBackToSections = () => {
4758
+ setStep("sectionPicker");
4759
+ };
4760
+ const handleClose = () => {
4761
+ reset();
4762
+ onClose();
4763
+ };
4764
+ const isValid = useMemo2(() => {
4765
+ if (urlError) return false;
4766
+ if (showBreadcrumb) return true;
4767
+ if (selectedPage) return true;
4768
+ if (!searchValue.trim()) return false;
4769
+ return validateUrlInput(searchValue, availablePages, pages) === "";
4770
+ }, [urlError, showBreadcrumb, selectedPage, searchValue, availablePages, pages]);
4771
+ const handleSubmit = () => {
4772
+ if (selectedPage && selectedSection) {
4773
+ onSubmit(buildTarget(selectedPage, selectedSection));
4774
+ handleClose();
4775
+ return;
4776
+ }
4777
+ if (selectedPage) {
4778
+ onSubmit(buildTarget(selectedPage, null));
4779
+ handleClose();
4780
+ return;
4781
+ }
4782
+ if (!searchValue.trim()) return;
4783
+ const error = validateUrlInput(searchValue, availablePages, pages);
4784
+ if (error) {
4785
+ setUrlError(error);
4786
+ return;
4787
+ }
4788
+ const matchingPage = pages.find((p) => p.title.toLowerCase() === searchValue.toLowerCase());
4789
+ if (matchingPage) {
4790
+ onSubmit(matchingPage.path);
4791
+ handleClose();
4792
+ return;
4793
+ }
4794
+ onSubmit(normalizeUrl(searchValue));
4795
+ handleClose();
4796
+ };
4797
+ const submitLabel = mode === "edit" ? "Save" : "Create";
4798
+ const title = mode === "edit" ? "Edit navigation item" : "Create navigation item";
4799
+ const secondaryLabel = showBreadcrumb ? "Back to sections" : "Cancel";
4800
+ const handleSecondary = showBreadcrumb ? handleBackToSections : handleClose;
4801
+ return {
4802
+ title,
4803
+ submitLabel,
4804
+ secondaryLabel,
4805
+ searchValue,
4806
+ selectedPage,
4807
+ selectedSection,
4808
+ step,
4809
+ dropdownOpen,
4810
+ setDropdownOpen,
4811
+ urlError,
4812
+ filteredPages,
4813
+ showBreadcrumb,
4814
+ showSectionPicker,
4815
+ showChooseSection,
4816
+ showSectionRow,
4817
+ isValid,
4818
+ activeSections,
4819
+ handleInputChange,
4820
+ handlePageSelect,
4821
+ handleChooseSection,
4822
+ handleSectionSelect,
4823
+ handleBackToSections,
4824
+ handleClose,
4825
+ handleSecondary,
4826
+ handleSubmit
4827
+ };
4828
+ }
4829
+
4830
+ // src/ui/link-modal/LinkEditorPanel.tsx
4831
+ import { Fragment as Fragment2, jsx as jsx13, jsxs as jsxs7 } from "react/jsx-runtime";
4832
+ function LinkEditorPanel({
4833
+ open = true,
4834
+ mode = "create",
4835
+ pages,
4836
+ sections = [],
4837
+ sectionsByPath,
4838
+ initialTarget,
4839
+ existingTargets = [],
4840
+ onClose,
4841
+ onSubmit
4842
+ }) {
4843
+ const state = useLinkModalState({
4844
+ open,
4845
+ mode,
4846
+ pages,
4847
+ sections,
4848
+ sectionsByPath,
4849
+ initialTarget,
4850
+ existingTargets,
4851
+ onClose,
4852
+ onSubmit
4853
+ });
4854
+ const isCancel = state.secondaryLabel === "Cancel";
4855
+ return /* @__PURE__ */ jsxs7(Fragment2, { children: [
4856
+ /* @__PURE__ */ jsx13(
4857
+ "button",
4858
+ {
4859
+ type: "button",
4860
+ className: "absolute right-[9px] top-[9px] rounded-sm p-1.5 text-foreground hover:bg-muted/50",
4861
+ onClick: state.handleClose,
4862
+ "aria-label": "Close",
4863
+ children: /* @__PURE__ */ jsx13(IconX, { "aria-hidden": true })
4864
+ }
4865
+ ),
4866
+ /* @__PURE__ */ jsx13("header", { className: "flex w-full flex-col gap-1.5 p-6 pr-12", children: /* @__PURE__ */ jsx13("h2", { className: "m-0 w-full break-words text-lg font-semibold leading-none tracking-tight text-card-foreground", children: state.title }) }),
4867
+ /* @__PURE__ */ jsxs7("div", { className: "flex w-full flex-col gap-3 px-6 pb-8 pt-1", children: [
4868
+ state.showBreadcrumb && state.selectedPage && state.selectedSection ? /* @__PURE__ */ jsx13(
4869
+ DestinationBreadcrumb,
4870
+ {
4871
+ pageTitle: state.selectedPage.title,
4872
+ sectionLabel: state.selectedSection.label
4873
+ }
4874
+ ) : /* @__PURE__ */ jsx13(
4875
+ UrlOrPageInput,
4876
+ {
4877
+ value: state.searchValue,
4878
+ selectedPage: state.selectedPage,
4879
+ dropdownOpen: state.dropdownOpen,
4880
+ onDropdownOpenChange: state.setDropdownOpen,
4881
+ filteredPages: state.filteredPages,
4882
+ onInputChange: state.handleInputChange,
4883
+ onPageSelect: state.handlePageSelect,
4884
+ urlError: state.urlError
4885
+ }
4886
+ ),
4887
+ state.showChooseSection ? /* @__PURE__ */ jsxs7("div", { className: "flex flex-col justify-center gap-2", children: [
4888
+ /* @__PURE__ */ jsx13(Button, { type: "button", variant: "outline", className: "w-fit cursor-pointer", size: "sm", onClick: state.handleChooseSection, children: "Choose a section" }),
4889
+ /* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-1 text-sm text-muted-foreground", children: [
4890
+ /* @__PURE__ */ jsx13(IconInfo, { className: "shrink-0", "aria-hidden": true }),
4891
+ /* @__PURE__ */ jsx13("span", { children: "Pick a section this link should scroll to." })
4892
+ ] })
4893
+ ] }) : null,
4894
+ state.showSectionPicker ? /* @__PURE__ */ jsx13(SectionPickerList, { sections: state.activeSections, onSelect: state.handleSectionSelect }) : null,
4895
+ state.showSectionRow && state.selectedSection ? /* @__PURE__ */ jsx13(SectionTreeItem, { section: state.selectedSection, selected: true }) : null
4896
+ ] }),
4897
+ /* @__PURE__ */ jsxs7("footer", { className: "flex w-full items-center justify-end gap-2 px-6 pb-6", children: [
4898
+ /* @__PURE__ */ jsx13(
4899
+ Button,
4900
+ {
4901
+ type: "button",
4902
+ variant: isCancel ? "outline" : "ghost",
4903
+ className: "leading-6 shadow-none cursor-pointer",
4904
+ style: isCancel ? {
4905
+ backgroundColor: "var(--ohw-background)",
4906
+ borderColor: "var(--ohw-border)",
4907
+ color: "var(--ohw-foreground)"
4908
+ } : void 0,
4909
+ onClick: state.handleSecondary,
4910
+ children: state.secondaryLabel
4911
+ }
4912
+ ),
4913
+ /* @__PURE__ */ jsx13(
4914
+ Button,
4915
+ {
4916
+ type: "button",
4917
+ className: "border-0 leading-6 shadow-none hover:opacity-90 disabled:opacity-50 cursor-pointer",
4918
+ style: {
4919
+ backgroundColor: "var(--ohw-primary)",
4920
+ color: "var(--ohw-primary-foreground)"
4921
+ },
4922
+ disabled: !state.isValid,
4923
+ onClick: state.handleSubmit,
4924
+ children: state.submitLabel
4925
+ }
4926
+ )
4927
+ ] })
4928
+ ] });
4929
+ }
4930
+
4931
+ // src/ui/link-modal/LinkPopover.tsx
4932
+ import { jsx as jsx14 } from "react/jsx-runtime";
4933
+ function LinkPopover({
4934
+ rect,
4935
+ parentScroll,
4936
+ panelRef,
4937
+ ...editorProps
4938
+ }) {
4939
+ const { top, left, transform } = calcPopoverPos(rect, parentScroll);
4940
+ return /* @__PURE__ */ jsx14(
4941
+ "div",
4942
+ {
4943
+ ref: panelRef,
4944
+ "data-ohw-link-popover-root": "",
4945
+ "data-ohw-link-modal-root": "",
4946
+ "data-ohw-bridge": "",
4947
+ className: cn(
4948
+ "pointer-events-auto fixed z-[2147483647] flex w-[483px] max-w-[calc(100vw-16px)] flex-col overflow-hidden",
4949
+ "rounded-xl border border-border bg-background font-sans shadow-lg outline-none"
4950
+ ),
4951
+ style: { top, left, transform },
4952
+ onMouseDown: (e) => e.stopPropagation(),
4953
+ children: /* @__PURE__ */ jsx14(LinkEditorPanel, { ...editorProps, open: true })
4954
+ }
4955
+ );
4956
+ }
4957
+
4958
+ // src/ui/link-modal/devFixtures.ts
4959
+ var DEV_SITE_PAGES = [
4960
+ { path: "/", title: "Home" },
4961
+ { path: "/about", title: "About" },
4962
+ { path: "/classes", title: "Classes" },
4963
+ { path: "/pricing", title: "Pricing" },
4964
+ { path: "/studio", title: "Studio" },
4965
+ { path: "/instructors", title: "Instructors" },
4966
+ { path: "/contact", title: "Contact" }
4967
+ ];
4968
+ var DEV_SECTIONS_BY_PATH = {
4969
+ "/": [
4970
+ { id: "hero", label: "Hero" },
4971
+ { id: "lagree-intro", label: "Lagree Intro" },
4972
+ { id: "classes-strip", label: "Classes" },
4973
+ { id: "feel-split", label: "Feel Split" },
4974
+ { id: "founder-teaser", label: "Founder" },
4975
+ { id: "plan-form", label: "Plan Form" },
4976
+ { id: "testimonials", label: "Testimonials" },
4977
+ { id: "wordmark", label: "Wordmark" }
4978
+ ],
4979
+ "/about": [
4980
+ { id: "manifesto", label: "Manifesto" },
4981
+ { id: "story-letter", label: "Our Story" },
4982
+ { id: "lagree-explainer", label: "Lagree Explainer" },
4983
+ { id: "waitlist-cta", label: "Waitlist" },
4984
+ { id: "personal-training", label: "Personal training" }
4985
+ ],
4986
+ "/classes": [{ id: "class-library", label: "Class Library" }],
4987
+ "/pricing": [
4988
+ { id: "benefits-marquee", label: "Benefits" },
4989
+ { id: "monthly-memberships", label: "Memberships" },
4990
+ { id: "package-matcher", label: "Packages" },
4991
+ { id: "specialty-programs", label: "Specialty Programs" },
4992
+ { id: "free-class-cta", label: "Free Class" }
4993
+ ],
4994
+ "/studio": [
4995
+ { id: "studio-intro", label: "Studio Intro" },
4996
+ { id: "studio-features", label: "Studio Features" },
4997
+ { id: "studio-gallery", label: "Studio Gallery" },
4998
+ { id: "studio-visit", label: "Visit Studio" }
4999
+ ],
5000
+ "/instructors": [{ id: "instructor-grid", label: "Instructors" }],
5001
+ "/contact": [
5002
+ { id: "hello-hero", label: "Hello" },
5003
+ { id: "contact-form", label: "Contact Form" },
5004
+ { id: "faq", label: "FAQ" }
5005
+ ]
5006
+ };
5007
+ function shouldUseDevFixtures() {
5008
+ if (typeof window === "undefined") return false;
5009
+ const raw = readPreservedSearch();
5010
+ const q = raw.startsWith("?") ? raw.slice(1) : raw;
5011
+ return new URLSearchParams(q).get("ohw-fixtures") === "1";
5012
+ }
5013
+
5014
+ // src/ui/badge.tsx
5015
+ import { jsx as jsx15 } from "react/jsx-runtime";
4184
5016
  var badgeVariants = cva(
4185
5017
  "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
4186
5018
  {
@@ -4198,11 +5030,11 @@ var badgeVariants = cva(
4198
5030
  }
4199
5031
  );
4200
5032
  function Badge({ className, variant, ...props }) {
4201
- return /* @__PURE__ */ jsx5("div", { className: cn(badgeVariants({ variant }), className), ...props });
5033
+ return /* @__PURE__ */ jsx15("div", { className: cn(badgeVariants({ variant }), className), ...props });
4202
5034
  }
4203
5035
 
4204
5036
  // src/OhhwellsBridge.tsx
4205
- import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
5037
+ import { Fragment as Fragment3, jsx as jsx16, jsxs as jsxs8 } from "react/jsx-runtime";
4206
5038
  var PRIMARY = "#0885FE";
4207
5039
  var IMAGE_FADE_MS = 300;
4208
5040
  function runOpacityFade(el, onDone) {
@@ -4342,10 +5174,9 @@ function initSectionsFromContent(content, removeExisting = false) {
4342
5174
  const inEditor = typeof window !== "undefined" && window.self !== window.top;
4343
5175
  if (inEditor) getSectionsTracker().textContent = raw;
4344
5176
  const pageEntries = getPageSchedulingEntries(raw);
4345
- const notifyForEntry = inEditor ? (e) => !e.scheduleId : false;
4346
- mountSchedulingEntries(pageEntries, notifyForEntry);
4347
- requestAnimationFrame(() => retryMissingSchedulingMounts(pageEntries, notifyForEntry));
4348
- setTimeout(() => retryMissingSchedulingMounts(pageEntries, notifyForEntry), 250);
5177
+ mountSchedulingEntries(pageEntries, false);
5178
+ requestAnimationFrame(() => retryMissingSchedulingMounts(pageEntries, false));
5179
+ setTimeout(() => retryMissingSchedulingMounts(pageEntries, false), 250);
4349
5180
  } catch {
4350
5181
  }
4351
5182
  }
@@ -4372,7 +5203,7 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
4372
5203
  const root = createRoot(container);
4373
5204
  flushSync(() => {
4374
5205
  root.render(
4375
- /* @__PURE__ */ jsx6(
5206
+ /* @__PURE__ */ jsx16(
4376
5207
  SchedulingWidget,
4377
5208
  {
4378
5209
  notifyOnConnect,
@@ -4404,16 +5235,23 @@ function mountSchedulingEntries(entries, notifyOnConnect = false) {
4404
5235
  const pending = entries.filter((e) => e.type === "scheduling").sort((a, b) => schedulingMountDepth(a.insertAfter) - schedulingMountDepth(b.insertAfter));
4405
5236
  for (let attempt = 0; attempt < pending.length + 1 && pending.length > 0; attempt++) {
4406
5237
  for (let i = pending.length - 1; i >= 0; i--) {
4407
- const entry = pending[i];
4408
- const shouldNotify = typeof notifyOnConnect === "function" ? notifyOnConnect(entry) : notifyOnConnect;
4409
- if (mountSchedulingWidget(entry.insertAfter, shouldNotify, entry.scheduleId)) {
5238
+ const { insertAfter, scheduleId } = pending[i];
5239
+ if (mountSchedulingWidget(insertAfter, notifyOnConnect, scheduleId)) {
4410
5240
  pending.splice(i, 1);
4411
5241
  }
4412
5242
  }
4413
5243
  }
4414
5244
  }
5245
+ function getLinkHref(el) {
5246
+ const anchor = el instanceof HTMLAnchorElement ? el : el.querySelector("a");
5247
+ return anchor?.getAttribute("href") ?? "";
5248
+ }
5249
+ function applyLinkHref(el, val) {
5250
+ const anchor = el instanceof HTMLAnchorElement ? el : el.querySelector("a");
5251
+ if (anchor) anchor.setAttribute("href", val);
5252
+ }
4415
5253
  function collectEditableNodes() {
4416
- return Array.from(document.querySelectorAll("[data-ohw-editable]")).map((el) => {
5254
+ const nodes = Array.from(document.querySelectorAll("[data-ohw-editable]")).map((el) => {
4417
5255
  if (el.dataset.ohwEditable === "image") {
4418
5256
  const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
4419
5257
  return { key: el.dataset.ohwKey ?? "", type: "image", text: img?.src ?? "" };
@@ -4423,12 +5261,52 @@ function collectEditableNodes() {
4423
5261
  const url = raw.replace(/^url\(['"]?/, "").replace(/['"]?\)$/, "");
4424
5262
  return { key: el.dataset.ohwKey ?? "", type: "bg-image", text: url };
4425
5263
  }
5264
+ if (el.dataset.ohwEditable === "link") {
5265
+ return { key: el.dataset.ohwKey ?? "", type: "link", text: getLinkHref(el) };
5266
+ }
4426
5267
  return {
4427
5268
  key: el.dataset.ohwKey ?? "",
4428
5269
  type: el.dataset.ohwEditable ?? "text",
4429
5270
  text: el.dataset.ohwEditable === "plain" ? el.innerText ?? "" : el.innerHTML
4430
5271
  };
4431
5272
  });
5273
+ document.querySelectorAll("[data-ohw-href-key]").forEach((el) => {
5274
+ const key = el.getAttribute("data-ohw-href-key") ?? "";
5275
+ if (!key) return;
5276
+ nodes.push({ key, type: "link", text: getLinkHref(el) });
5277
+ });
5278
+ return nodes;
5279
+ }
5280
+ function applyLinkByKey(key, val) {
5281
+ document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
5282
+ if (el.dataset.ohwEditable === "link") applyLinkHref(el, val);
5283
+ });
5284
+ document.querySelectorAll(`[data-ohw-href-key="${key}"]`).forEach((el) => {
5285
+ applyLinkHref(el, val);
5286
+ });
5287
+ }
5288
+ function isInsideLinkEditor(target) {
5289
+ return Boolean(
5290
+ target.closest("[data-ohw-link-popover-root]") || target.closest("[data-ohw-link-modal-root]") || target.closest('[data-slot="popover-content"]')
5291
+ );
5292
+ }
5293
+ function getHrefKeyFromElement(el) {
5294
+ if (!el) return null;
5295
+ const anchor = el.closest("[data-ohw-href-key]");
5296
+ if (!anchor) return null;
5297
+ const key = anchor.getAttribute("data-ohw-href-key");
5298
+ if (!key) return null;
5299
+ return { anchor, key };
5300
+ }
5301
+ function titleCaseSectionId(id) {
5302
+ return id.split("-").filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
5303
+ }
5304
+ function collectSections() {
5305
+ return Array.from(document.querySelectorAll("[data-ohw-section]")).map((el) => {
5306
+ const id = el.getAttribute("data-ohw-section") ?? "";
5307
+ const label = el.getAttribute("data-ohw-section-label") ?? titleCaseSectionId(id);
5308
+ return { id, label };
5309
+ });
4432
5310
  }
4433
5311
  var FORCE_PSEUDO_STATES = [
4434
5312
  { pseudo: ":hover", state: "hover" },
@@ -4569,7 +5447,7 @@ var TOOLBAR_GROUPS = [
4569
5447
  ];
4570
5448
  function GlowFrame({ rect, elRef }) {
4571
5449
  const GAP = 6;
4572
- return /* @__PURE__ */ jsx6(
5450
+ return /* @__PURE__ */ jsx16(
4573
5451
  "div",
4574
5452
  {
4575
5453
  ref: elRef,
@@ -4617,10 +5495,12 @@ function FloatingToolbar({
4617
5495
  parentScroll,
4618
5496
  elRef,
4619
5497
  onCommand,
4620
- activeCommands
5498
+ activeCommands,
5499
+ showEditLink,
5500
+ onEditLink
4621
5501
  }) {
4622
5502
  const { top, left, transform } = calcToolbarPos(rect, parentScroll);
4623
- return /* @__PURE__ */ jsx6(
5503
+ return /* @__PURE__ */ jsxs8(
4624
5504
  "div",
4625
5505
  {
4626
5506
  ref: elRef,
@@ -4643,56 +5523,97 @@ function FloatingToolbar({
4643
5523
  pointerEvents: "auto",
4644
5524
  whiteSpace: "nowrap"
4645
5525
  },
4646
- children: TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ jsxs3(React3.Fragment, { children: [
4647
- gi > 0 && /* @__PURE__ */ jsx6("span", { style: { display: "block", width: 1, height: 24, background: "#E7E5E4", flexShrink: 0 } }),
4648
- btns.map((btn) => {
4649
- const isActive = activeCommands.has(btn.cmd);
4650
- return /* @__PURE__ */ jsx6(
4651
- "button",
4652
- {
4653
- title: btn.title,
4654
- onMouseDown: (e) => {
4655
- e.preventDefault();
4656
- onCommand(btn.cmd);
4657
- },
4658
- onMouseEnter: (e) => {
4659
- if (!isActive) e.currentTarget.style.background = "#F5F5F4";
4660
- },
4661
- onMouseLeave: (e) => {
4662
- if (!isActive) e.currentTarget.style.background = "transparent";
4663
- },
4664
- style: {
4665
- display: "flex",
4666
- alignItems: "center",
4667
- justifyContent: "center",
4668
- border: "none",
4669
- background: isActive ? PRIMARY : "transparent",
4670
- borderRadius: 4,
4671
- cursor: "pointer",
4672
- color: isActive ? "#FFFFFF" : "#1C1917",
4673
- flexShrink: 0,
4674
- padding: 6
5526
+ onMouseDown: (e) => e.stopPropagation(),
5527
+ children: [
5528
+ TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ jsxs8(React5.Fragment, { children: [
5529
+ gi > 0 && /* @__PURE__ */ jsx16("span", { style: { display: "block", width: 1, height: 24, background: "#E7E5E4", flexShrink: 0 } }),
5530
+ btns.map((btn) => {
5531
+ const isActive = activeCommands.has(btn.cmd);
5532
+ return /* @__PURE__ */ jsx16(
5533
+ "button",
5534
+ {
5535
+ title: btn.title,
5536
+ onMouseDown: (e) => {
5537
+ e.preventDefault();
5538
+ onCommand(btn.cmd);
5539
+ },
5540
+ onMouseEnter: (e) => {
5541
+ if (!isActive) e.currentTarget.style.background = "#F5F5F4";
5542
+ },
5543
+ onMouseLeave: (e) => {
5544
+ if (!isActive) e.currentTarget.style.background = "transparent";
5545
+ },
5546
+ style: {
5547
+ display: "flex",
5548
+ alignItems: "center",
5549
+ justifyContent: "center",
5550
+ border: "none",
5551
+ background: isActive ? PRIMARY : "transparent",
5552
+ borderRadius: 4,
5553
+ cursor: "pointer",
5554
+ color: isActive ? "#FFFFFF" : "#1C1917",
5555
+ flexShrink: 0,
5556
+ padding: 6
5557
+ },
5558
+ children: /* @__PURE__ */ jsx16(
5559
+ "svg",
5560
+ {
5561
+ width: "16",
5562
+ height: "16",
5563
+ viewBox: "0 0 24 24",
5564
+ fill: "none",
5565
+ stroke: isActive ? "#FFFFFF" : "#1C1917",
5566
+ strokeWidth: "2.5",
5567
+ strokeLinecap: "round",
5568
+ strokeLinejoin: "round",
5569
+ style: isActive && gi > 0 ? { filter: "drop-shadow(0 0 0.5px #fff)" } : void 0,
5570
+ dangerouslySetInnerHTML: { __html: ICONS[btn.cmd] }
5571
+ }
5572
+ )
4675
5573
  },
4676
- children: /* @__PURE__ */ jsx6(
4677
- "svg",
4678
- {
4679
- width: "16",
4680
- height: "16",
4681
- viewBox: "0 0 24 24",
4682
- fill: "none",
4683
- stroke: isActive ? "#FFFFFF" : "#1C1917",
4684
- strokeWidth: "2.5",
4685
- strokeLinecap: "round",
4686
- strokeLinejoin: "round",
4687
- style: isActive && gi > 0 ? { filter: "drop-shadow(0 0 0.5px #fff)" } : void 0,
4688
- dangerouslySetInnerHTML: { __html: ICONS[btn.cmd] }
4689
- }
4690
- )
5574
+ btn.cmd
5575
+ );
5576
+ })
5577
+ ] }, gi)),
5578
+ showEditLink ? /* @__PURE__ */ jsx16(
5579
+ "button",
5580
+ {
5581
+ type: "button",
5582
+ title: "Edit link",
5583
+ onMouseDown: (e) => {
5584
+ e.preventDefault();
5585
+ e.stopPropagation();
5586
+ onEditLink?.();
5587
+ },
5588
+ onClick: (e) => {
5589
+ e.preventDefault();
5590
+ e.stopPropagation();
5591
+ },
5592
+ onMouseEnter: (e) => {
5593
+ e.currentTarget.style.background = "#F5F5F4";
5594
+ },
5595
+ onMouseLeave: (e) => {
5596
+ e.currentTarget.style.background = "transparent";
4691
5597
  },
4692
- btn.cmd
4693
- );
4694
- })
4695
- ] }, gi))
5598
+ style: {
5599
+ display: "flex",
5600
+ alignItems: "center",
5601
+ justifyContent: "center",
5602
+ border: "none",
5603
+ background: "transparent",
5604
+ borderRadius: 4,
5605
+ cursor: "pointer",
5606
+ color: "#1C1917",
5607
+ flexShrink: 0,
5608
+ padding: 6
5609
+ },
5610
+ children: /* @__PURE__ */ jsxs8("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: [
5611
+ /* @__PURE__ */ jsx16("path", { d: "M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" }),
5612
+ /* @__PURE__ */ jsx16("path", { d: "M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" })
5613
+ ] })
5614
+ }
5615
+ ) : null
5616
+ ]
4696
5617
  }
4697
5618
  );
4698
5619
  }
@@ -4706,7 +5627,7 @@ function StateToggle({
4706
5627
  states,
4707
5628
  onStateChange
4708
5629
  }) {
4709
- return /* @__PURE__ */ jsx6(
5630
+ return /* @__PURE__ */ jsx16(
4710
5631
  ToggleGroup,
4711
5632
  {
4712
5633
  "data-ohw-state-toggle": "",
@@ -4720,7 +5641,7 @@ function StateToggle({
4720
5641
  left: rect.right - 8,
4721
5642
  transform: "translateX(-100%)"
4722
5643
  },
4723
- children: states.map((state) => /* @__PURE__ */ jsx6(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
5644
+ children: states.map((state) => /* @__PURE__ */ jsx16(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
4724
5645
  }
4725
5646
  );
4726
5647
  }
@@ -4747,8 +5668,8 @@ function OhhwellsBridge() {
4747
5668
  const router = useRouter();
4748
5669
  const searchParams = useSearchParams();
4749
5670
  const isEditMode = isEditSessionActive();
4750
- const [bridgeRoot, setBridgeRoot] = useState3(null);
4751
- useEffect2(() => {
5671
+ const [bridgeRoot, setBridgeRoot] = useState5(null);
5672
+ useEffect4(() => {
4752
5673
  const figtreeFontId = "ohw-figtree-font";
4753
5674
  if (!document.getElementById(figtreeFontId)) {
4754
5675
  const preconnect1 = Object.assign(document.createElement("link"), { rel: "preconnect", href: "https://fonts.googleapis.com" });
@@ -4772,39 +5693,75 @@ function OhhwellsBridge() {
4772
5693
  }, []);
4773
5694
  const subdomainFromQuery = searchParams.get("subdomain");
4774
5695
  const subdomain = resolveSubdomain(subdomainFromQuery);
4775
- const postToParent = useCallback((data) => {
5696
+ const postToParent = useCallback2((data) => {
4776
5697
  if (typeof window !== "undefined" && window.parent !== window) {
4777
5698
  window.parent.postMessage(data, "*");
4778
5699
  }
4779
5700
  }, []);
4780
- const [fetchState, setFetchState] = useState3("idle");
4781
- const autoSaveTimers = useRef2(/* @__PURE__ */ new Map());
4782
- const activeElRef = useRef2(null);
4783
- const originalContentRef = useRef2(null);
4784
- const activeStateElRef = useRef2(null);
4785
- const parentScrollRef = useRef2(null);
4786
- const toolbarElRef = useRef2(null);
4787
- const glowElRef = useRef2(null);
4788
- const hoveredImageRef = useRef2(null);
4789
- const hoveredImageHasTextOverlapRef = useRef2(false);
4790
- const hoveredGapRef = useRef2(null);
4791
- const imageUnhoverTimerRef = useRef2(null);
4792
- const imageShowTimerRef = useRef2(null);
4793
- const editStylesRef = useRef2(null);
4794
- const activateRef = useRef2(() => {
5701
+ const [fetchState, setFetchState] = useState5("idle");
5702
+ const autoSaveTimers = useRef3(/* @__PURE__ */ new Map());
5703
+ const activeElRef = useRef3(null);
5704
+ const originalContentRef = useRef3(null);
5705
+ const activeStateElRef = useRef3(null);
5706
+ const parentScrollRef = useRef3(null);
5707
+ const toolbarElRef = useRef3(null);
5708
+ const glowElRef = useRef3(null);
5709
+ const hoveredImageRef = useRef3(null);
5710
+ const hoveredImageHasTextOverlapRef = useRef3(false);
5711
+ const hoveredGapRef = useRef3(null);
5712
+ const imageUnhoverTimerRef = useRef3(null);
5713
+ const imageShowTimerRef = useRef3(null);
5714
+ const editStylesRef = useRef3(null);
5715
+ const activateRef = useRef3(() => {
4795
5716
  });
4796
- const deactivateRef = useRef2(() => {
5717
+ const deactivateRef = useRef3(() => {
4797
5718
  });
4798
- const refreshActiveCommandsRef = useRef2(() => {
5719
+ const refreshActiveCommandsRef = useRef3(() => {
4799
5720
  });
4800
- const postToParentRef = useRef2(postToParent);
5721
+ const postToParentRef = useRef3(postToParent);
4801
5722
  postToParentRef.current = postToParent;
4802
- const [toolbarRect, setToolbarRect] = useState3(null);
4803
- const [toggleState, setToggleState] = useState3(null);
4804
- const [maxBadge, setMaxBadge] = useState3(null);
4805
- const [activeCommands, setActiveCommands] = useState3(/* @__PURE__ */ new Set());
4806
- const [sectionGap, setSectionGap] = useState3(null);
4807
- useEffect2(() => {
5723
+ const [toolbarRect, setToolbarRect] = useState5(null);
5724
+ const [toggleState, setToggleState] = useState5(null);
5725
+ const [maxBadge, setMaxBadge] = useState5(null);
5726
+ const [activeCommands, setActiveCommands] = useState5(/* @__PURE__ */ new Set());
5727
+ const [toolbarShowEditLink, setToolbarShowEditLink] = useState5(false);
5728
+ const [linkPopover, setLinkPopover] = useState5(null);
5729
+ const [sitePages, setSitePages] = useState5([]);
5730
+ const [sectionsByPath, setSectionsByPath] = useState5({});
5731
+ const setLinkPopoverRef = useRef3(setLinkPopover);
5732
+ const linkPopoverPanelRef = useRef3(null);
5733
+ const linkPopoverOpenRef = useRef3(false);
5734
+ const linkPopoverGraceUntilRef = useRef3(0);
5735
+ setLinkPopoverRef.current = setLinkPopover;
5736
+ const bumpLinkPopoverGrace = () => {
5737
+ linkPopoverGraceUntilRef.current = Date.now() + 350;
5738
+ };
5739
+ useEffect4(() => {
5740
+ if (!isEditMode) return;
5741
+ const useFixtures = shouldUseDevFixtures();
5742
+ if (useFixtures) {
5743
+ setSitePages(DEV_SITE_PAGES);
5744
+ setSectionsByPath(DEV_SECTIONS_BY_PATH);
5745
+ if (process.env.NODE_ENV === "development") {
5746
+ console.info("[ohhwells-bridge] Dev fixtures loaded (ohw-fixtures=1)");
5747
+ }
5748
+ }
5749
+ const onSitePages = (e) => {
5750
+ if (e.data?.type !== "ow:site-pages" || !Array.isArray(e.data.pages)) return;
5751
+ if (useFixtures) return;
5752
+ setSitePages(
5753
+ e.data.pages.map((p) => ({
5754
+ path: p.path,
5755
+ title: p.title
5756
+ }))
5757
+ );
5758
+ };
5759
+ window.addEventListener("message", onSitePages);
5760
+ if (!useFixtures) postToParent({ type: "ow:request-site-pages" });
5761
+ return () => window.removeEventListener("message", onSitePages);
5762
+ }, [isEditMode, postToParent]);
5763
+ const [sectionGap, setSectionGap] = useState5(null);
5764
+ useEffect4(() => {
4808
5765
  const update = () => {
4809
5766
  const el = activeElRef.current;
4810
5767
  if (el) setToolbarRect(el.getBoundingClientRect());
@@ -4828,10 +5785,10 @@ function OhhwellsBridge() {
4828
5785
  vvp.removeEventListener("resize", update);
4829
5786
  };
4830
5787
  }, []);
4831
- const refreshStateRules = useCallback(() => {
5788
+ const refreshStateRules = useCallback2(() => {
4832
5789
  editStylesRef.current?.forceHover && (editStylesRef.current.forceHover.textContent = collectStateRules());
4833
5790
  }, []);
4834
- const deactivate = useCallback(() => {
5791
+ const deactivate = useCallback2(() => {
4835
5792
  const el = activeElRef.current;
4836
5793
  if (!el) return;
4837
5794
  const key = el.dataset.ohwKey;
@@ -4854,9 +5811,10 @@ function OhhwellsBridge() {
4854
5811
  setToolbarRect(null);
4855
5812
  setMaxBadge(null);
4856
5813
  setActiveCommands(/* @__PURE__ */ new Set());
5814
+ setToolbarShowEditLink(false);
4857
5815
  postToParent({ type: "ow:exit-edit" });
4858
5816
  }, [postToParent]);
4859
- const activate = useCallback((el) => {
5817
+ const activate = useCallback2((el) => {
4860
5818
  if (activeElRef.current === el) return;
4861
5819
  deactivate();
4862
5820
  if (hoveredImageRef.current) {
@@ -4869,6 +5827,7 @@ function OhhwellsBridge() {
4869
5827
  originalContentRef.current = el.innerHTML;
4870
5828
  el.focus();
4871
5829
  setToolbarRect(el.getBoundingClientRect());
5830
+ setToolbarShowEditLink(Boolean(getHrefKeyFromElement(el)));
4872
5831
  postToParent({ type: "ow:enter-edit", key: el.dataset.ohwKey });
4873
5832
  requestAnimationFrame(() => refreshActiveCommandsRef.current());
4874
5833
  }, [deactivate, postToParent]);
@@ -4896,10 +5855,13 @@ function OhhwellsBridge() {
4896
5855
  } else if (el.dataset.ohwEditable === "bg-image") {
4897
5856
  const next = `url('${val}')`;
4898
5857
  if (el.style.backgroundImage !== next) el.style.backgroundImage = next;
5858
+ } else if (el.dataset.ohwEditable === "link") {
5859
+ applyLinkHref(el, val);
4899
5860
  } else if (el.innerHTML !== val) {
4900
5861
  el.innerHTML = val;
4901
5862
  }
4902
5863
  });
5864
+ applyLinkByKey(key, val);
4903
5865
  }
4904
5866
  initSectionsFromContent(content, true);
4905
5867
  return imageLoads.length > 0 ? Promise.all(imageLoads).then(() => {
@@ -4926,7 +5888,7 @@ function OhhwellsBridge() {
4926
5888
  cancelled = true;
4927
5889
  };
4928
5890
  }, [subdomain, isEditMode, pathname]);
4929
- useEffect2(() => {
5891
+ useEffect4(() => {
4930
5892
  if (!subdomain || isEditMode) return;
4931
5893
  let debounceTimer = null;
4932
5894
  const applyFromCache = () => {
@@ -4942,10 +5904,13 @@ function OhhwellsBridge() {
4942
5904
  } else if (el.dataset.ohwEditable === "bg-image") {
4943
5905
  const next = `url('${val}')`;
4944
5906
  if (el.style.backgroundImage !== next) el.style.backgroundImage = next;
5907
+ } else if (el.dataset.ohwEditable === "link") {
5908
+ applyLinkHref(el, val);
4945
5909
  } else if (el.innerHTML !== val) {
4946
5910
  el.innerHTML = val;
4947
5911
  }
4948
5912
  });
5913
+ applyLinkByKey(key, val);
4949
5914
  }
4950
5915
  };
4951
5916
  const scheduleApply = () => {
@@ -4966,10 +5931,10 @@ function OhhwellsBridge() {
4966
5931
  const visible = Boolean(subdomain) && fetchState !== "done";
4967
5932
  el.style.display = visible ? "flex" : "none";
4968
5933
  }, [subdomain, fetchState]);
4969
- useEffect2(() => {
5934
+ useEffect4(() => {
4970
5935
  postToParent({ type: "ow:navigation", path: pathname });
4971
5936
  }, [pathname, postToParent]);
4972
- useEffect2(() => {
5937
+ useEffect4(() => {
4973
5938
  if (!isEditMode) return;
4974
5939
  const measure = () => {
4975
5940
  const h = document.body.scrollHeight;
@@ -4993,7 +5958,7 @@ function OhhwellsBridge() {
4993
5958
  window.removeEventListener("resize", handleResize);
4994
5959
  };
4995
5960
  }, [pathname, isEditMode, postToParent]);
4996
- useEffect2(() => {
5961
+ useEffect4(() => {
4997
5962
  if (!subdomainFromQuery || isEditMode) return;
4998
5963
  const handleClick = (e) => {
4999
5964
  const anchor = e.target.closest("a");
@@ -5009,7 +5974,7 @@ function OhhwellsBridge() {
5009
5974
  document.addEventListener("click", handleClick, true);
5010
5975
  return () => document.removeEventListener("click", handleClick, true);
5011
5976
  }, [subdomainFromQuery, isEditMode, router]);
5012
- useEffect2(() => {
5977
+ useEffect4(() => {
5013
5978
  if (!isEditMode) {
5014
5979
  editStylesRef.current?.base.remove();
5015
5980
  editStylesRef.current?.forceHover.remove();
@@ -5035,6 +6000,7 @@ function OhhwellsBridge() {
5035
6000
  [data-ohw-editable]:not([contenteditable]):not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"]) { cursor: text !important; }
5036
6001
  [data-ohw-editable="image"], [data-ohw-editable="image"] *,
5037
6002
  [data-ohw-editable="bg-image"], [data-ohw-editable="bg-image"] * { cursor: pointer !important; }
6003
+ [data-ohw-editable="link"], [data-ohw-editable="link"] * { cursor: pointer !important; }
5038
6004
  [data-ohw-hovered]:not([contenteditable]) {
5039
6005
  outline: 2px dashed ${PRIMARY} !important;
5040
6006
  outline-offset: 4px;
@@ -5072,8 +6038,21 @@ function OhhwellsBridge() {
5072
6038
  if (target.closest("[data-ohw-toolbar]")) return;
5073
6039
  if (target.closest("[data-ohw-state-toggle]")) return;
5074
6040
  if (target.closest("[data-ohw-max-badge]")) return;
6041
+ if (isInsideLinkEditor(target)) return;
5075
6042
  const editable = target.closest("[data-ohw-editable]");
5076
6043
  if (editable) {
6044
+ if (editable.dataset.ohwEditable === "link") {
6045
+ e.preventDefault();
6046
+ e.stopPropagation();
6047
+ deactivateRef.current();
6048
+ bumpLinkPopoverGrace();
6049
+ setLinkPopoverRef.current({
6050
+ key: editable.dataset.ohwKey ?? "",
6051
+ target: getLinkHref(editable),
6052
+ rect: editable.getBoundingClientRect()
6053
+ });
6054
+ return;
6055
+ }
5077
6056
  if (editable.dataset.ohwEditable === "image" || editable.dataset.ohwEditable === "bg-image") {
5078
6057
  e.preventDefault();
5079
6058
  e.stopPropagation();
@@ -5094,6 +6073,15 @@ function OhhwellsBridge() {
5094
6073
  }
5095
6074
  const anchor = target.closest("a");
5096
6075
  if (anchor) e.preventDefault();
6076
+ if (Date.now() < linkPopoverGraceUntilRef.current) return;
6077
+ if (linkPopoverOpenRef.current && activeElRef.current) {
6078
+ const hrefCtx = getHrefKeyFromElement(activeElRef.current);
6079
+ if (hrefCtx && (hrefCtx.anchor === target || hrefCtx.anchor.contains(target))) return;
6080
+ }
6081
+ if (linkPopoverOpenRef.current) {
6082
+ setLinkPopoverRef.current(null);
6083
+ return;
6084
+ }
5097
6085
  deactivateRef.current();
5098
6086
  };
5099
6087
  const handleMouseOver = (e) => {
@@ -5544,10 +6532,13 @@ function OhhwellsBridge() {
5544
6532
  if (img) img.src = val;
5545
6533
  } else if (el.dataset.ohwEditable === "bg-image") {
5546
6534
  el.style.backgroundImage = `url('${val}')`;
6535
+ } else if (el.dataset.ohwEditable === "link") {
6536
+ applyLinkHref(el, val);
5547
6537
  } else {
5548
6538
  el.innerHTML = val;
5549
6539
  }
5550
6540
  });
6541
+ applyLinkByKey(key, val);
5551
6542
  }
5552
6543
  if (sectionsJson) {
5553
6544
  initSectionsFromContent({ __ohw_sections: sectionsJson }, true);
@@ -5557,10 +6548,19 @@ function OhhwellsBridge() {
5557
6548
  window.addEventListener("message", handleHydrate);
5558
6549
  const handleDeactivate = (e) => {
5559
6550
  if (e.data?.type !== "ow:deactivate") return;
6551
+ if (Date.now() < linkPopoverGraceUntilRef.current) return;
6552
+ if (linkPopoverOpenRef.current) {
6553
+ setLinkPopoverRef.current(null);
6554
+ return;
6555
+ }
5560
6556
  deactivateRef.current();
5561
6557
  };
5562
6558
  window.addEventListener("message", handleDeactivate);
5563
6559
  const handleKeyDown = (e) => {
6560
+ if (e.key === "Escape" && linkPopoverOpenRef.current) {
6561
+ setLinkPopoverRef.current(null);
6562
+ return;
6563
+ }
5564
6564
  if (e.key !== "Escape") return;
5565
6565
  const el = activeElRef.current;
5566
6566
  if (el && originalContentRef.current !== null) {
@@ -5814,7 +6814,7 @@ function OhhwellsBridge() {
5814
6814
  if (imageShowTimerRef.current) clearTimeout(imageShowTimerRef.current);
5815
6815
  };
5816
6816
  }, [isEditMode, refreshStateRules]);
5817
- useEffect2(() => {
6817
+ useEffect4(() => {
5818
6818
  if (!isEditMode) return;
5819
6819
  document.querySelectorAll("[data-ohw-active-state]").forEach((el) => {
5820
6820
  el.removeAttribute("data-ohw-active-state");
@@ -5829,20 +6829,26 @@ function OhhwellsBridge() {
5829
6829
  const raf = requestAnimationFrame(() => refreshStateRules());
5830
6830
  const timer = setTimeout(() => {
5831
6831
  refreshStateRules();
5832
- postToParent({ type: "ow:ready", version: "1", nodes: collectEditableNodes() });
6832
+ const sections = collectSections();
6833
+ setSectionsByPath((prev) => {
6834
+ const next = { ...prev, [pathname]: sections };
6835
+ return shouldUseDevFixtures() ? { ...DEV_SECTIONS_BY_PATH, ...next } : next;
6836
+ });
6837
+ postToParent({ type: "ow:ready", version: "1", path: pathname, nodes: collectEditableNodes(), sections });
6838
+ postToParent({ type: "ow:request-site-pages" });
5833
6839
  }, 150);
5834
6840
  return () => {
5835
6841
  cancelAnimationFrame(raf);
5836
6842
  clearTimeout(timer);
5837
6843
  };
5838
6844
  }, [pathname, isEditMode, refreshStateRules, postToParent]);
5839
- const handleCommand = useCallback((cmd) => {
6845
+ const handleCommand = useCallback2((cmd) => {
5840
6846
  document.execCommand(cmd, false);
5841
6847
  activeElRef.current?.focus();
5842
6848
  if (activeElRef.current) setToolbarRect(activeElRef.current.getBoundingClientRect());
5843
6849
  refreshActiveCommandsRef.current();
5844
6850
  }, []);
5845
- const handleStateChange = useCallback((state) => {
6851
+ const handleStateChange = useCallback2((state) => {
5846
6852
  if (!activeStateElRef.current) return;
5847
6853
  const el = activeStateElRef.current;
5848
6854
  if (state === "Default") {
@@ -5855,13 +6861,48 @@ function OhhwellsBridge() {
5855
6861
  }
5856
6862
  setToggleState((prev) => prev ? { ...prev, activeState: state } : null);
5857
6863
  }, [deactivate]);
6864
+ const closeLinkPopover = useCallback2(() => setLinkPopover(null), []);
6865
+ const openLinkPopoverForActive = useCallback2(() => {
6866
+ const hrefCtx = getHrefKeyFromElement(activeElRef.current);
6867
+ if (!hrefCtx) return;
6868
+ bumpLinkPopoverGrace();
6869
+ setLinkPopover({
6870
+ key: hrefCtx.key,
6871
+ target: getLinkHref(hrefCtx.anchor),
6872
+ rect: hrefCtx.anchor.getBoundingClientRect()
6873
+ });
6874
+ }, []);
6875
+ const handleLinkPopoverSubmit = useCallback2(
6876
+ (target) => {
6877
+ if (!linkPopover) return;
6878
+ const { key } = linkPopover;
6879
+ applyLinkByKey(key, target);
6880
+ postToParent({ type: "ow:change", nodes: [{ key, text: target }] });
6881
+ setLinkPopover(null);
6882
+ },
6883
+ [linkPopover, postToParent]
6884
+ );
6885
+ const showEditLink = toolbarShowEditLink;
6886
+ const currentSections = sectionsByPath[pathname] ?? [];
6887
+ linkPopoverOpenRef.current = linkPopover !== null;
5858
6888
  return bridgeRoot ? createPortal(
5859
- /* @__PURE__ */ jsxs3(Fragment2, { children: [
5860
- toolbarRect && /* @__PURE__ */ jsxs3(Fragment2, { children: [
5861
- /* @__PURE__ */ jsx6(GlowFrame, { rect: toolbarRect, elRef: glowElRef }),
5862
- /* @__PURE__ */ jsx6(FloatingToolbar, { rect: toolbarRect, parentScroll: parentScrollRef.current, elRef: toolbarElRef, onCommand: handleCommand, activeCommands })
6889
+ /* @__PURE__ */ jsxs8(Fragment3, { children: [
6890
+ toolbarRect && /* @__PURE__ */ jsxs8(Fragment3, { children: [
6891
+ /* @__PURE__ */ jsx16(GlowFrame, { rect: toolbarRect, elRef: glowElRef }),
6892
+ /* @__PURE__ */ jsx16(
6893
+ FloatingToolbar,
6894
+ {
6895
+ rect: toolbarRect,
6896
+ parentScroll: parentScrollRef.current,
6897
+ elRef: toolbarElRef,
6898
+ onCommand: handleCommand,
6899
+ activeCommands,
6900
+ showEditLink,
6901
+ onEditLink: openLinkPopoverForActive
6902
+ }
6903
+ )
5863
6904
  ] }),
5864
- maxBadge && /* @__PURE__ */ jsxs3(
6905
+ maxBadge && /* @__PURE__ */ jsxs8(
5865
6906
  "div",
5866
6907
  {
5867
6908
  "data-ohw-max-badge": "",
@@ -5887,7 +6928,7 @@ function OhhwellsBridge() {
5887
6928
  ]
5888
6929
  }
5889
6930
  ),
5890
- toggleState && /* @__PURE__ */ jsx6(
6931
+ toggleState && /* @__PURE__ */ jsx16(
5891
6932
  StateToggle,
5892
6933
  {
5893
6934
  rect: toggleState.rect,
@@ -5896,15 +6937,31 @@ function OhhwellsBridge() {
5896
6937
  onStateChange: handleStateChange
5897
6938
  }
5898
6939
  ),
5899
- sectionGap && /* @__PURE__ */ jsxs3(
6940
+ linkPopover ? /* @__PURE__ */ jsx16(
6941
+ LinkPopover,
6942
+ {
6943
+ rect: linkPopover.rect,
6944
+ parentScroll: parentScrollRef.current,
6945
+ panelRef: linkPopoverPanelRef,
6946
+ open: true,
6947
+ mode: "edit",
6948
+ pages: sitePages,
6949
+ sections: currentSections,
6950
+ sectionsByPath,
6951
+ initialTarget: linkPopover.target,
6952
+ onClose: closeLinkPopover,
6953
+ onSubmit: handleLinkPopoverSubmit
6954
+ }
6955
+ ) : null,
6956
+ sectionGap && /* @__PURE__ */ jsxs8(
5900
6957
  "div",
5901
6958
  {
5902
6959
  "data-ohw-section-insert-line": "",
5903
6960
  className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
5904
6961
  style: { top: sectionGap.y, transform: "translateY(-50%)" },
5905
6962
  children: [
5906
- /* @__PURE__ */ jsx6("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
5907
- /* @__PURE__ */ jsx6(
6963
+ /* @__PURE__ */ jsx16("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
6964
+ /* @__PURE__ */ jsx16(
5908
6965
  Badge,
5909
6966
  {
5910
6967
  className: "px-8 py-1 bg-primary hover:bg-primary text-primary-foreground text-xs font-medium shrink-0 rounded-full cursor-pointer pointer-events-auto",
@@ -5917,7 +6974,7 @@ function OhhwellsBridge() {
5917
6974
  children: "Add Section"
5918
6975
  }
5919
6976
  ),
5920
- /* @__PURE__ */ jsx6("div", { className: "flex-1 bg-primary", style: { height: 3 } })
6977
+ /* @__PURE__ */ jsx16("div", { className: "flex-1 bg-primary", style: { height: 3 } })
5921
6978
  ]
5922
6979
  }
5923
6980
  )
@@ -5926,11 +6983,19 @@ function OhhwellsBridge() {
5926
6983
  ) : null;
5927
6984
  }
5928
6985
  export {
6986
+ LinkEditorPanel,
6987
+ LinkPopover,
5929
6988
  OhhwellsBridge,
5930
6989
  SchedulingWidget,
5931
6990
  Toggle,
5932
6991
  ToggleGroup,
5933
6992
  ToggleGroupItem,
5934
- toggleVariants
6993
+ buildTarget,
6994
+ filterAvailablePages,
6995
+ getEditModeInitialState,
6996
+ isValidUrl,
6997
+ parseTarget,
6998
+ toggleVariants,
6999
+ validateUrlInput
5935
7000
  };
5936
7001
  //# sourceMappingURL=index.js.map