@akinon/pz-theme 2.0.71-beta.0 → 2.0.71

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.
@@ -0,0 +1,251 @@
1
+ import type { PropertyType } from './property-types';
2
+
3
+ export type { PropertyType };
4
+
5
+ /** Prefix every developer-authored type carries. Permanent once published. */
6
+ export const CUSTOM_TYPE_PREFIX = 'custom:';
7
+
8
+ /**
9
+ * `custom:<vendor>:<name>`. Both segments are lowercase and hyphen-separated.
10
+ *
11
+ * Two properties this buys, and both are load-bearing:
12
+ * - It can never collide with a built-in type. Neither closed enum contains a
13
+ * colon, so `custom:acme:hero` and a future built-in `hero` cannot meet.
14
+ * - It is greppable, and `isCustomThemeType` below is the one spelling of it.
15
+ * Most of the code that has to treat a developer-authored type differently
16
+ * lives in the editor and the generator rather than here, so this export is
17
+ * the shape those repos mirror: a mirror can be diffed against this grammar
18
+ * instead of being re-derived slightly wrong.
19
+ */
20
+ export const CUSTOM_TYPE_PATTERN =
21
+ /^custom:[a-z0-9][a-z0-9-]{0,47}:[a-z0-9][a-z0-9-]{0,47}$/;
22
+
23
+ export const isCustomThemeType = (type: unknown): type is string =>
24
+ typeof type === 'string' && CUSTOM_TYPE_PATTERN.test(type);
25
+
26
+ /** Vendor and block-name segments, validated separately for better errors. */
27
+ export const CUSTOM_SEGMENT_PATTERN = /^[a-z0-9][a-z0-9-]{0,47}$/;
28
+
29
+ /**
30
+ * A `ThemeEditable`/`useThemeSetting` key. Deliberately narrower than the type
31
+ * segments: it becomes an object key in `block.properties` AND is interpolated
32
+ * into a `data-part` value as `field-<key>`, so it excludes everything the part
33
+ * grammar reserves — `/` is the part-key delimiter and a leading `:` marks a
34
+ * root state.
35
+ */
36
+ export const SETTING_KEY_PATTERN = /^[a-z][a-zA-Z0-9]{0,31}$/;
37
+
38
+ /**
39
+ * A `data-part` value as `sanitizePartName` (utils/part-styles.ts) leaves it —
40
+ * that function keeps `[A-Za-z0-9_-]` and silently drops everything else.
41
+ *
42
+ * A declared part outside this grammar would be advertised in the catalog under
43
+ * one spelling and matched in CSS under another, so the control the editor drew
44
+ * for it would write styles no element on the page answers to. It also excludes
45
+ * both characters the style-key grammar reserves — `/` is the part-key
46
+ * delimiter and a leading `:` marks a root state — so a part name can never be
47
+ * mistaken for a key.
48
+ */
49
+ export const PART_NAME_PATTERN = /^[A-Za-z0-9_-]{1,64}$/;
50
+
51
+ /**
52
+ * The reserved block type one declared slot materializes as.
53
+ *
54
+ * A slot IS a child node rather than a name resolved against the block's one
55
+ * `block.blocks` array, and that is what makes several slots possible at all:
56
+ * insertion is already addressed as `(placeholderId, sectionId, parentBlockId,
57
+ * index)`, so a slot that is a node is filled by the machinery that exists —
58
+ * the slot node's id is simply the parent id. Three declared slots are three
59
+ * child nodes and three tree rows the merchant fills independently.
60
+ *
61
+ * Deliberately NOT under the `custom:<vendor>:<name>` grammar. That prefix
62
+ * means "a type this brand's build renders", and a slot node is rendered by
63
+ * nobody: `ThemeSlot` reaches past it to its children and the node itself puts
64
+ * no element on the page. One reserved type also means the editor recognises a
65
+ * slot without consulting a brand's catalog.
66
+ */
67
+ export const CUSTOM_SLOT_BLOCK_TYPE = 'custom-slot';
68
+
69
+ /**
70
+ * The property carrying a slot node's name.
71
+ *
72
+ * `block.properties` and not `block.label`, because the label is the one field
73
+ * a merchant can rename from the layer tree (`RENAME_BLOCK`) — a rename would
74
+ * detach the node from the `<ThemeSlot name="…">` that reads it, and nothing
75
+ * would say why the region went empty. Properties survive a save verbatim: the
76
+ * editor's `buildBlockSchemaForHierarchy` copies the whole object into the
77
+ * widget schema and `reconstructBlock` reads it straight back, key for key.
78
+ */
79
+ export const SLOT_NAME_PROPERTY = 'slotName';
80
+
81
+ /**
82
+ * A slot name. Narrower than a part name, wider than a setting key.
83
+ *
84
+ * It is never interpolated into a selector, so the part grammar's restrictions
85
+ * do not apply; what it must do is match LITERALLY between the component's
86
+ * `<ThemeSlot name="…">` and the node the editor created, across a JSON round
87
+ * trip. Excluding whitespace and case-collapsing surprises is the whole job.
88
+ */
89
+ export const SLOT_NAME_PATTERN = /^[a-z][a-zA-Z0-9-]{0,31}$/;
90
+
91
+ /**
92
+ * Keys the editor's generic property panel already treats specially. A custom
93
+ * block naming a field one of these would silently inherit behaviour it never
94
+ * asked for — `items` is rerouted to a bespoke slider editor, `background-image`
95
+ * is `url()`-wrapped, `font-family` becomes a Google Font picker, and `htmlId`
96
+ * and `tag` are pulled out of the Content tab entirely.
97
+ *
98
+ * This list is duplicated from the editor on purpose and is the WRONG kind of
99
+ * copy — it is the fourth mirror of a contract in this system. It exists so the
100
+ * refusal works today; the derivation script that makes it self-maintaining is
101
+ * its own ticket, and until that lands this list is a known drift risk.
102
+ */
103
+ export const RESERVED_SETTING_KEYS: readonly string[] = [
104
+ 'items',
105
+ 'backgroundImage',
106
+ 'background-image',
107
+ 'fontFamily',
108
+ 'font-family',
109
+ 'htmlId',
110
+ 'tag',
111
+ 'customCss',
112
+ 'publish-window',
113
+ 'typographyVariant',
114
+ 'role'
115
+ ];
116
+
117
+ /**
118
+ * Caps. Enforced at the sender; re-enforced on arrival in the editor, whose
119
+ * `CUSTOM_LIMITS` carries the same four numbers.
120
+ *
121
+ * Every one is a literal rather than derived from another, because each is
122
+ * mirrored INDEPENDENTLY on the far side: an expression here would let a change
123
+ * to one cap move a second one on the sender alone, and the receiver's answer
124
+ * to a catalog over any cap is to drop all of it.
125
+ */
126
+ export const CUSTOM_LIMITS = {
127
+ typesPerBuild: 40,
128
+ settingsPerType: 24,
129
+ slotsPerType: 8,
130
+ /**
131
+ * Every part the catalog advertises — the field-derived ones and the
132
+ * developer-declared ones together.
133
+ *
134
+ * A LITERAL, matching the editor's `CUSTOM_LIMITS.partsPerType`, and not
135
+ * `settingsPerType + slotsPerType` even though 32 is what that arithmetic
136
+ * happens to produce. That sum was the real bound only while `field-<key>`
137
+ * was the one way a part came into existence; a developer now declares parts
138
+ * directly, so the two numbers describe different things and only look
139
+ * related. Left derived, raising the settings cap would silently raise this
140
+ * one on the SENDER while the receiver's stayed at 32 — and the receiver
141
+ * drops the WHOLE catalog over the cap, so a build would lose every block in
142
+ * its palette because an unrelated limit moved.
143
+ */
144
+ partsPerType: 32,
145
+ catalogBytes: 256 * 1024
146
+ } as const;
147
+
148
+ /**
149
+ * The serializable half of a custom block — everything the editor needs and
150
+ * nothing it cannot receive. Functions are structurally impossible here: the
151
+ * editor has no `eval`/`new Function`, and `postMessage` throws on a function
152
+ * anyway, so `condition`/`customRender` can never cross.
153
+ */
154
+ export interface CustomSettingDescriptor {
155
+ key: string;
156
+ type: PropertyType;
157
+ label: string;
158
+ /** Always per-breakpoint. See `materializeDefault`. */
159
+ defaultValue?: unknown;
160
+ options?: Array<{ label: string; value: unknown }>;
161
+ min?: number;
162
+ max?: number;
163
+ step?: number;
164
+ unit?: string;
165
+ maxLength?: number;
166
+ helpText?: string;
167
+ group?: string;
168
+ showWhen?: { key: string; value: unknown };
169
+ /**
170
+ * Set when the setting came from a `ThemeEditable`, which makes it a content
171
+ * field rather than a behaviour knob. The editor uses it to offer the control
172
+ * on the `field-<key>` part as well as in the Content tab, so clicking the
173
+ * element on canvas lands on the right control.
174
+ */
175
+ editable?: boolean;
176
+ }
177
+
178
+ export interface CustomSlotDescriptor {
179
+ /**
180
+ * The name of the slot NODE this slot renders, matched literally. The empty
181
+ * string is the unnamed slot, which is what every block written against the
182
+ * single-slot API declares and what a legacy composition's children resolve
183
+ * to — see `resolveSlotChildren` in `slot-nodes.ts`.
184
+ */
185
+ name: string;
186
+ label?: string;
187
+ accepts?: string[];
188
+ max?: number;
189
+ }
190
+
191
+ export interface CustomBlockDescriptor {
192
+ type: string;
193
+ name: string;
194
+ label: string;
195
+ icon?: string;
196
+ group?: string;
197
+ hasChildren: boolean;
198
+ settings: CustomSettingDescriptor[];
199
+ slots: CustomSlotDescriptor[];
200
+ /**
201
+ * Every `data-part` the block advertises: the `field-<key>` parts derived
202
+ * from its `ThemeEditable` fields first, then the ones the developer declared
203
+ * through `defineCustomBlock({ parts })`.
204
+ *
205
+ * ONE FLAT LIST OF STRINGS, deliberately. The editor validates this field
206
+ * element by element and drops the whole catalog when an entry is not a
207
+ * string, so turning it into a list of objects to carry the states would make
208
+ * every block in the build vanish from the palette of any editor older than
209
+ * the change. The states ride alongside instead.
210
+ */
211
+ parts: string[];
212
+ /**
213
+ * Declared states per part, for the parts that declared any — `{ row:
214
+ * ['hover', 'open'] }`. Absent when no part declared a state, which is why it
215
+ * is optional rather than an empty object: an editor that has never heard of
216
+ * it ignores an unknown key, and a brand that declares no state sends nothing
217
+ * extra.
218
+ *
219
+ * Only states `buildPartSelectors` can actually match appear here
220
+ * (`PART_STYLE_STATES`); a state with no selector expression would produce a
221
+ * control writing CSS that can never apply.
222
+ */
223
+ partStates?: Record<string, string[]>;
224
+ }
225
+
226
+ export interface CustomCatalog {
227
+ vendor: string;
228
+ blocks: CustomBlockDescriptor[];
229
+ }
230
+
231
+ /**
232
+ * Per-breakpoint shape for every authored default.
233
+ *
234
+ * Not cosmetic. `handlePropertyChange` in the editor re-spreads an existing
235
+ * value only when it is ALREADY per-breakpoint; a bare scalar is replaced
236
+ * wholesale by `{tablet: v}` on the first non-desktop edit, after which
237
+ * `getResponsiveValue` finds no `desktop` key and falls back to the schema
238
+ * default — the merchant's desktop value is gone with no error. Materializing
239
+ * here means an authored default can never be that bare scalar.
240
+ *
241
+ * Values that are already objects (a per-breakpoint map the developer wrote
242
+ * themselves) and arrays (which are stored unbucketed) pass through untouched.
243
+ */
244
+ export const materializeDefault = (value: unknown): unknown => {
245
+ if (value === undefined) return undefined;
246
+ if (value === null) return value;
247
+ if (Array.isArray(value)) return value;
248
+ if (typeof value === 'object') return value;
249
+
250
+ return { desktop: value };
251
+ };
@@ -0,0 +1,225 @@
1
+ 'use client';
2
+
3
+ import { getResponsiveValue } from '../utils';
4
+ import { devWarn } from '../utils/dev-warn';
5
+ import { useCustomBlockContext } from './block-context';
6
+ import {
7
+ normalizeSettingChoices,
8
+ settingKeyError,
9
+ settingTypeError
10
+ } from './collect';
11
+ import type { ThemeSettingChoice } from './collect';
12
+ import type { PropertyType } from './property-types';
13
+ import { materializeDefault } from './types';
14
+ import type { CustomSettingDescriptor } from './types';
15
+
16
+ // Re-exported so a block file still reaches the option shorthand through the
17
+ // module it writes its settings against. The declaration itself moved to
18
+ // `collect.ts` with the normalizer that consumes it — see the note there.
19
+ export type { ThemeSettingChoice };
20
+
21
+ /**
22
+ * What each control type resolves to, so a call needs no type argument — the
23
+ * comparison in the documented example (`tone === 'dark'`) only typechecks
24
+ * because `select` maps to `string`.
25
+ *
26
+ * Total over `PropertyType`, including the three types that throw at runtime:
27
+ * the map is an index, not a permission.
28
+ */
29
+ interface SettingValueByType {
30
+ text: string;
31
+ select: string;
32
+ url: string;
33
+ color: string;
34
+ icon: string;
35
+ image: string;
36
+ file: string;
37
+ richtext: string;
38
+ toggle: boolean;
39
+ slider: number;
40
+ typography: Record<string, unknown>;
41
+ multiselect: string[];
42
+ localetext: Record<string, string>;
43
+ }
44
+
45
+ /**
46
+ * Stands in for TypeScript 5.4's `NoInfer`. Brands consume this package as
47
+ * source and compile it with their own TypeScript, several of them older.
48
+ *
49
+ * Without it, `default: 'light'` makes the return type the literal `'light'`
50
+ * and `tone === 'dark'` becomes a compile error about non-overlapping types —
51
+ * the API would reject the exact snippet it documents.
52
+ */
53
+ type NoInferred<T> = [T][T extends unknown ? 0 : never];
54
+
55
+ export interface ThemeSettingOptions<
56
+ TType extends PropertyType = PropertyType,
57
+ TValue = unknown
58
+ > {
59
+ type: TType;
60
+ label: string;
61
+ default?: NoInferred<TValue>;
62
+ options?: ReadonlyArray<ThemeSettingChoice>;
63
+ min?: number;
64
+ max?: number;
65
+ step?: number;
66
+ unit?: string;
67
+ maxLength?: number;
68
+ helpText?: string;
69
+ group?: string;
70
+ showWhen?: { key: string; value: unknown };
71
+ /**
72
+ * Marks the setting as a content field rather than a behaviour knob, so the
73
+ * editor also offers it on the `field-<key>` part. `ThemeEditable` sets it;
74
+ * a hand-written call has no on-screen element to attach a control to and
75
+ * should leave it alone.
76
+ */
77
+ editable?: boolean;
78
+ }
79
+
80
+ /**
81
+ * Throws rather than warns. A malformed key or an unreadable control type
82
+ * produces a panel control that shows its default forever and silently drops
83
+ * whatever the merchant types into it — a failure that surfaces weeks later as
84
+ * "the editor does not save". Production skips the check: by then the
85
+ * declaration is fixed at build time, and a storefront must not go down over a
86
+ * property panel.
87
+ *
88
+ * The rules come from `collect.ts` rather than being restated here. Both paths
89
+ * refuse the same key for the same reason, and a copy of the rule that drifted
90
+ * would be worse than no check at all — the hook would accept a key the
91
+ * collector then dropped, leaving a setting with no control and no error.
92
+ */
93
+ const assertDeclarationIsValid = (
94
+ key: string,
95
+ options: ThemeSettingOptions<PropertyType, unknown>
96
+ ): void => {
97
+ if (process.env.NODE_ENV === 'production') return;
98
+
99
+ const reason = settingKeyError(key) ?? settingTypeError(options.type);
100
+ if (!reason) return;
101
+
102
+ throw new Error(`[pz-theme] useThemeSetting: ${reason}.`);
103
+ };
104
+
105
+ const buildDescriptor = (
106
+ key: string,
107
+ options: ThemeSettingOptions<PropertyType, unknown>
108
+ ): CustomSettingDescriptor => {
109
+ const descriptor: CustomSettingDescriptor = {
110
+ key,
111
+ type: options.type,
112
+ label: options.label
113
+ };
114
+
115
+ // Per-breakpoint from the start: a bare scalar default is replaced wholesale
116
+ // by the editor on the first non-desktop edit, taking the merchant's desktop
117
+ // value with it. See the note on `materializeDefault`.
118
+ if (options.default !== undefined) {
119
+ descriptor.defaultValue = materializeDefault(options.default);
120
+ }
121
+
122
+ const choices = normalizeSettingChoices(options.options);
123
+ if (choices) descriptor.options = choices;
124
+
125
+ if (options.min !== undefined) descriptor.min = options.min;
126
+ if (options.max !== undefined) descriptor.max = options.max;
127
+ if (options.step !== undefined) descriptor.step = options.step;
128
+ if (options.unit !== undefined) descriptor.unit = options.unit;
129
+ if (options.maxLength !== undefined) descriptor.maxLength = options.maxLength;
130
+ if (options.helpText !== undefined) descriptor.helpText = options.helpText;
131
+ if (options.group !== undefined) descriptor.group = options.group;
132
+ if (options.showWhen !== undefined) descriptor.showWhen = options.showWhen;
133
+ if (options.editable) descriptor.editable = true;
134
+
135
+ return descriptor;
136
+ };
137
+
138
+ /**
139
+ * Separates "no default was authored" from "the authored default is an empty
140
+ * string". `getResponsiveValue` defaults its own third parameter to `''`, so
141
+ * passing `undefined` for the fallback would silently become `''`.
142
+ */
143
+ const NOT_AUTHORED = Symbol('pz-theme.notAuthored');
144
+
145
+ const resolveAuthoredDefault = (
146
+ value: unknown,
147
+ breakpoint: string
148
+ ): unknown => {
149
+ const resolved = getResponsiveValue(value, breakpoint, NOT_AUTHORED);
150
+
151
+ // An object default is a per-breakpoint map by the convention
152
+ // `materializeDefault` sets — except for `typography`, whose natural default
153
+ // is a style object with no `desktop` key. Resolving that would yield
154
+ // nothing and the control would read as unset, so a default that resolves to
155
+ // nothing is handed back whole rather than dropped.
156
+ return resolved === NOT_AUTHORED ? value : resolved;
157
+ };
158
+
159
+ /**
160
+ * Declares one merchant-editable value that has no on-screen representation —
161
+ * how many products a grid shows, which category it pulls from, whether a
162
+ * slider auto-advances, which side the image sits on.
163
+ *
164
+ * Without it, every variation becomes its own entry in the add menu: one
165
+ * component shipped four times as "Promo strip (left)", "Promo strip (right)"
166
+ * and so on. `ThemeEditable` covers the other half — the values a merchant
167
+ * edits by clicking the thing itself.
168
+ *
169
+ * A HOOK rather than a prop, and that is the load-bearing part. React requires
170
+ * hooks to be called unconditionally, so the set of settings a component
171
+ * declares is identical on every render — which is what makes the declaration
172
+ * statically knowable and lets the whole property panel be built from a single
173
+ * render pass. A prop bag or a runtime `declare()` call could sit behind an
174
+ * `if`, and the panel would gain and lose controls as the merchant edited.
175
+ *
176
+ * The declaration IS the registration: there is no second schema file to keep
177
+ * in step, so a setting cannot drift from its use.
178
+ */
179
+ export function useThemeSetting<
180
+ TType extends PropertyType,
181
+ TValue = SettingValueByType[TType]
182
+ >(key: string, options: ThemeSettingOptions<TType, TValue>): TValue {
183
+ const context = useCustomBlockContext();
184
+ const declaration = options as ThemeSettingOptions<PropertyType, unknown>;
185
+
186
+ assertDeclarationIsValid(key, declaration);
187
+
188
+ if (!context) {
189
+ // Never throws: a developer previewing the component in a plain React tree,
190
+ // a storybook or a unit test should see the declared defaults, not a crash.
191
+ // The key rides in the message so distinct misplacements dedupe
192
+ // independently inside `devWarn`.
193
+ devWarn(
194
+ 'useThemeSetting',
195
+ `"${key}" was read outside a custom block, so the merchant's value is ` +
196
+ `unreachable and the declared default is used instead. Render this ` +
197
+ `component through a block registered with registerCustomBlocks().`
198
+ );
199
+ return declaration.default as TValue;
200
+ }
201
+
202
+ // Reported on every render rather than once from an effect: the collection
203
+ // pass renders the component and reads the sink synchronously, and on the
204
+ // server effects never run at all.
205
+ context.collect?.setting(buildDescriptor(key, declaration));
206
+
207
+ const breakpoint = context.currentBreakpoint;
208
+
209
+ // `block.properties[key]`, never `block.value` — `value` is one slot per
210
+ // block, so the second setting would overwrite the first, and the backend
211
+ // drops attribute keys that were never declared.
212
+ const stored = context.block.properties?.[key];
213
+
214
+ // The sentinel again, for the same reason: handing `getResponsiveValue` an
215
+ // undefined fallback would resolve an undeclared default to `''`, so a
216
+ // `toggle` with no default would come back as an empty string rather than
217
+ // undefined.
218
+ const resolved = getResponsiveValue(stored, breakpoint, NOT_AUTHORED);
219
+
220
+ return (
221
+ resolved === NOT_AUTHORED
222
+ ? resolveAuthoredDefault(declaration.default, breakpoint)
223
+ : resolved
224
+ ) as TValue;
225
+ }
package/src/index.ts CHANGED
@@ -138,3 +138,69 @@ export { CheckoutPiecesProvider } from './chrome/checkout-pieces-provider';
138
138
  export { CheckoutPiece } from './chrome/checkout-piece';
