smoodly 0.0.53 → 0.0.54

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/dist/admin/editor/CanvasOverlay.d.ts +3 -1
  2. package/dist/admin/editor/CanvasOverlay.js +2 -2
  3. package/dist/admin/editor/EditorView.js +243 -63
  4. package/dist/admin/editor/InsertPicker.d.ts +8 -1
  5. package/dist/admin/editor/InsertPicker.js +2 -2
  6. package/dist/admin/editor/InspectorFields.js +1 -1
  7. package/dist/admin/editor/Outline.d.ts +2 -3
  8. package/dist/admin/editor/PageInspector.js +1 -1
  9. package/dist/admin/editor/StructureView.d.ts +31 -0
  10. package/dist/admin/editor/StructureView.js +58 -0
  11. package/dist/admin/editor/card-preview.d.ts +10 -0
  12. package/dist/admin/editor/card-preview.js +50 -0
  13. package/dist/admin/editor/global-edit.js +29 -22
  14. package/dist/admin/editor/history.d.ts +39 -0
  15. package/dist/admin/editor/history.js +64 -0
  16. package/dist/admin/editor/section-clipboard.d.ts +40 -0
  17. package/dist/admin/editor/section-clipboard.js +106 -0
  18. package/dist/admin/editor/undo-keys.d.ts +19 -0
  19. package/dist/admin/editor/undo-keys.js +27 -0
  20. package/dist/admin/editor/useInlineSession.d.ts +4 -1
  21. package/dist/admin/editor/useInlineSession.js +11 -2
  22. package/dist/admin/editor/view-memory.d.ts +9 -0
  23. package/dist/admin/editor/view-memory.js +17 -0
  24. package/dist/admin/files.generated.js +1 -1
  25. package/dist/admin/ui/ActionMenu.d.ts +2 -0
  26. package/dist/admin/ui/ActionMenu.js +1 -1
  27. package/dist/admin/ui/primitives.d.ts +1 -0
  28. package/dist/admin/ui/primitives.js +1 -1
  29. package/dist/admin/ui/theme.js +70 -0
  30. package/dist/admin/ui/useSortable.js +3 -1
  31. package/dist/version.d.ts +1 -1
  32. package/dist/version.js +1 -1
  33. package/package.json +1 -1
@@ -3,6 +3,8 @@ export type ActionMenuItem = {
3
3
  run(): void;
4
4
  disabled?: boolean;
5
5
  danger?: boolean;
6
+ /** The reason on a disabled entry (a paste the zone refuses), shown as the button's title. */
7
+ title?: string;
6
8
  };
7
9
  /** The button's box in viewport pixels, `right` measured from the viewport's right edge. */
8
10
  type Anchor = {
@@ -68,5 +68,5 @@ export function ActionMenu({ label, items, disabled, small = false }) {
68
68
  document.removeEventListener("scroll", onScroll, true);
69
69
  };
70
70
  }, [open]);
71
- return (_jsxs("div", { ref: ref, style: { position: "relative", flex: "none" }, children: [_jsx("button", { ref: button, type: "button", className: cx("sm-dotmenu", small && "sm-dotmenu--sm"), "aria-label": label, title: label, "aria-haspopup": "menu", "aria-expanded": open !== null, disabled: disabled, onClick: toggle, children: "\u00B7\u00B7\u00B7" }), open && (_jsx("div", { ref: menu, role: "menu", className: "sm-picker sm-picker--menu", style: place ?? { top: open.bottom + 6, right: open.right, visibility: "hidden" }, children: items.map((item) => (_jsx("button", { type: "button", role: "menuitem", className: cx("sm-picker__item", item.danger && "sm-picker__item--danger"), disabled: item.disabled, onClick: () => { setOpen(null); item.run(); }, children: item.label }, item.label))) }))] }));
71
+ return (_jsxs("div", { ref: ref, style: { position: "relative", flex: "none" }, children: [_jsx("button", { ref: button, type: "button", className: cx("sm-dotmenu", small && "sm-dotmenu--sm"), "aria-label": label, title: label, "aria-haspopup": "menu", "aria-expanded": open !== null, disabled: disabled, onClick: toggle, children: "\u00B7\u00B7\u00B7" }), open && (_jsx("div", { ref: menu, role: "menu", className: "sm-picker sm-picker--menu", style: place ?? { top: open.bottom + 6, right: open.right, visibility: "hidden" }, children: items.map((item) => (_jsx("button", { type: "button", role: "menuitem", className: cx("sm-picker__item", item.danger && "sm-picker__item--danger"), disabled: item.disabled, title: item.title, onClick: () => { setOpen(null); item.run(); }, children: item.label }, item.label))) }))] }));
72
72
  }
