@bloklabs/react 1.16.1 → 2.1.0
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 +23 -0
- package/dist/index.cjs +1 -1
- package/dist/index.mjs +365 -353
- package/package.json +3 -4
- package/types/index.d.ts +63 -4
- package/src/BlockPortalHost.tsx +0 -58
- package/src/BlokContent.tsx +0 -112
- package/src/BlokEditor.tsx +0 -201
- package/src/BlokView.tsx +0 -106
- package/src/block-portal-registry.ts +0 -161
- package/src/blocks-snapshot.ts +0 -27
- package/src/blok-instance-context.ts +0 -35
- package/src/config-keys.ts +0 -72
- package/src/content-baseline-map.ts +0 -38
- package/src/createReactBlock.tsx +0 -1062
- package/src/createReactInlineTool.tsx +0 -406
- package/src/deep-equal.ts +0 -7
- package/src/holder-map.ts +0 -17
- package/src/index.ts +0 -62
- package/src/live-tool-config.ts +0 -154
- package/src/provide-blok.tsx +0 -70
- package/src/registry-map.ts +0 -21
- package/src/types.ts +0 -38
- package/src/useBlocks.ts +0 -119
- package/src/useBlok.ts +0 -908
- package/src/useBlokHandle.ts +0 -141
- package/src/useBlokReady.ts +0 -60
- package/src/useBlokView.ts +0 -70
- package/src/view-nodes-to-react.ts +0 -153
package/src/useBlokHandle.ts
DELETED
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
import { useRef, useMemo, type RefCallback } from 'react';
|
|
2
|
-
import { EMPTY_OUTPUT_DATA } from '@bloklabs/core/adapters';
|
|
3
|
-
import { getContentBaseline } from './content-baseline-map';
|
|
4
|
-
import type { Blok, OutputData, LooseOutputData } from '@/types';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* A typed, null-safe imperative handle for a Blok editor embedded via
|
|
8
|
-
* `<BlokEditor>`. Solves the "raw ref is `Blok | null` until ready" papercut:
|
|
9
|
-
* instead of every consumer re-declaring a `forwardRef`/`useImperativeHandle`
|
|
10
|
-
* with `?.` guards, `useBlokHandle()` returns this stable object whose methods
|
|
11
|
-
* safely no-op until the editor is ready.
|
|
12
|
-
*
|
|
13
|
-
* Attach it via `ref`: `<BlokEditor ref={handle.ref} … />`. The methods read the
|
|
14
|
-
* live instance through an internal ref, so they can be called from event
|
|
15
|
-
* handlers without re-subscribing, and they stay valid across re-renders.
|
|
16
|
-
*
|
|
17
|
-
* `current` / `isReady` are IMPERATIVE reads (they reflect the instance at call
|
|
18
|
-
* time and do NOT trigger a re-render when readiness flips). For render-reactive
|
|
19
|
-
* readiness use `useBlokReady`; drop to `current` when you need the full editor
|
|
20
|
-
* API beyond the handful of shortcuts below.
|
|
21
|
-
*/
|
|
22
|
-
export interface BlokEditorHandle {
|
|
23
|
-
/**
|
|
24
|
-
* Ref callback to attach to `<BlokEditor ref={handle.ref} />`. Stable across
|
|
25
|
-
* renders. Stores the live instance (or null before ready / after unmount).
|
|
26
|
-
*/
|
|
27
|
-
readonly ref: RefCallback<Blok | null>;
|
|
28
|
-
/** The live editor instance, or null before ready / after unmount. */
|
|
29
|
-
readonly current: Blok | null;
|
|
30
|
-
/** True once the editor is ready (the ref has been populated). */
|
|
31
|
-
readonly isReady: boolean;
|
|
32
|
-
/**
|
|
33
|
-
* Focus the editor. Returns whether focus landed; `false` (no-op) before ready.
|
|
34
|
-
* @param atEnd - place the caret at the end of the focused block
|
|
35
|
-
*/
|
|
36
|
-
focus(atEnd?: boolean): boolean;
|
|
37
|
-
/**
|
|
38
|
-
* Clear all content. Resolves immediately (no-op) before ready.
|
|
39
|
-
*
|
|
40
|
-
* Safe to mix with a controlled `data` prop: the adapter's content baseline is
|
|
41
|
-
* updated once the clear lands, so setting `data` back to the document the
|
|
42
|
-
* editor itself emitted re-renders it instead of being dismissed as an echo.
|
|
43
|
-
*/
|
|
44
|
-
clear(): Promise<void>;
|
|
45
|
-
/** Serialize the current content, or resolve to `null` before ready. */
|
|
46
|
-
save(): Promise<OutputData | null>;
|
|
47
|
-
/** Ask before an in-app navigation (see `Blok.confirmLeave`). Resolves `true` before ready. */
|
|
48
|
-
confirmLeave(): Promise<boolean>;
|
|
49
|
-
/**
|
|
50
|
-
* Render new content in place. Resolves immediately (no-op) before ready.
|
|
51
|
-
*
|
|
52
|
-
* Safe to mix with a controlled `data` prop: the rendered document becomes the
|
|
53
|
-
* adapter's content baseline, so a later `data` change back to the previous
|
|
54
|
-
* document still re-renders.
|
|
55
|
-
* @param data - the document to render
|
|
56
|
-
*/
|
|
57
|
-
render(data: OutputData | LooseOutputData): Promise<void>;
|
|
58
|
-
/**
|
|
59
|
-
* Set or toggle read-only mode. Resolves to the resulting state; `false`
|
|
60
|
-
* (no-op) before ready.
|
|
61
|
-
* @param state - explicit state; omit to toggle
|
|
62
|
-
*/
|
|
63
|
-
setReadOnly(state?: boolean): Promise<boolean>;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Returns a stable, null-safe {@link BlokEditorHandle} for a `<BlokEditor>`.
|
|
68
|
-
*
|
|
69
|
-
* @example
|
|
70
|
-
* ```tsx
|
|
71
|
-
* const blok = useBlokHandle();
|
|
72
|
-
* return (
|
|
73
|
-
* <>
|
|
74
|
-
* <BlokEditor ref={blok.ref} tools={tools} data={data} />
|
|
75
|
-
* <button onClick={() => blok.focus()}>Focus</button>
|
|
76
|
-
* <button onClick={() => blok.save().then(onSubmit)}>Send</button>
|
|
77
|
-
* </>
|
|
78
|
-
* );
|
|
79
|
-
* ```
|
|
80
|
-
*/
|
|
81
|
-
export function useBlokHandle(): BlokEditorHandle {
|
|
82
|
-
const instanceRef = useRef<Blok | null>(null);
|
|
83
|
-
|
|
84
|
-
// Built once and kept stable: every method reads the live instance through
|
|
85
|
-
// `instanceRef`, so the handle identity (and its ref callback) never changes
|
|
86
|
-
// across renders even as the underlying editor is created or destroyed.
|
|
87
|
-
return useMemo<BlokEditorHandle>(() => ({
|
|
88
|
-
ref: (node): void => {
|
|
89
|
-
instanceRef.current = node;
|
|
90
|
-
},
|
|
91
|
-
get current(): Blok | null {
|
|
92
|
-
return instanceRef.current;
|
|
93
|
-
},
|
|
94
|
-
get isReady(): boolean {
|
|
95
|
-
return instanceRef.current !== null;
|
|
96
|
-
},
|
|
97
|
-
focus: (atEnd?: boolean): boolean => instanceRef.current?.focus(atEnd) ?? false,
|
|
98
|
-
// clear/render change the content OUT OF BAND of the controlled `data`
|
|
99
|
-
// effect, so they report the result to the adapter's content baseline (see
|
|
100
|
-
// content-baseline-map). Reported only once the call RESOLVES: a failed one
|
|
101
|
-
// leaves the editor on its previous content, and a baseline pointing at
|
|
102
|
-
// content the editor never showed would dedupe away the correcting update.
|
|
103
|
-
clear: (): Promise<void> => {
|
|
104
|
-
const instance = instanceRef.current;
|
|
105
|
-
|
|
106
|
-
if (instance === null) {
|
|
107
|
-
return Promise.resolve();
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
return instance.clear().then(() => {
|
|
111
|
-
getContentBaseline(instance)?.markRendered(EMPTY_OUTPUT_DATA);
|
|
112
|
-
});
|
|
113
|
-
},
|
|
114
|
-
save: (): Promise<OutputData | null> =>
|
|
115
|
-
instanceRef.current !== null ? instanceRef.current.save() : Promise.resolve(null),
|
|
116
|
-
confirmLeave: (): Promise<boolean> =>
|
|
117
|
-
instanceRef.current !== null ? instanceRef.current.confirmLeave() : Promise.resolve(true),
|
|
118
|
-
render: (data: OutputData | LooseOutputData): Promise<void> => {
|
|
119
|
-
const instance = instanceRef.current;
|
|
120
|
-
|
|
121
|
-
if (instance === null) {
|
|
122
|
-
return Promise.resolve();
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
return instance.render(data).then(() => {
|
|
126
|
-
getContentBaseline(instance)?.markRendered(data);
|
|
127
|
-
});
|
|
128
|
-
},
|
|
129
|
-
setReadOnly: (state?: boolean): Promise<boolean> => {
|
|
130
|
-
const instance = instanceRef.current;
|
|
131
|
-
|
|
132
|
-
if (instance === null) {
|
|
133
|
-
return Promise.resolve(false);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// Resolve the toggle here (omitted `state` flips current) so we can use the
|
|
137
|
-
// non-deprecated `set()`, which requires an explicit target state.
|
|
138
|
-
return instance.readOnly.set(state ?? !instance.readOnly.isEnabled);
|
|
139
|
-
},
|
|
140
|
-
}), []);
|
|
141
|
-
}
|
package/src/useBlokReady.ts
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import { useEffect, useState, type RefObject } from 'react';
|
|
2
|
-
import { Blok as BlokRuntime } from '@bloklabs/core';
|
|
3
|
-
|
|
4
|
-
/** Options accepted by {@link useBlokReady}. */
|
|
5
|
-
export interface UseBlokReadyOptions {
|
|
6
|
-
/**
|
|
7
|
-
* Restrict the wait to editors mounted inside this element. Accepts the ref
|
|
8
|
-
* you already hold on the container (read on every notification, so a ref
|
|
9
|
-
* that attaches after the first render is picked up).
|
|
10
|
-
*
|
|
11
|
-
* Omit it to observe every editor on the page. Passing it while it still
|
|
12
|
-
* resolves to null reports NOT ready — an unresolved scope must never fall
|
|
13
|
-
* back to the page-global one.
|
|
14
|
-
*/
|
|
15
|
-
within?: RefObject<Element | null> | Element | null;
|
|
16
|
-
/**
|
|
17
|
-
* `'ready'` (default) settles when each editor has finished booting.
|
|
18
|
-
* `'rendered'` also waits for its content to be in the DOM and re-arms on
|
|
19
|
-
* every post-boot re-render.
|
|
20
|
-
*/
|
|
21
|
-
settleOn?: 'ready' | 'rendered';
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Live readiness of the Blok editors in a DOM scope.
|
|
26
|
-
*
|
|
27
|
-
* Returns `false` until every editor inside `within` is settled, and follows
|
|
28
|
-
* the scope afterwards: editors mounted later re-close the gate, and with
|
|
29
|
-
* `settleOn: 'rendered'` so does each re-render. A scope holding no editors is
|
|
30
|
-
* ready, so callers need no "nothing to wait for" special case.
|
|
31
|
-
*
|
|
32
|
-
* The first render always reports `false` — the scope element is not attached
|
|
33
|
-
* yet, and over-waiting is safe while under-waiting is a bug.
|
|
34
|
-
* @param options - scope and readiness depth
|
|
35
|
-
*/
|
|
36
|
-
export function useBlokReady(options: UseBlokReadyOptions = {}): boolean {
|
|
37
|
-
const { within, settleOn } = options;
|
|
38
|
-
const [ready, setReady] = useState(false);
|
|
39
|
-
|
|
40
|
-
useEffect(() => {
|
|
41
|
-
const read = (): void => {
|
|
42
|
-
const scope = within instanceof Element ? within : (within?.current ?? null);
|
|
43
|
-
|
|
44
|
-
// A scope that was asked for but has not resolved yet is NOT the global
|
|
45
|
-
// scope — reporting ready here would gate on the wrong set of editors.
|
|
46
|
-
setReady(within !== undefined && scope === null
|
|
47
|
-
? false
|
|
48
|
-
: BlokRuntime.readyState({
|
|
49
|
-
within: scope,
|
|
50
|
-
settleOn,
|
|
51
|
-
}).ready);
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
read();
|
|
55
|
-
|
|
56
|
-
return BlokRuntime.subscribeReady(read);
|
|
57
|
-
}, [within, settleOn]);
|
|
58
|
-
|
|
59
|
-
return ready;
|
|
60
|
-
}
|
package/src/useBlokView.ts
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import { createElement, Fragment, useMemo } from 'react';
|
|
2
|
-
import type { ReactNode } from 'react';
|
|
3
|
-
import { blocksToViewNodes } from '@bloklabs/core/view';
|
|
4
|
-
import type { BlocksToHtmlOptions } from '@bloklabs/core/view';
|
|
5
|
-
import type { LooseOutputData, OutputData } from '@bloklabs/core';
|
|
6
|
-
|
|
7
|
-
import { viewNodesToReact } from './view-nodes-to-react';
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Render a saved Blok document to React elements with NO wrapper element —
|
|
11
|
-
* a Fragment of the block elements, for placement inside `<label>`s, table
|
|
12
|
-
* cells, and other slots where an extra `<div>` is invalid or unwanted.
|
|
13
|
-
*
|
|
14
|
-
* Synchronous and effect-free (SSR-safe); memoized on the data reference and
|
|
15
|
-
* the individual option values.
|
|
16
|
-
*
|
|
17
|
-
* The props-based signature is semver-stable. The raw `ViewNode` tree the
|
|
18
|
-
* bindings map from remains `@experimental` — but consumers of this hook never
|
|
19
|
-
* touch it directly.
|
|
20
|
-
* @param data - saved document (strict or loose wire shape; nullish tolerated)
|
|
21
|
-
* @param options - schema / renderers / inlineRenderers / unknown-block policy / toolAttributes / blockIds / transformUrl / pageHref / pageInfo
|
|
22
|
-
*/
|
|
23
|
-
export const useBlokView = (
|
|
24
|
-
data: OutputData | LooseOutputData | null | undefined,
|
|
25
|
-
options?: BlocksToHtmlOptions
|
|
26
|
-
): ReactNode => {
|
|
27
|
-
const schema = options?.schema;
|
|
28
|
-
const renderers = options?.renderers;
|
|
29
|
-
const onUnknownBlock = options?.onUnknownBlock;
|
|
30
|
-
const toolAttributes = options?.toolAttributes;
|
|
31
|
-
const blockIds = options?.blockIds;
|
|
32
|
-
const transformUrl = options?.transformUrl;
|
|
33
|
-
const inlineRenderers = options?.inlineRenderers;
|
|
34
|
-
const pageHref = options?.pageHref;
|
|
35
|
-
const pageInfo = options?.pageInfo;
|
|
36
|
-
const classes = options?.classes;
|
|
37
|
-
const direction = options?.direction;
|
|
38
|
-
|
|
39
|
-
return useMemo(() => {
|
|
40
|
-
return createElement(
|
|
41
|
-
Fragment,
|
|
42
|
-
null,
|
|
43
|
-
...viewNodesToReact(
|
|
44
|
-
blocksToViewNodes(data, {
|
|
45
|
-
schema,
|
|
46
|
-
renderers,
|
|
47
|
-
onUnknownBlock,
|
|
48
|
-
toolAttributes,
|
|
49
|
-
blockIds,
|
|
50
|
-
transformUrl,
|
|
51
|
-
inlineRenderers,
|
|
52
|
-
pageHref,
|
|
53
|
-
pageInfo,
|
|
54
|
-
/**
|
|
55
|
-
* Opt-in, NOT defaulted on. Parity rendering wraps every block in the
|
|
56
|
-
* core's holder → content scaffolding, which would contradict this
|
|
57
|
-
* hook's contract of emitting no wrapper element — the reason it
|
|
58
|
-
* exists is placement inside `<label>`s, table cells and other slots
|
|
59
|
-
* where extra elements are invalid or unwanted.
|
|
60
|
-
*
|
|
61
|
-
* `<BlokView>` passes `classes: true` for its own output, since it
|
|
62
|
-
* already owns a wrapper and its job IS to look like the editor.
|
|
63
|
-
*/
|
|
64
|
-
classes,
|
|
65
|
-
direction,
|
|
66
|
-
})
|
|
67
|
-
)
|
|
68
|
-
);
|
|
69
|
-
}, [data, schema, renderers, onUnknownBlock, toolAttributes, blockIds, transformUrl, inlineRenderers, pageHref, pageInfo, classes, direction]);
|
|
70
|
-
};
|
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ViewNode → React element mapping for the synchronous view bindings.
|
|
3
|
-
* Real elements via `createElement` recursion — never `dangerouslySetInnerHTML`.
|
|
4
|
-
*/
|
|
5
|
-
import { createElement } from 'react';
|
|
6
|
-
import type { CSSProperties, ReactNode } from 'react';
|
|
7
|
-
import type { ViewNode } from '@bloklabs/core/view';
|
|
8
|
-
|
|
9
|
-
/** HTML void elements — must never receive children. */
|
|
10
|
-
const VOID_ELEMENTS = new Set([
|
|
11
|
-
'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input',
|
|
12
|
-
'link', 'meta', 'param', 'source', 'track', 'wbr',
|
|
13
|
-
]);
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Presence-valued HTML attributes mapped to boolean React props. `checked`
|
|
17
|
-
* is handled separately (→ `defaultChecked`, so React does not treat the
|
|
18
|
-
* input as controlled).
|
|
19
|
-
*/
|
|
20
|
-
const BOOLEAN_ATTRIBUTES = new Set([
|
|
21
|
-
'disabled', 'open', 'controls', 'autoplay', 'loop', 'muted', 'required',
|
|
22
|
-
'multiple', 'playsinline', 'reversed', 'hidden', 'default', 'novalidate',
|
|
23
|
-
'allowfullscreen', 'itemscope',
|
|
24
|
-
]);
|
|
25
|
-
|
|
26
|
-
/** HTML attribute name → React prop name (beyond the identity mapping). */
|
|
27
|
-
const ATTRIBUTE_NAME_MAP: Record<string, string> = {
|
|
28
|
-
class: 'className',
|
|
29
|
-
for: 'htmlFor',
|
|
30
|
-
colspan: 'colSpan',
|
|
31
|
-
rowspan: 'rowSpan',
|
|
32
|
-
tabindex: 'tabIndex',
|
|
33
|
-
readonly: 'readOnly',
|
|
34
|
-
srcset: 'srcSet',
|
|
35
|
-
srclang: 'srcLang',
|
|
36
|
-
datetime: 'dateTime',
|
|
37
|
-
autoplay: 'autoPlay',
|
|
38
|
-
playsinline: 'playsInline',
|
|
39
|
-
novalidate: 'noValidate',
|
|
40
|
-
allowfullscreen: 'allowFullScreen',
|
|
41
|
-
crossorigin: 'crossOrigin',
|
|
42
|
-
referrerpolicy: 'referrerPolicy',
|
|
43
|
-
contenteditable: 'contentEditable',
|
|
44
|
-
spellcheck: 'spellCheck',
|
|
45
|
-
maxlength: 'maxLength',
|
|
46
|
-
minlength: 'minLength',
|
|
47
|
-
usemap: 'useMap',
|
|
48
|
-
itemscope: 'itemScope',
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* React names SVG attributes in camelCase (`stroke-width` → `strokeWidth`) and
|
|
53
|
-
* warns on the raw form. Only inside `<svg>`: elsewhere a hyphenated name (a
|
|
54
|
-
* custom element's attribute) must reach the DOM as written.
|
|
55
|
-
* @param name - SVG attribute name
|
|
56
|
-
*/
|
|
57
|
-
const svgPropName = (name: string): string => (
|
|
58
|
-
name.startsWith('aria-') || name.startsWith('data-')
|
|
59
|
-
? name
|
|
60
|
-
: name.replace(/-([a-z])/g, (_, letter: string) => letter.toUpperCase())
|
|
61
|
-
);
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Convert a sanitized `style` attribute string to a React style object
|
|
65
|
-
* (React only accepts object styles). Custom properties keep their raw name;
|
|
66
|
-
* everything else is camelCased.
|
|
67
|
-
* @param css - style attribute text
|
|
68
|
-
*/
|
|
69
|
-
const styleStringToObject = (css: string): CSSProperties => {
|
|
70
|
-
const style: Record<string, string> = {};
|
|
71
|
-
|
|
72
|
-
for (const declaration of css.split(';')) {
|
|
73
|
-
const separator = declaration.indexOf(':');
|
|
74
|
-
|
|
75
|
-
if (separator === -1) {
|
|
76
|
-
continue;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const property = declaration.slice(0, separator).trim();
|
|
80
|
-
const value = declaration.slice(separator + 1).trim();
|
|
81
|
-
|
|
82
|
-
if (property === '' || value === '') {
|
|
83
|
-
continue;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const key = property.startsWith('--')
|
|
87
|
-
? property
|
|
88
|
-
: property.toLowerCase().replace(/-([a-z])/g, (_, letter: string) => letter.toUpperCase());
|
|
89
|
-
|
|
90
|
-
style[key] = value;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
return style;
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Map one ViewNode attribute record to React props.
|
|
98
|
-
* @param attrs - sanitized attributes from the view tree
|
|
99
|
-
* @param key - sibling index used as the React key
|
|
100
|
-
* @param inSvg - whether the element sits in an `<svg>` subtree
|
|
101
|
-
*/
|
|
102
|
-
const attrsToProps = (attrs: Record<string, string>, key: number, inSvg: boolean): Record<string, unknown> => {
|
|
103
|
-
const props: Record<string, unknown> = { key };
|
|
104
|
-
|
|
105
|
-
for (const [name, value] of Object.entries(attrs)) {
|
|
106
|
-
if (name === 'style') {
|
|
107
|
-
props.style = styleStringToObject(value);
|
|
108
|
-
continue;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
if (name === 'checked') {
|
|
112
|
-
props.defaultChecked = true;
|
|
113
|
-
continue;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
if (name === 'value') {
|
|
117
|
-
props.defaultValue = value;
|
|
118
|
-
continue;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
if (BOOLEAN_ATTRIBUTES.has(name)) {
|
|
122
|
-
props[ATTRIBUTE_NAME_MAP[name] ?? name] = true;
|
|
123
|
-
continue;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
props[ATTRIBUTE_NAME_MAP[name] ?? (inSvg ? svgPropName(name) : name)] = value;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
return props;
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Map a list of ViewNodes to React nodes. Element keys come from the sibling
|
|
134
|
-
* index (the tree is static per render).
|
|
135
|
-
* @param nodes - view tree siblings
|
|
136
|
-
* @param inSvg - whether the siblings sit in an `<svg>` subtree
|
|
137
|
-
*/
|
|
138
|
-
export const viewNodesToReact = (nodes: ViewNode[], inSvg = false): ReactNode[] => {
|
|
139
|
-
return nodes.map((node, index): ReactNode => {
|
|
140
|
-
if ('text' in node) {
|
|
141
|
-
return node.text;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
const svg = inSvg || node.tag === 'svg';
|
|
145
|
-
const props = attrsToProps(node.attrs, index, svg);
|
|
146
|
-
|
|
147
|
-
if (VOID_ELEMENTS.has(node.tag) || node.children.length === 0) {
|
|
148
|
-
return createElement(node.tag, props);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
return createElement(node.tag, props, ...viewNodesToReact(node.children, svg));
|
|
152
|
-
});
|
|
153
|
-
};
|