@levo-so/site-runtime 0.1.113

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 ADDED
@@ -0,0 +1,93 @@
1
+ # @levo-so/site-runtime
2
+
3
+ Everything that runs **inside** a Levo Studio v3 site (the TanStack Start apps forked from
4
+ `levo-api/studio/starter`):
5
+
6
+ | Export | What it is |
7
+ |---|---|
8
+ | `Inspector` | The editor-preview element inspector — select / text-edit / staged DOM overrides / layers snapshot, driven by the dashboard over postMessage. Imported directly by the scaffold's `__root`. Inert outside the editor preview. |
9
+ | `PageHead` / `pageHead` / `parseRawHead` | Per-page document metadata composed from SEO-editor sidecars (`src/pages/<page>.meta.ts`) + site defaults (`_site.meta.ts`). |
10
+ | `LevoSiteProvider` / `useLevoSite` / `useLevoInsights` | The Levo client + insights (`createLevoClient` from `@levo-so/core`, `createLevoInsights` from `@levo-so/insights` — core-only, no UI-kit tree). Mounted once from `__root` via the scaffold's `src/lib/levo/provider.tsx` shim, which feeds `VITE_LEVO_API_URL` / `VITE_LEVO_WORKSPACE` / `VITE_LEVO_SITE_ID` / `VITE_LEVO_INSIGHTS_URL` / `VITE_LEVO_APP_MODE`. Both hooks return `null` on an unwired site — consumers stay inert; a null `insightsUrl` runs trackers/identity with zero network. |
11
+ | `IEditorToPreview` / `IPreviewToEditor` + friends | The editor ↔ preview wire contract (types only). The dashboard's `previewMessaging.ts` re-exports these (`IOutboundMessage`/`IInboundMessage` aliases), so a contract change here is a compile error on both sides. |
12
+
13
+ ## How sites consume it
14
+
15
+ **Direct imports** — `__root` imports `Inspector` from the package, and the studio SEO
16
+ editor splices `import { PageHead } from "@levo-so/site-runtime"` into pages it wires.
17
+ No compat shims: old deployments get nuked, not patched (a conscious trade — there is no
18
+ File-API shadow-hotfix; a runtime fix ships via publish → image rebuild → re-provision).
19
+
20
+ The **only** `src/lib/` file left in the scaffold is the env-injection shim — it exists
21
+ because Vite injects `import.meta.env` into site-source modules only, never into packaged
22
+ deps:
23
+
24
+ ```tsx
25
+ // src/lib/levo/provider.tsx — reads VITE_LEVO_API_URL / VITE_LEVO_WORKSPACE /
26
+ // VITE_LEVO_SITE_ID / VITE_LEVO_INSIGHTS_URL / VITE_LEVO_APP_MODE →
27
+ // <LevoSiteProvider config={…}>, mounted once by __root.
28
+ ```
29
+
30
+ ## Update propagation (read before "why isn't my fix live?")
31
+
32
+ Sites do **not** install dependencies. `node_modules` is baked into the `levo-studio-base`
33
+ image and copied into a per-site named Docker volume at provision — then frozen (hibernate/
34
+ wake is `docker stop`/`start`). So:
35
+
36
+ - **New sites** pick up a release once `starter/package.json` bumps and the base image rebuilds.
37
+ - **Existing sites** keep the version their volume was provisioned with until their
38
+ container + volume are recreated from a newer image (a WM "roll" operation — doesn't exist
39
+ yet) — or, per current policy, are simply nuked and re-provisioned.
40
+ - Consequence: the fleet updates out from under generated code. The public API here must
41
+ stay strictly backward-compatible.
42
+
43
+ ## Local development
44
+
45
+ - **In-monorepo (daily loop):** `pnpm --filter @levo-so/site-runtime dev` (watch build);
46
+ consumers in this workspace link via `workspace:*`.
47
+ - **Against the real starter:** `scripts/create-dev-site.sh` generates a machine-local
48
+ copy of `levo-api/studio/starter` (default `<repos-parent>/studio-dev-site`, port 3200)
49
+ with this package `file:`-linked (npm symlinks it, so watch builds propagate),
50
+ `resolve.dedupe: ["react", "react-dom"]` in its Vite config (two Reacts = hooks crash),
51
+ and the demo fixture pages copied in. The copy deliberately lives
52
+ OUTSIDE the monorepo — real sites npm-install standalone, so it must not inherit the
53
+ workspace's pnpm version overrides or node_modules layout; re-run the script (`--force`)
54
+ whenever the starter changes. Run its `npm run dev` + the package watch build, then flip
55
+ the commented `NEXT_PUBLIC_STUDIO_LOCAL_PREVIEW_TEMPLATE` toggle in the dashboard's
56
+ `env/.env.localhost`. Full editor ↔ runtime loop, no Docker. (File-API writes still
57
+ target the provisioned project, not the host-run site — Apply visibly "reverts" there.)
58
+ - **Full pipeline:** rebuild `levo-studio-base:dev` against a `pnpm pack` tarball or a
59
+ changesets snapshot release (`changeset version --snapshot && changeset publish --tag dev`).
60
+
61
+ ## Source layout
62
+
63
+ The whole inspector runtime lives in `src/inspector/`: `index.tsx` is the composition
64
+ root (modes, selection + observer arbitration, pointer tracking — the timing-interlocked
65
+ core) wiring the subsystem factories: `editorChannel` (send + origin lock),
66
+ `originalsStash` (pre-edit text), `textEditController` (contenteditable lifecycle),
67
+ `stagedOverrides` (override registry + sheet), `messageRouter` (inbound dispatch),
68
+ `historyWatcher` (SPA nav). `dom.ts` holds the pure DOM helpers; `protocol.ts` the wire
69
+ contract. `src/pageHead.tsx` and `src/LevoSiteProvider.tsx` are independent of the inspector.
70
+
71
+ ## Shipping a runtime change (starter adopted 2026-07-10)
72
+
73
+ The golden starter (`levo-api/studio/starter`) consumes this package from the registry
74
+ (`^0.3.0`) with direct imports (`__root` → `Inspector`; the SEO splicer writes the
75
+ package import). The pipeline for a change:
76
+
77
+ 1. Changeset → `changeset version` → **publish** (`pnpm --filter @levo-so/site-runtime
78
+ publish`, needs npm creds — the publish MUST land before the next `levo-studio-base`
79
+ image build, which is the only thing that ever installs it).
80
+ 2. A patch (`0.2.x`) flows into the next image build automatically; a minor (`0.3.0`)
81
+ also needs the starter's range bumped.
82
+ 3. New sites pick it up once the image rebuilds; existing sites keep their frozen volume
83
+ until re-provisioned (nuke policy) or a WM roll op exists.
84
+
85
+ ## Protocol change checklist
86
+
87
+ 1. Change `src/inspector/protocol.ts` (and the runtime behavior in `src/inspector/`),
88
+ then rebuild — the dashboard type-checks against `dist/`.
89
+ 2. `pnpm --filter levo-dashboard check-types` — the dashboard imports this contract, so
90
+ drift shows up as compile errors there (its `IInspectorElement` stays a deliberately
91
+ looser WORKING type for palette/Layers-constructed partials; that one is not the wire).
92
+ 3. Add a changeset; note the change in `docs/studio-v3/site-runtime/README.md` (the
93
+ protocol changelog).
@@ -0,0 +1,25 @@
1
+ import { ILevoClient } from '@levo-so/core';
2
+ import { ILevoInsights } from '@levo-so/insights';
3
+ import { ILevoSiteConfig } from './client';
4
+ export type { ILevoSiteConfig };
5
+ /**
6
+ * The site's Levo client, or `null` when the site isn't wired to a Levo backend yet
7
+ * (no `apiUrl` in config — e.g. env not injected at provision). Consumers no-op on null.
8
+ *
9
+ * Explicit return type on purpose: inference would inline core-internal names
10
+ * (`IPageContext`) that its rolled-up d.ts doesn't export → TS4023 on declaration emit.
11
+ */
12
+ export declare const useLevoSite: () => ILevoClient | null;
13
+ /** The site's insights (analytics/track), or `null` on an unwired site. */
14
+ export declare const useLevoInsights: () => ILevoInsights | null;
15
+ /**
16
+ * Mounts the Levo client + insights for a generated site. Rendered once from the
17
+ * scaffold's `__root` (via the env-reading shim); SSR-safe — construction guards its
18
+ * window/document reads, and `insights.init()` (client-side only by contract) runs in
19
+ * an effect after mount.
20
+ */
21
+ export declare const LevoSiteProvider: React.FC<{
22
+ config?: ILevoSiteConfig;
23
+ children: React.ReactNode;
24
+ }>;
25
+ //# sourceMappingURL=LevoSiteProvider.d.ts.map
@@ -0,0 +1,23 @@
1
+ import { createSiteClient as e } from "./client.js";
2
+ import { createLevoInsights as t } from "@levo-so/insights";
3
+ import { createContext as n, useContext as r, useState as i } from "react";
4
+ import { jsx as a } from "react/jsx-runtime";
5
+ //#region src/LevoSiteProvider.tsx
6
+ var o = n(null), s = () => r(o)?.client ?? null, c = () => r(o)?.insights ?? null, l = ({ config: n, children: r }) => {
7
+ let [s] = i(() => {
8
+ let r = e(n);
9
+ return r ? {
10
+ client: r,
11
+ insights: t(r, {
12
+ insightsUrl: n?.insightsUrl || null,
13
+ site: n?.siteId || null
14
+ })
15
+ } : null;
16
+ });
17
+ return /* @__PURE__ */ a(o.Provider, {
18
+ value: s,
19
+ children: r
20
+ });
21
+ };
22
+ //#endregion
23
+ export { l as LevoSiteProvider, c as useLevoInsights, s as useLevoSite };
@@ -0,0 +1,26 @@
1
+ import { ILevoClient } from '@levo-so/core';
2
+ /** Site-level Levo config — the scaffold shim fills this from Vite env. */
3
+ export interface ILevoSiteConfig {
4
+ /** Origin of the Levo public API / proxy the site's data calls go through. */
5
+ apiUrl?: string;
6
+ /** The site's workspace id (`W…`). Invalid/absent → client runs workspace-less. */
7
+ workspace?: string;
8
+ /** The site's id, stamped onto analytics events. */
9
+ siteId?: string;
10
+ /**
11
+ * Analytics reverse-proxy URL. Absent/null → identity + DOM trackers still run
12
+ * (popup triggers work) but nothing is sent — queued events are dropped.
13
+ */
14
+ insightsUrl?: string | null;
15
+ /** Forwarded to the client's APP_MODE; defaults to "production". */
16
+ appMode?: string;
17
+ }
18
+ /**
19
+ * Build the site's core Levo client from config, or `null` when the site isn't wired to a
20
+ * backend yet (no `apiUrl`). SSR-safe — `createLevoClient` guards its window/document reads.
21
+ *
22
+ * Explicit return type on purpose: inference would inline core-internal names its rolled-up
23
+ * d.ts doesn't export → TS4023 on declaration emit.
24
+ */
25
+ export declare const createSiteClient: (config?: ILevoSiteConfig) => ILevoClient | null;
26
+ //# sourceMappingURL=client.d.ts.map
package/dist/client.js ADDED
@@ -0,0 +1,14 @@
1
+ import { createLevoClient as e } from "@levo-so/core";
2
+ //#region src/client.ts
3
+ var t = (t) => {
4
+ let n = (t?.apiUrl || "").replace(/\/$/, "");
5
+ return n ? e({
6
+ apiUrl: n,
7
+ workspace: t?.workspace ? t.workspace : null,
8
+ appName: "studio-site",
9
+ NODE_ENV: "",
10
+ APP_MODE: t?.appMode || "production"
11
+ }) : null;
12
+ };
13
+ //#endregion
14
+ export { t as createSiteClient };
@@ -0,0 +1,101 @@
1
+ import { LevoQuery } from '@levo-so/core';
2
+ import { ILevoSiteConfig } from '../client';
3
+ /**
4
+ * The minimal QueryClient surface `prefetchLevoSources` needs. Structural on purpose — a
5
+ * branded `QueryClient` type in the public API would clash whenever the consuming site
6
+ * resolves a different `@tanstack/react-query` copy/version than the one this package was
7
+ * built against (peer dep). Any real QueryClient satisfies this.
8
+ */
9
+ export interface IQueryClientLike {
10
+ ensureQueryData: (options: {
11
+ queryKey: readonly unknown[];
12
+ queryFn: () => Promise<unknown>;
13
+ }) => Promise<unknown>;
14
+ }
15
+ /** A list binding — collection records or blog posts matching `query`. */
16
+ export interface ILevoSourceConfig {
17
+ /** Only "collection" | "blog" today — the core client has no event/community module. */
18
+ module: "collection" | "blog";
19
+ /** Collection key / blog key. */
20
+ identifier: string;
21
+ /** where / sort / limit / page / search. `limit: -1` = all. */
22
+ query?: LevoQuery.FindQuery;
23
+ /** Reserved (blog sub-module identifiers etc.); round-tripped through the binding. */
24
+ options?: Record<string, unknown>;
25
+ }
26
+ export interface ILevoSourceResult<T = Record<string, unknown>> {
27
+ /** null until loaded, on error, and on an unwired site. */
28
+ data: T[] | null;
29
+ meta: {
30
+ total: number;
31
+ pages: number;
32
+ } | null;
33
+ isLoading: boolean;
34
+ error: string | null;
35
+ /** Spread onto the container that renders the list — stamps the editor binding. */
36
+ bind: {
37
+ "data-levo-source": string;
38
+ };
39
+ }
40
+ /** A single-record binding — one collection record / blog post by `slug` (a detail page). */
41
+ export interface ILevoRecordConfig {
42
+ module: "collection" | "blog";
43
+ identifier: string;
44
+ /**
45
+ * Reserved for field projection. The slug endpoints return the whole record today, so
46
+ * this is round-tripped through the binding but not yet sent to the API.
47
+ */
48
+ query?: Pick<LevoQuery.FindQuery, "select" | "include">;
49
+ }
50
+ export interface ILevoRecordResult<T = Record<string, unknown>> {
51
+ data: T | null;
52
+ isLoading: boolean;
53
+ error: string | null;
54
+ bind: {
55
+ "data-levo-source": string;
56
+ };
57
+ }
58
+ /**
59
+ * One entry in a page's `dataSources` export — a list or record binding, discriminated by
60
+ * `mode`. The route loader reads these to prefetch; the page passes each to its hook. `mode`
61
+ * is optional for lists (the default) and required for records (so the loader knows to use
62
+ * the route slug).
63
+ */
64
+ export type ILevoSourceDef = (ILevoSourceConfig & {
65
+ mode?: "list";
66
+ }) | (ILevoRecordConfig & {
67
+ mode: "record";
68
+ });
69
+ /** The shape a page exports so its route loader can prefetch every binding it renders. */
70
+ export type ILevoDataSources = Record<string, ILevoSourceDef>;
71
+ /**
72
+ * Render a LIST of platform content. The config is a static literal (the editor rewrites
73
+ * it). Reads the router QueryClient: if the route loader prefetched this binding, the first
74
+ * render has data (SSR ships it); otherwise it fetches client-side. Always render a loading
75
+ * and an empty state — `data` is null before the first load and on unwired sites.
76
+ */
77
+ export declare const useLevoSource: <T = Record<string, unknown>>(config: ILevoSourceConfig) => ILevoSourceResult<T>;
78
+ /**
79
+ * Render ONE record by slug — a detail page. `slug` is a RUNTIME arg (the page's route
80
+ * param), kept OUT of the config literal so the binding stays static and editable. No slug
81
+ * (or unwired site) → inert. `data` null after load = not found: render a friendly fallback.
82
+ */
83
+ export declare const useLevoRecord: <T = Record<string, unknown>>(config: ILevoRecordConfig, slug: string | undefined) => ILevoRecordResult<T>;
84
+ /**
85
+ * Prefetch every binding a page declares into the router's QueryClient — call it (awaited)
86
+ * from the route `loader` so SSR renders fully-populated HTML. Builds its own client from
87
+ * the site config (loaders run outside React, so `useLevoSite` isn't available). Record
88
+ * bindings pull their slug from the route `params` (`$slug`). Resilient: a single failed
89
+ * source resolves to a cached error so it doesn't blank the whole page — the hook surfaces
90
+ * that binding's error while the rest render.
91
+ */
92
+ export declare const prefetchLevoSources: ({ queryClient, config, sources, params, }: {
93
+ queryClient: IQueryClientLike;
94
+ /** Site env config — same object the LevoSiteProvider receives. */
95
+ config: ILevoSiteConfig | undefined;
96
+ /** The page's `dataSources` export (undefined = page has none). */
97
+ sources: ILevoDataSources | undefined;
98
+ /** Route params; record bindings read `params.slug`. */
99
+ params?: Record<string, string>;
100
+ }) => Promise<void>;
101
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,79 @@
1
+ import { createSiteClient as e } from "../client.js";
2
+ import { useLevoSite as t } from "../LevoSiteProvider.js";
3
+ import { getLevoError as n } from "@levo-so/core";
4
+ import { useQuery as r } from "@tanstack/react-query";
5
+ //#region src/data/index.ts
6
+ var i = (e) => [
7
+ "levo-source",
8
+ "list",
9
+ e.module,
10
+ e.identifier,
11
+ e.query ?? null,
12
+ e.options ?? null
13
+ ], a = (e, t) => [
14
+ "levo-source",
15
+ "record",
16
+ e.module,
17
+ e.identifier,
18
+ t ?? null
19
+ ], o = async (e, t) => {
20
+ let n = t.module === "collection" ? await e.collection.getCollectionContentList({
21
+ collection_key: t.identifier,
22
+ ...t.query ?? {}
23
+ }) : await e.blog.getAllBlogs(t.identifier, t.query ?? {});
24
+ return {
25
+ data: n.content.data ?? [],
26
+ meta: n.content.meta ?? null
27
+ };
28
+ }, s = async (e, t, n) => (t.module === "collection" ? await e.collection.getCollectionContent({
29
+ collection_key: t.identifier,
30
+ slug: n
31
+ }) : await e.blog.getSingleBlog(t.identifier, n)).content.data ?? null, c = (e) => {
32
+ let a = t(), s = r({
33
+ queryKey: i(e),
34
+ queryFn: () => o(a, e),
35
+ enabled: !!a
36
+ });
37
+ return {
38
+ data: s.data?.data ?? null,
39
+ meta: s.data?.meta ?? null,
40
+ isLoading: s.isLoading,
41
+ error: s.error ? n(s.error).message : null,
42
+ bind: { "data-levo-source": JSON.stringify({
43
+ mode: "list",
44
+ ...e
45
+ }) }
46
+ };
47
+ }, l = (e, i) => {
48
+ let o = t(), c = r({
49
+ queryKey: a(e, i),
50
+ queryFn: () => s(o, e, i),
51
+ enabled: !!o && !!i
52
+ });
53
+ return {
54
+ data: c.data ?? null,
55
+ isLoading: c.isLoading,
56
+ error: c.error ? n(c.error).message : null,
57
+ bind: { "data-levo-source": JSON.stringify({
58
+ mode: "record",
59
+ ...e
60
+ }) }
61
+ };
62
+ }, u = async ({ queryClient: t, config: n, sources: r, params: c }) => {
63
+ let l = e(n);
64
+ !l || !r || await Promise.all(Object.values(r).map((e) => {
65
+ if (e.mode === "record") {
66
+ let n = c?.slug;
67
+ return n ? t.ensureQueryData({
68
+ queryKey: a(e, n),
69
+ queryFn: () => s(l, e, n)
70
+ }).catch(() => void 0) : Promise.resolve();
71
+ }
72
+ return t.ensureQueryData({
73
+ queryKey: i(e),
74
+ queryFn: () => o(l, e)
75
+ }).catch(() => void 0);
76
+ }));
77
+ };
78
+ //#endregion
79
+ export { u as prefetchLevoSources, l as useLevoRecord, c as useLevoSource };
@@ -0,0 +1,26 @@
1
+ /**
2
+ * @levo-so/site-runtime — everything that runs INSIDE a Levo Studio v3 site:
3
+ *
4
+ * - `Inspector` — the editor-preview element inspector (select / text-edit / staged
5
+ * overrides / layers snapshot), driven by the dashboard over postMessage. Mounted by
6
+ * the scaffold's `__root`; inert outside the editor preview (it only reacts to the
7
+ * editor's messages).
8
+ * - `PageHead` / `pageHead` — per-page document metadata composed from SEO-editor
9
+ * sidecars + site defaults.
10
+ * - `LevoSiteProvider` / `useLevoSite` — the Levo client (core-only), mounted from
11
+ * `__root` via the scaffold's env-reading shim.
12
+ * - the editor ↔ preview message contract types (`IEditorToPreview` / `IPreviewToEditor`).
13
+ *
14
+ * Sites import the package directly; the scaffold's single `src/lib/levo/provider.tsx`
15
+ * shim exists only to read Vite env (not injected into packaged deps) into the provider.
16
+ */
17
+ export { createSiteClient } from './client';
18
+ export type { ILevoDataSources, ILevoRecordConfig, ILevoRecordResult, ILevoSourceConfig, ILevoSourceDef, ILevoSourceResult, IQueryClientLike, } from './data';
19
+ export { prefetchLevoSources, useLevoRecord, useLevoSource } from './data';
20
+ export { Inspector } from './inspector';
21
+ export type { IEditorToPreview, IElementLocator, IHistoryFn, ILayersNode, IPageSource, IPatchedHistoryFn, IPreviewToEditor, ISelectedElement, IStagedEntry, IStagedStyleEntry, IStagedTextEntry, ITextChangedElement, } from './inspector/protocol';
22
+ export type { ILevoSiteConfig } from './LevoSiteProvider';
23
+ export { LevoSiteProvider, useLevoInsights, useLevoSite } from './LevoSiteProvider';
24
+ export type { IComposedHead, IPageMeta, ISiteMeta } from './pageHead';
25
+ export { PageHead, pageHead, parseRawHead } from './pageHead';
26
+ //# sourceMappingURL=index.d.ts.map
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ import { createSiteClient as e } from "./client.js";
2
+ import { LevoSiteProvider as t, useLevoInsights as n, useLevoSite as r } from "./LevoSiteProvider.js";
3
+ import { prefetchLevoSources as i, useLevoRecord as a, useLevoSource as o } from "./data/index.js";
4
+ import { Inspector as s } from "./inspector/index.js";
5
+ import { PageHead as c, pageHead as l, parseRawHead as u } from "./pageHead.js";
6
+ export { s as Inspector, t as LevoSiteProvider, c as PageHead, e as createSiteClient, l as pageHead, u as parseRawHead, i as prefetchLevoSources, n as useLevoInsights, a as useLevoRecord, r as useLevoSite, o as useLevoSource };
@@ -0,0 +1,56 @@
1
+ import { ILayersNode, IPageSource, ISelectedElement, IStagedStyleEntry } from './protocol';
2
+ export declare const Z: {
3
+ readonly ring: 99997;
4
+ readonly overlay: 99998;
5
+ readonly label: 99999;
6
+ };
7
+ export declare const COLOR: {
8
+ readonly hover: "#3b82f6";
9
+ readonly hoverBg: "rgba(59,130,246,0.12)";
10
+ readonly hoverLabelBg: "#1d4ed8";
11
+ readonly select: "#16a34a";
12
+ readonly selectGlow: "rgba(22,163,74,0.18)";
13
+ };
14
+ export declare const sourceOf: (el: Element | null) => string | null;
15
+ export declare const nodesForSource: (src: string) => Element[];
16
+ export declare const sourceIndex: (el: Element, src: string) => number;
17
+ export declare const nodeForSource: (src: string, idx?: number) => Element | null;
18
+ export declare const addStagedKey: (el: Element, stageId: string) => void;
19
+ export declare const removeStagedKey: (el: Element, stageId: string) => void;
20
+ export declare const cssRuleFor: (stageId: string, entry: IStagedStyleEntry) => string;
21
+ /** Create the four overlay artifacts (hover box + label, selection ring + label). */
22
+ export declare const createOverlayNodes: () => {
23
+ overlay: HTMLDivElement;
24
+ label: HTMLDivElement;
25
+ ring: HTMLDivElement;
26
+ ringLabel: HTMLDivElement;
27
+ };
28
+ /** Glue a box + its label to an element's viewport rect (shared by the hover overlay and
29
+ * the selection ring — the label clamp + above/below flip live in one place). */
30
+ export declare const positionBox: (box: HTMLElement, boxLabel: HTMLElement, el: Element) => void;
31
+ export declare const externalLinkHref: (target: EventTarget | null) => string | null;
32
+ export declare const isTextLeaf: (el: Element) => boolean;
33
+ /**
34
+ * Everything a selection report carries about `el` — shared by click-picks and the inbound
35
+ * `select-element` command so both emit the identical element-selected shape. `fullText` is
36
+ * present ONLY for a source-stamped text-leaf (so the editor never offers a text edit that
37
+ * would destroy nested markup). The 120-slice `text` feeds labels, not the editor.
38
+ */
39
+ export declare const selectedPayload: (el: Element) => ISelectedElement;
40
+ /**
41
+ * Every `data-levo-source` binding container on the page — the reply to `request-sources`,
42
+ * feeding the editor's page-level Data manager (a list of bindings without needing a
43
+ * selection). Unlike `sourceBindingOf` (nearest ancestor of a clicked node), this enumerates
44
+ * the containers themselves. Binding containers are few (one per bound region), so the
45
+ * per-container `sourceIndex` re-query is cheap — no layers-tree-style single-pass needed.
46
+ */
47
+ export declare const enumerateSources: () => IPageSource[];
48
+ /**
49
+ * Snapshot the live DOM for the editor's Layers panel. Pure pull — no observers, no pushes.
50
+ * `ownNodes` are the runtime's overlay artifacts to skip.
51
+ */
52
+ export declare const buildLayersTree: (ownNodes: readonly Element[]) => {
53
+ tree: ILayersNode;
54
+ truncated: boolean;
55
+ };
56
+ //# sourceMappingURL=dom.d.ts.map