139
139
  export { CHECKOUT_PIECE_KEYS } from './chrome/checkout-pieces';
140
140
  export type { CheckoutPieceKey } from './chrome/checkout-pieces';
141
+
142
+ /**
143
+ * The custom block authoring API, named export by named export.
144
+ *
145
+ * Curated where the rest of this file is not, and where deep imports
146
+ * (`@akinon/pz-theme/src/custom`) are the de-facto norm everywhere else,
147
+ * because this is the only surface here consumed by code in ANOTHER REPO. A
148
+ * symbol that leaks out of `src/custom` becomes a compile dependency in a
149
+ * brand's block file the moment someone imports it, and removing it back out
150
+ * is then a breaking change in a repo this team does not own. Listing each
151
+ * export makes widening that surface a deliberate edit rather than the side
152
+ * effect of adding a file to the folder — which is exactly what `export *`
153
+ * would make it.
154
+ *
155
+ * `partAttrs` is deliberately absent: `export * from './utils'` above already
156
+ * publishes it at the root. The custom barrel re-exports it so a block file
157
+ * needs one import; adding it here too would be a second spelling of the same
158
+ * symbol from the same entry point.
159
+ *
160
+ * THIS ENTRY POINT IS SERVER-ONLY, which is why `registerCustomBlocks` cannot
161
+ * actually be used through it. `ThemePlaceholder` two dozen lines up imports
162
+ * `server-only`, and that poisons the whole module for a client graph — a
163
+ * brand's client root importing `registerCustomBlocks` from here gets "cannot
164
+ * be imported from a Client Component module" at build time. Registration is
165
+ * client-only (`custom/index.ts` explains why), so the barrel a brand writes
166
+ * imports from `@akinon/pz-theme/src/custom` instead. What these exports buy is
167
+ * discovery and the types, not a registration path.
168
+ */
169
+ // The slot-node grammar (`CUSTOM_SLOT_BLOCK_TYPE`, `SLOT_NAME_PROPERTY`) and the
170
+ // one spelling of the check (`isSlotNode`) are named here for the repos that
171
+ // mirror the grammar rather than re-deriving it.
172
+ export {
173
+ defineCustomBlock,
174
+ registerCustomBlocks,
175
+ ThemeEditable,
176
+ ThemeSlot,
177
+ useThemeSetting,
178
+ getCustomCatalog,
179
+ isCustomThemeType,
180
+ CUSTOM_TYPE_PREFIX,
181
+ CUSTOM_SLOT_BLOCK_TYPE,
182
+ SLOT_NAME_PROPERTY,
183
+ isSlotNode,
184
+ slotNodeName
185
+ } from './custom';
186
+ export type {
187
+ CustomBlockComponent,
188
+ CustomBlockDefinition,
189
+ CustomBlockDescriptor,
190
+ CustomBlockInput,
191
+ CustomBlockOptions,
192
+ CustomCatalog,
193
+ CustomPartDeclaration,
194
+ CustomPartsDeclaration,
195
+ CustomSettingDeclaration,
196
+ CustomSettingDescriptor,
197
+ CustomSettingOption,
198
+ CustomSlotDeclaration,
199
+ CustomSlotDescriptor,
200
+ EditableKind,
201
+ PropertyType,
202
+ ThemeEditableProps,
203
+ ThemeSettingChoice,
204
+ ThemeSettingOptions,
205
+ ThemeSlotProps
206
+ } from './custom';