@sevenfold/setto-client 0.6.1 → 0.6.2
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/README.md +4 -0
- package/dist/SettoRepeater.d.ts +17 -0
- package/dist/SettoSection.d.ts +8 -0
- package/dist/edit-mode/mount.d.ts +6 -1
- package/dist/setto-client.js +56 -28
- package/dist/setto-client.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -387,3 +387,7 @@ Produces `dist/setto-client.js` and `.d.ts` via Vite library mode. Only needed b
|
|
|
387
387
|
- Section colour toolbar only offers `brandColors` — no custom hex/rgba input.
|
|
388
388
|
- CTA card colours and nested component colours are not section-themeable yet.
|
|
389
389
|
- Single editor per site (no concurrent-edit conflict handling).
|
|
390
|
+
|
|
391
|
+
## Knowledge base
|
|
392
|
+
|
|
393
|
+
`.wiki/` is this repo's LLM-maintained knowledge bundle (Karpathy LLM-wiki pattern, OKF core): one page per component, edit-mode feature, block-tree module, store and workflow, each with code citations, contracts, failure modes and doc-drift notes. Start at [`.wiki/index.md`](.wiki/index.md) (catalog) or [`.wiki/overview.md`](.wiki/overview.md) (the one page to read first). Keep it in sync with code changes as described in [`.wiki/AGENTS.md`](.wiki/AGENTS.md).
|
package/dist/SettoRepeater.d.ts
CHANGED
|
@@ -12,5 +12,22 @@ export interface SettoRepeaterProps {
|
|
|
12
12
|
/**
|
|
13
13
|
* Renders a dynamic list from i18n. In edit mode, editors can add items (button
|
|
14
14
|
* at the end of the list) and remove items (round × revealed on hover).
|
|
15
|
+
*
|
|
16
|
+
* The published site gets no per-item wrapper — only the editor does.
|
|
17
|
+
*
|
|
18
|
+
* The wrapper exists to give the remove button something to be absolutely
|
|
19
|
+
* positioned against, which is edit chrome and has no business in the output.
|
|
20
|
+
* Leaving it in cost real layout: an item styled `border-b py-7 first:pt-0` is
|
|
21
|
+
* the *only* child of its own wrapper, so `:first-child` matched every item and
|
|
22
|
+
* `pt-0` won on all of them — every row in a repeated list sat flush on the
|
|
23
|
+
* rule above it instead of clearing it by 28px. The same trap catches `last:`,
|
|
24
|
+
* `odd:`, `even:` and `:not(:last-child)`; `last:border-b-0` would have erased
|
|
25
|
+
* every divider on the page.
|
|
26
|
+
*
|
|
27
|
+
* Worse, it was invisible while authoring: in edit mode the remove button is
|
|
28
|
+
* rendered before the children, so the item is not `:first-child` there and the
|
|
29
|
+
* padding appears. The editor showed the correct layout and the published site
|
|
30
|
+
* shipped the broken one. Dropping the wrapper from the published output is
|
|
31
|
+
* what brings the two back into agreement.
|
|
15
32
|
*/
|
|
16
33
|
export declare function SettoRepeater({ itemsKey, defaultItem, itemLabel, children, className, }: SettoRepeaterProps): import("react/jsx-runtime").JSX.Element;
|
package/dist/SettoSection.d.ts
CHANGED
|
@@ -7,5 +7,13 @@ export interface SettoSectionProps extends ComponentPropsWithoutRef<'section'> {
|
|
|
7
7
|
* Wraps a page section with theme-driven background colour and edit-mode
|
|
8
8
|
* selection. Click the section chrome (not editable text or nested blocks)
|
|
9
9
|
* to open the colour toolbar.
|
|
10
|
+
*
|
|
11
|
+
* The section also carries `id={sectionId}`, which is what makes a header's
|
|
12
|
+
* `href="#produkter"` land anywhere. Every generated site so far wrote that nav
|
|
13
|
+
* and shipped it dead: the only handle on the element was
|
|
14
|
+
* `data-setto-section`, an attribute no fragment identifier can address, and a
|
|
15
|
+
* menu that silently does nothing looks exactly like a menu that works until
|
|
16
|
+
* somebody taps it. An explicit `id` prop still wins — it is spread after this
|
|
17
|
+
* one — so a page that needs a different anchor can say so.
|
|
10
18
|
*/
|
|
11
19
|
export declare const SettoSection: import("react").ForwardRefExoticComponent<SettoSectionProps & import("react").RefAttributes<HTMLElement>>;
|
|
@@ -3,12 +3,17 @@ import type { ThemeStore } from '../lib/theme-store';
|
|
|
3
3
|
interface EditModeShellProps {
|
|
4
4
|
children: ReactNode;
|
|
5
5
|
themeStore: ThemeStore | null;
|
|
6
|
+
/** When false, children render unchanged — no portals or document layout. */
|
|
7
|
+
active: boolean;
|
|
6
8
|
}
|
|
7
9
|
/**
|
|
8
10
|
* Edit mode: full-width page with a floating Setto FAB anchored bottom-right.
|
|
9
11
|
* Text is edited inline via contentEditable on `<T>` — no sidebar.
|
|
12
|
+
*
|
|
13
|
+
* Always mounted around the host app so toggling edit mode does not remount
|
|
14
|
+
* page content (which would replay GSAP / scroll-driven animations).
|
|
10
15
|
*/
|
|
11
|
-
export declare function EditModeShell({ children, themeStore }: EditModeShellProps): import("react/jsx-runtime").JSX.Element;
|
|
16
|
+
export declare function EditModeShell({ children, themeStore, active }: EditModeShellProps): import("react/jsx-runtime").JSX.Element;
|
|
12
17
|
/** @deprecated Use EditModeShell */
|
|
13
18
|
export declare const EditModeMount: typeof EditModeShell;
|
|
14
19
|
export {};
|
package/dist/setto-client.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx, jsxs, Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { useState, useEffect, useSyncExternalStore,
|
|
2
|
+
import { useState, useRef, useEffect, useSyncExternalStore, useLayoutEffect, createContext, useContext, useCallback, useMemo, useId, forwardRef, Fragment as Fragment$1 } from "react";
|
|
3
3
|
import { useTranslation } from "react-i18next";
|
|
4
4
|
import { createPortal } from "react-dom";
|
|
5
5
|
function __rest(s, e) {
|
|
@@ -21175,21 +21175,38 @@ const EMPTY = {
|
|
|
21175
21175
|
};
|
|
21176
21176
|
function useSiteAccess(supabase, session, siteId) {
|
|
21177
21177
|
const [state, setState] = useState(EMPTY);
|
|
21178
|
+
const cachedUserIdRef = useRef(null);
|
|
21178
21179
|
useEffect(() => {
|
|
21179
21180
|
if (!session) {
|
|
21181
|
+
cachedUserIdRef.current = null;
|
|
21180
21182
|
setState({ sites: null, currentSite: null, loading: false, error: null });
|
|
21181
21183
|
return;
|
|
21182
21184
|
}
|
|
21185
|
+
const userId = session.user.id;
|
|
21186
|
+
const sameUser = cachedUserIdRef.current === userId;
|
|
21183
21187
|
let cancelled = false;
|
|
21184
|
-
setState((prev) =>
|
|
21188
|
+
setState((prev) => {
|
|
21189
|
+
if (sameUser && prev.sites !== null && !prev.loading) {
|
|
21190
|
+
return prev;
|
|
21191
|
+
}
|
|
21192
|
+
return {
|
|
21193
|
+
...prev,
|
|
21194
|
+
loading: true,
|
|
21195
|
+
error: null,
|
|
21196
|
+
sites: sameUser ? prev.sites : null,
|
|
21197
|
+
currentSite: sameUser ? prev.currentSite : null
|
|
21198
|
+
};
|
|
21199
|
+
});
|
|
21185
21200
|
supabase.from("sites").select("*").then(({ data, error }) => {
|
|
21186
21201
|
if (cancelled) return;
|
|
21187
21202
|
if (error) {
|
|
21203
|
+
cachedUserIdRef.current = userId;
|
|
21188
21204
|
setState({ sites: [], currentSite: null, loading: false, error: error.message });
|
|
21189
21205
|
return;
|
|
21190
21206
|
}
|
|
21191
21207
|
const rows = data ?? [];
|
|
21192
21208
|
const current = rows.find((s) => s.id === siteId) ?? null;
|
|
21209
|
+
cachedUserIdRef.current = userId;
|
|
21193
21210
|
setState({ sites: rows, currentSite: current, loading: false, error: null });
|
|
21194
21211
|
});
|
|
21195
21212
|
return () => {
|
|
@@ -21217,6 +21234,7 @@ const STYLE_ID$1 = "setto-edit-layout";
|
|
|
21217
21234
|
const HTML_CLASS = "setto-edit-mode";
|
|
21218
21235
|
function useSettoDocumentLayout(active) {
|
|
21219
21236
|
useEffect(() => {
|
|
21237
|
+
if (!active) return;
|
|
21220
21238
|
const html = document.documentElement;
|
|
21221
21239
|
html.classList.add(HTML_CLASS);
|
|
21222
21240
|
html.style.setProperty("--setto-fab-size", `${FAB_SIZE}px`);
|
|
@@ -22765,12 +22783,18 @@ function UndoIcon() {
|
|
|
22765
22783
|
/* @__PURE__ */ jsx("path", { d: "M21 17a9 9 0 0 0-15-6.7L3 13" })
|
|
22766
22784
|
] });
|
|
22767
22785
|
}
|
|
22768
|
-
function EditModeShell({ children, themeStore }) {
|
|
22769
|
-
useSettoDocumentLayout(
|
|
22786
|
+
function EditModeShell({ children, themeStore, active }) {
|
|
22787
|
+
useSettoDocumentLayout(active);
|
|
22770
22788
|
const [toolbarRoot, setToolbarRoot] = useState(null);
|
|
22771
22789
|
const [hintRoot, setHintRoot] = useState(null);
|
|
22772
22790
|
const [sectionToolbarRoot, setSectionToolbarRoot] = useState(null);
|
|
22773
22791
|
useEffect(() => {
|
|
22792
|
+
if (!active) {
|
|
22793
|
+
setToolbarRoot(null);
|
|
22794
|
+
setHintRoot(null);
|
|
22795
|
+
setSectionToolbarRoot(null);
|
|
22796
|
+
return;
|
|
22797
|
+
}
|
|
22774
22798
|
const toolbar = document.createElement("div");
|
|
22775
22799
|
toolbar.id = "setto-toolbar-root";
|
|
22776
22800
|
document.body.appendChild(toolbar);
|
|
@@ -22790,12 +22814,12 @@ function EditModeShell({ children, themeStore }) {
|
|
|
22790
22814
|
hint.remove();
|
|
22791
22815
|
sectionBar.remove();
|
|
22792
22816
|
};
|
|
22793
|
-
}, []);
|
|
22817
|
+
}, [active]);
|
|
22794
22818
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
22795
22819
|
children,
|
|
22796
|
-
toolbarRoot ? createPortal(/* @__PURE__ */ jsx(EditToolbar, {}), toolbarRoot) : null,
|
|
22797
|
-
hintRoot ? createPortal(/* @__PURE__ */ jsx(EditLinkHint, {}), hintRoot) : null,
|
|
22798
|
-
sectionToolbarRoot && themeStore ? createPortal(/* @__PURE__ */ jsx(SectionToolbar, {}), sectionToolbarRoot) : null
|
|
22820
|
+
active && toolbarRoot ? createPortal(/* @__PURE__ */ jsx(EditToolbar, {}), toolbarRoot) : null,
|
|
22821
|
+
active && hintRoot ? createPortal(/* @__PURE__ */ jsx(EditLinkHint, {}), hintRoot) : null,
|
|
22822
|
+
active && sectionToolbarRoot && themeStore ? createPortal(/* @__PURE__ */ jsx(SectionToolbar, {}), sectionToolbarRoot) : null
|
|
22799
22823
|
] });
|
|
22800
22824
|
}
|
|
22801
22825
|
const SettoContext = createContext(null);
|
|
@@ -22855,7 +22879,8 @@ function SettoProvider({ config, children }) {
|
|
|
22855
22879
|
themeStore: storeReady ? themeStoreRef.current : null,
|
|
22856
22880
|
assetStore: storeReady ? assetStoreRef.current : null
|
|
22857
22881
|
};
|
|
22858
|
-
|
|
22882
|
+
const editShellActive = editMode && !!value.store;
|
|
22883
|
+
return /* @__PURE__ */ jsx(SettoContext.Provider, { value, children: /* @__PURE__ */ jsx(SectionEditProvider, { children: /* @__PURE__ */ jsx(EditModeShell, { active: editShellActive, themeStore: value.themeStore, children }) }) });
|
|
22859
22884
|
}
|
|
22860
22885
|
function useSetto() {
|
|
22861
22886
|
const ctx = useContext(SettoContext);
|
|
@@ -23207,6 +23232,7 @@ const SettoSection = forwardRef(
|
|
|
23207
23232
|
"section",
|
|
23208
23233
|
{
|
|
23209
23234
|
ref,
|
|
23235
|
+
id: sectionId,
|
|
23210
23236
|
"data-setto-section": sectionId,
|
|
23211
23237
|
className,
|
|
23212
23238
|
style: mergedStyle,
|
|
@@ -23716,25 +23742,27 @@ function SettoRepeater({
|
|
|
23716
23742
|
};
|
|
23717
23743
|
const canRemove = keys.length > 1;
|
|
23718
23744
|
return /* @__PURE__ */ jsxs("div", { className, "data-setto-repeater": true, children: [
|
|
23719
|
-
keys.map(
|
|
23720
|
-
|
|
23721
|
-
|
|
23722
|
-
|
|
23723
|
-
|
|
23724
|
-
|
|
23725
|
-
|
|
23726
|
-
|
|
23727
|
-
|
|
23728
|
-
|
|
23729
|
-
e
|
|
23730
|
-
|
|
23731
|
-
|
|
23732
|
-
|
|
23733
|
-
|
|
23734
|
-
|
|
23735
|
-
|
|
23736
|
-
|
|
23737
|
-
|
|
23745
|
+
keys.map(
|
|
23746
|
+
(itemKey, index) => editable ? /* @__PURE__ */ jsxs("div", { className: "setto-repeater-item", style: { position: "relative" }, children: [
|
|
23747
|
+
canRemove ? /* @__PURE__ */ jsx(
|
|
23748
|
+
"button",
|
|
23749
|
+
{
|
|
23750
|
+
type: "button",
|
|
23751
|
+
"data-setto-ui": true,
|
|
23752
|
+
className: "setto-repeater-remove",
|
|
23753
|
+
"aria-label": `Fjern ${itemLabel}`,
|
|
23754
|
+
title: `Fjern ${itemLabel}`,
|
|
23755
|
+
onClick: (e) => {
|
|
23756
|
+
e.stopPropagation();
|
|
23757
|
+
handleRemove(itemKey);
|
|
23758
|
+
},
|
|
23759
|
+
style: removeBtnStyle,
|
|
23760
|
+
children: "×"
|
|
23761
|
+
}
|
|
23762
|
+
) : null,
|
|
23763
|
+
children(itemKey, index)
|
|
23764
|
+
] }, itemKey) : /* @__PURE__ */ jsx(Fragment$1, { children: children(itemKey, index) }, itemKey)
|
|
23765
|
+
),
|
|
23738
23766
|
editable ? /* @__PURE__ */ jsx(
|
|
23739
23767
|
"div",
|
|
23740
23768
|
{
|