@@ -103,6 +103,7 @@ export declare function Segmented<T extends string>({ options, value, onChange,
103
103
  disabled?: boolean;
104
104
  title?: string;
105
105
  className?: string;
106
+ "aria-label"?: string;
106
107
  } | undefined;
107
108
  }): import("react").JSX.Element;
108
109
  export declare function StatusBadge({ status }: {
@@ -103,7 +103,7 @@ export function Tabs({ tabs, value, onChange }) {
103
103
  export function Segmented({ options, value, onChange, stretch, disabled, label, option, className }) {
104
104
  return (_jsx("div", { className: cx("sm-seg", stretch && "sm-seg--stretch", disabled && "sm-seg--disabled", className), children: options.map((o) => {
105
105
  const extra = option?.(o);
106
- return (_jsx("button", { type: "button", className: cx("sm-seg__opt", extra?.className), "aria-pressed": o === value, disabled: disabled || extra?.disabled, title: extra?.title, onClick: () => onChange(o), children: label ? label(o) : o }, o));
106
+ return (_jsx("button", { type: "button", className: cx("sm-seg__opt", extra?.className), "aria-pressed": o === value, disabled: disabled || extra?.disabled, title: extra?.title, "aria-label": extra?.["aria-label"], onClick: () => onChange(o), children: label ? label(o) : o }, o));
107
107
  }) }));
108
108
  }
109
109
  /* ── data ────────────────────────────────────────────────────────────── */
@@ -638,6 +638,8 @@ body:has(.sm-admin){margin:0}
638
638
  .sm-seg{display:flex;gap:2px;padding:3px;background:var(--seg-bg);border-radius:var(--radius-btn)}
639
639
  /* A locale switch: each option wide enough to read as a tab, not a chip. */
640
640
  .sm-seg--locale .sm-seg__opt{min-width:64px}
641
+ /* The Canvas | Structure switch: two icon options. */
642
+ .sm-seg--view .sm-seg__opt{padding:4px 8px;display:grid;place-items:center}
641
643
  .sm-seg--stretch{width:100%}
642
644
  .sm-seg--disabled{opacity:.6}
643
645
  .sm-seg__opt:disabled{cursor:default}
@@ -768,6 +770,11 @@ body:has(.sm-admin){margin:0}
768
770
  position:relative;background:var(--page-bg);border-radius:var(--page-radius);
769
771
  box-shadow:var(--shadow-page);min-height:100%;
770
772
  }
773
+ /* Structure (spec 2026-10-19 editor at launch §1): the structure layer takes the scroller; the frame stays mounted
774
+ and hidden under it: the wrapper is a zero-height absolute box that clips the frame (so the scroller does not grow by the
775
+ page's height), while the frame's own box keeps its width and height for the bridge. After .sm-page: it overrides its position. */
776
+ .sm-canvas__scroll--structure{position:relative}
777
+ .sm-page.sm-canvas__hidden{visibility:hidden;position:absolute;left:0;top:0;width:100%;height:0;min-height:0;pointer-events:none}
771
778
  .sm-canvas__loading{
772
779
  height:240px;display:grid;place-items:center;color:var(--text-subtle);font-family:var(--meta-font);
773
780
  font-size:var(--meta-size);letter-spacing:var(--caps-tracking);text-transform:var(--caps-transform);
@@ -834,6 +841,67 @@ body:has(.sm-admin){margin:0}
834
841
  /* A row with a grip: the grip takes the left padding's place and the label follows it. */
835
842
  .sm-outline-row--grip{padding-left:2px}
836
843
  .sm-outline-row--grip .sm-outline-row__label{flex:1}
844
+ /* The structure view (spec 2026-10-19 editor at launch §1): the canvas column as rows of cards, the
845
+ outline's boxes grown up — a zone's caps label, a card per section on the outline's white, the
846
+ outline's selected fill and drift stripes. The add affordance between cards is the "+" pill at
847
+ label size, faint until its row is hovered. */
848
+ .sm-structure{max-width:720px;margin:0 auto;padding:var(--canvas-pad);padding-bottom:40px;display:flex;flex-direction:column;gap:20px}
849
+ .sm-structure__group{display:flex;flex-direction:column;gap:4px}
850
+ .sm-structure__zone{display:flex;align-items:center;justify-content:space-between;gap:8px;min-height:24px}
851
+ .sm-structure__zone .sm-sectionlabel{flex:1;min-width:0}
852
+ .sm-structure__rows{display:flex;flex-direction:column}
853
+ .sm-structure__row{display:flex;flex-direction:column}
854
+ .sm-structure__card{
855
+ display:grid;grid-template-columns:24px 32px minmax(0,1fr) auto auto;gap:10px;align-items:center;
856
+ width:100%;min-height:56px;padding:8px 6px 8px 2px;text-align:left;font:inherit;
857
+ border-radius:var(--outline-row-radius);background:var(--outline-row-bg);border:var(--outline-row-border);
858
+ color:var(--text);cursor:pointer;
859
+ }
860
+ .sm-structure__card:hover{background:var(--control-hover)}
861
+ .sm-structure__card:hover .sm-grip{opacity:1}
862
+ .sm-structure__card:focus-visible{outline:2px solid var(--accent);outline-offset:-2px}
863
+ .sm-structure__card--selected,.sm-structure__card--selected:hover{background:var(--nav-active-bg);color:var(--nav-active-fg);border-color:transparent}
864
+ .sm-structure__card--locked{background:var(--surface-inset);color:var(--text-muted);border-color:transparent}
865
+ .sm-structure__card--broken{box-shadow:inset 3px 0 0 var(--danger)}
866
+ .sm-structure__card--orphan{box-shadow:inset 3px 0 0 var(--line-dashed)}
867
+ .sm-structure__gripslot{width:24px;height:24px}
868
+ .sm-structure__icon{
869
+ width:32px;height:32px;display:grid;place-items:center;border-radius:var(--radius-sm);
870
+ background:var(--surface-inset);font-size:var(--text-lg);line-height:1;flex:none;
871
+ }
872
+ .sm-structure__card--selected .sm-structure__icon,.sm-structure__card--locked .sm-structure__icon{background:color-mix(in srgb,currentColor 10%,transparent)}
873
+ .sm-structure__text{display:flex;flex-direction:column;gap:2px;min-width:0}
874
+ .sm-structure__title{font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;display:flex;align-items:center;gap:6px;min-width:0}
875
+ .sm-structure__badge{
876
+ font-size:var(--text-sm);font-weight:500;letter-spacing:var(--caps-tracking);text-transform:var(--caps-transform);
877
+ color:var(--text-muted);flex:none;
878
+ }
879
+ .sm-structure__card--selected .sm-structure__badge{color:inherit;opacity:.8}
880
+ .sm-structure__preview{font-size:var(--text-sm);color:var(--text-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:400}
881
+ .sm-structure__card--selected .sm-structure__preview{color:inherit;opacity:.8}
882
+ .sm-structure__thumb{width:44px;height:30px;border-radius:4px;overflow:hidden;background:var(--surface-inset);flex:none;display:block}
883
+ .sm-structure__img{width:100%;height:100%;object-fit:cover;display:block}
884
+ .sm-structure__menu{display:flex;flex:none}
885
+ .sm-structure__slot{display:flex;justify-content:center;padding:4px 0}
886
+ .sm-structure__add{
887
+ display:inline-flex;align-items:center;gap:6px;height:24px;padding:0 10px 0 4px;border-radius:999px;border:0;
888
+ background:var(--plus-bg);color:var(--plus-fg);font:500 var(--text-sm)/1 var(--font-sans);cursor:pointer;
889
+ box-shadow:var(--plus-shadow);white-space:nowrap;opacity:.35;transition:opacity .12s,background .12s;
890
+ }
891
+ .sm-structure__add-icon{
892
+ width:16px;height:16px;flex:none;border-radius:50%;background:var(--plus-icon-bg);color:var(--plus-icon-fg);
893
+ display:grid;place-items:center;font-size:12px;line-height:1;
894
+ }
895
+ .sm-structure__row:hover .sm-structure__add,.sm-structure__slot:hover .sm-structure__add,.sm-structure__add:focus-visible,.sm-structure__empty .sm-structure__add{opacity:1}
896
+ .sm-structure__add:hover{background:var(--plus-hover)}
897
+ /* The lifted row keeps the hook's transform and z-index; its card carries the shadow, its add slot hides. */
898
+ .sm-structure__row.sm-drag--lifted{background:transparent;box-shadow:none}
899
+ .sm-structure__row.sm-drag--lifted .sm-structure__card{box-shadow:var(--shadow-pop)}
900
+ .sm-structure__row.sm-drag--lifted .sm-structure__slot{visibility:hidden}
901
+ .sm-structure__empty{
902
+ display:flex;align-items:center;justify-content:space-between;gap:8px;min-height:var(--outline-row-h);padding:4px 12px;
903
+ border:1px dashed var(--line-dashed);border-radius:var(--outline-row-radius);color:var(--text-muted);font-weight:500;
904
+ }
837
905
 
838
906
  .sm-picker{
839
907
  width:300px;background:var(--menu-bg);border:var(--menu-border);border-radius:var(--radius-md);
@@ -847,6 +915,8 @@ body:has(.sm-admin){margin:0}
847
915
  /* after the base item rule: same specificity, so source order decides (ActionMenu states) */
848
916
  .sm-picker__item:disabled{opacity:.4;cursor:default}
849
917
  .sm-picker__item:disabled:hover{background:transparent}
918
+ /* The clipboard's entry leads the picker (spec 2026-10-19 editor at launch §3), set off from the sections under it. */
919
+ .sm-picker__item--paste{border-bottom:1px solid var(--line);margin-bottom:4px;border-radius:var(--radius-sm) var(--radius-sm) 0 0}
850
920
  .sm-picker__item--danger{color:var(--danger)}
851
921
  .sm-picker__icon{
852
922
  width:26px;height:26px;flex:none;border-radius:var(--radius-sm);background:var(--accent-soft);
@@ -172,7 +172,9 @@ export function useSortable(options) {
172
172
  window.addEventListener("keydown", key);
173
173
  };
174
174
  const onKeyDown = (index) => (e) => {
175
- e.stopPropagation(); // the row's own Enter/Space handler must not see the grip's keys
175
+ // The row's own Enter/Space handler must not see the grip's keys; every other key (the editor's Cmd+Z, review 2026-10-20) bubbles on.
176
+ if (e.key === "Enter" || e.key === " " || e.key === "ArrowUp" || e.key === "ArrowDown")
177
+ e.stopPropagation();
176
178
  if (e.key !== "ArrowUp" && e.key !== "ArrowDown")
177
179
  return;
178
180
  e.preventDefault();
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const SMOODLY_VERSION = "0.0.53";
1
+ export declare const SMOODLY_VERSION = "0.0.54";
package/dist/version.js CHANGED
@@ -1,2 +1,2 @@
1
1
  // Generated by scripts/write-version.mjs from package.json — do not edit.
2
- export const SMOODLY_VERSION = "0.0.53";
2
+ export const SMOODLY_VERSION = "0.0.54";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "smoodly",
3
- "version": "0.0.53",
3
+ "version": "0.0.54",
4
4
  "description": "Open-source, code-first CMS for Astro and Next.js: define a component once, it becomes a typed, editable building block.",
5
5
  "license": "MIT",
6
6
  "repository": {