@westopp/windo 0.1.1 → 0.1.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/dist/cli.cjs +1 -1
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1 -1
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +49 -10
- package/dist/index.d.ts +49 -10
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/cli/index.ts +1 -1
- package/src/client/App.tsx +46 -0
- package/src/client/Inspector.tsx +37 -0
- package/src/client/bridge.ts +21 -0
- package/src/client/chrome.css +72 -47
- package/src/client/internal-types.ts +12 -0
- package/src/index.ts +2 -0
- package/src/preview/ctx.ts +8 -1
- package/src/preview/index.ts +152 -10
- package/src/preview/render.tsx +5 -3
- package/src/protocol.ts +5 -0
- package/src/types.ts +33 -9
package/src/preview/ctx.ts
CHANGED
|
@@ -10,7 +10,10 @@ export function buildRenderContext<State>(
|
|
|
10
10
|
contexts: WindoContextMap,
|
|
11
11
|
postLog: (entry: WindoLogEntry) => void,
|
|
12
12
|
state: State,
|
|
13
|
-
setState: (patch: Partial<State>) => void
|
|
13
|
+
setState: (patch: Partial<State>) => void,
|
|
14
|
+
ctxState: Record<string, unknown>,
|
|
15
|
+
setCtxState: (patch: Record<string, unknown>) => void,
|
|
16
|
+
setColorScheme: (scheme: 'light' | 'dark') => void
|
|
14
17
|
): WindoRenderContext<State> {
|
|
15
18
|
const logger = {
|
|
16
19
|
log: (...args: unknown[]) => postLog({ ts: Date.now(), args }),
|
|
@@ -26,6 +29,10 @@ export function buildRenderContext<State>(
|
|
|
26
29
|
state,
|
|
27
30
|
setState,
|
|
28
31
|
contexts: {},
|
|
32
|
+
ctxState,
|
|
33
|
+
setCtxState,
|
|
34
|
+
setColorScheme,
|
|
35
|
+
toggleTheme: () => setColorScheme(env.colorScheme === 'light' ? 'dark' : 'light'),
|
|
29
36
|
}
|
|
30
37
|
|
|
31
38
|
for (const name of Object.keys(contexts)) {
|
package/src/preview/index.ts
CHANGED
|
@@ -10,7 +10,7 @@ import type { z } from 'zod'
|
|
|
10
10
|
import { describeSchema } from '../descriptor'
|
|
11
11
|
import type { WindoHostMessage, WindoPreviewMessage } from '../protocol'
|
|
12
12
|
import { isHostMessage, isWindoMessage, WINDO_MSG } from '../protocol'
|
|
13
|
-
import type { WindoActionMeta, WindoContextMap, WindoDefinition, WindoEnvState, WindoFactoryArg, WindoGroup, WindoLogEntry, WindoManifestEntry, WindoRenderContext, WindoVariantMeta } from '../types'
|
|
13
|
+
import type { WindoActionMeta, WindoContextMap, WindoDefinition, WindoEnvState, WindoFactoryArg, WindoGroup, WindoInitContext, WindoLogEntry, WindoManifestEntry, WindoPlacement, WindoPropDoc, WindoRenderContext, WindoVariant, WindoVariantMeta } from '../types'
|
|
14
14
|
import { buildRenderContext } from './ctx'
|
|
15
15
|
import { buildContextMeta, flattenZodError, idFromPath, sortEntries, summarizeLogArg, toCloneable } from './registry'
|
|
16
16
|
import { PreviewRoot } from './render'
|
|
@@ -46,7 +46,14 @@ let currentSchema: z.ZodType | undefined
|
|
|
46
46
|
let currentValues: unknown = {}
|
|
47
47
|
let env: WindoEnvState = DEFAULT_ENV
|
|
48
48
|
let currentState: Record<string, unknown> = {}
|
|
49
|
+
// Shared, cross-component state. Seeded once from the config and — unlike
|
|
50
|
+
// `currentState` — never reset on selection, so it survives switching windos.
|
|
51
|
+
let ctxState: Record<string, unknown> = { ...((config.ctxState as Record<string, unknown>) ?? {}) }
|
|
49
52
|
let root: Root | null = null
|
|
53
|
+
// Raised while a windo's full-ctx fields are being resolved (init state, defaultProps,
|
|
54
|
+
// placement). Resolution must be a pure read of the env/ctxState surface — so the
|
|
55
|
+
// mutating callbacks no-op while it's set, closing every re-entrant render path.
|
|
56
|
+
let resolving = false
|
|
50
57
|
|
|
51
58
|
function postToParent(msg: WindoPreviewMessage) {
|
|
52
59
|
window.parent.postMessage(msg, '*')
|
|
@@ -60,15 +67,57 @@ function postLog(entry: WindoLogEntry) {
|
|
|
60
67
|
|
|
61
68
|
/** The live ctx, rebuilt on demand so render-time, toolbar, and stage events all share the current state. */
|
|
62
69
|
function makeCtx(): WindoRenderContext {
|
|
63
|
-
return buildRenderContext(env, contexts, postLog, currentState, setState)
|
|
70
|
+
return buildRenderContext(env, contexts, postLog, currentState, setState, ctxState, setCtxState, setColorScheme)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* The init-scoped ctx handed to a `state` resolver. State is resolved exactly once,
|
|
75
|
+
* here, before any component-local state exists — so it exposes the env surface only
|
|
76
|
+
* with a no-op `setState`. The shared `setCtxState`/`setColorScheme` are still wired
|
|
77
|
+
* through but no-op while `resolving` is set, preventing an author from re-triggering
|
|
78
|
+
* resolution mid-init. Typed as `WindoInitContext` to drop the absent `state`/`setState`.
|
|
79
|
+
*/
|
|
80
|
+
function makeInitCtx(): WindoInitContext {
|
|
81
|
+
return buildRenderContext(env, contexts, postLog, {}, () => {}, ctxState, setCtxState, setColorScheme)
|
|
64
82
|
}
|
|
65
83
|
|
|
66
84
|
function setState(patch: Partial<Record<string, unknown>>) {
|
|
85
|
+
// No-op while resolving a windo's full-ctx fields — those are pure reads and must
|
|
86
|
+
// never drive a render, which would re-enter resolution and loop.
|
|
87
|
+
if (resolving) return
|
|
67
88
|
currentState = { ...currentState, ...patch }
|
|
68
89
|
render()
|
|
69
90
|
postState()
|
|
70
91
|
}
|
|
71
92
|
|
|
93
|
+
/** Merge a patch into the shared state, re-render, and echo it up to the chrome. */
|
|
94
|
+
function setCtxState(patch: Record<string, unknown>) {
|
|
95
|
+
if (resolving) return
|
|
96
|
+
ctxState = { ...ctxState, ...patch }
|
|
97
|
+
render()
|
|
98
|
+
postCtxState()
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Cloneable snapshot of the shared state (drops functions/JSX before postMessage). */
|
|
102
|
+
function cloneableCtxState(): Record<string, unknown> {
|
|
103
|
+
const out: Record<string, unknown> = {}
|
|
104
|
+
for (const key of Object.keys(ctxState)) out[key] = toCloneable(ctxState[key])
|
|
105
|
+
return out
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function postCtxState() {
|
|
109
|
+
postToParent({ source: WINDO_MSG, dir: 'preview', type: 'ctx-state', state: cloneableCtxState() })
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Set the canvas colour scheme from a component/action and echo it so the chrome's toggle stays in sync. */
|
|
113
|
+
function setColorScheme(scheme: 'light' | 'dark') {
|
|
114
|
+
if (resolving) return
|
|
115
|
+
if (env.colorScheme === scheme) return
|
|
116
|
+
env = { ...env, colorScheme: scheme }
|
|
117
|
+
render()
|
|
118
|
+
postToParent({ source: WINDO_MSG, dir: 'preview', type: 'color-scheme', colorScheme: scheme })
|
|
119
|
+
}
|
|
120
|
+
|
|
72
121
|
/** Post the current state snapshot + each action's live `disabled` flag up to the chrome. */
|
|
73
122
|
function postState() {
|
|
74
123
|
if (!currentId || !currentDef) return
|
|
@@ -113,11 +162,13 @@ function manifestEntry(id: string, def: WindoDefinition): WindoManifestEntry {
|
|
|
113
162
|
group: def.group,
|
|
114
163
|
tags: def.tags ?? [],
|
|
115
164
|
status: def.status ?? 'stable',
|
|
116
|
-
|
|
165
|
+
// The now-functionable fields can't be resolved here (no ctx at init): fall back
|
|
166
|
+
// to a safe static value, and assume the dynamic forms contribute a variant/state.
|
|
167
|
+
placement: typeof def.placement === 'function' ? 'center' : (def.placement ?? 'center'),
|
|
117
168
|
uses: def.uses ?? [],
|
|
118
|
-
hasVariants: (def.variants?.length ?? 0) > 0,
|
|
169
|
+
hasVariants: typeof def.variants === 'function' ? true : (def.variants?.length ?? 0) > 0,
|
|
119
170
|
actions,
|
|
120
|
-
hasState: !!def.state && Object.keys(def.state as object).length > 0,
|
|
171
|
+
hasState: typeof def.state === 'function' ? true : !!def.state && Object.keys(def.state as object).length > 0,
|
|
121
172
|
}
|
|
122
173
|
if (def.description !== undefined) entry.description = def.description
|
|
123
174
|
if (def.deprecation !== undefined) entry.deprecation = def.deprecation
|
|
@@ -132,10 +183,85 @@ function computeDefaults(schema: z.ZodType | undefined): unknown {
|
|
|
132
183
|
|
|
133
184
|
function safeCode(def: WindoDefinition, defaults: unknown): string | null {
|
|
134
185
|
if (!def.code) return null
|
|
186
|
+
// `code` is a pure string producer — resolve it under `resolving` so it can't drive a
|
|
187
|
+
// render and post an inconsistent describe payload by mutating state mid-resolution.
|
|
188
|
+
resolving = true
|
|
135
189
|
try {
|
|
136
|
-
return def.code(defaults as never)
|
|
190
|
+
return def.code(defaults as never, makeCtx())
|
|
137
191
|
} catch {
|
|
138
192
|
return null
|
|
193
|
+
} finally {
|
|
194
|
+
resolving = false
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** Resolve the authored Props table — a static array or a `ctx => array` function — under `resolving` so it stays a pure read. */
|
|
199
|
+
function safeProps(def: WindoDefinition): WindoPropDoc[] {
|
|
200
|
+
const p = def.props
|
|
201
|
+
if (typeof p !== 'function') return p ?? []
|
|
202
|
+
resolving = true
|
|
203
|
+
try {
|
|
204
|
+
return p(makeCtx()) ?? []
|
|
205
|
+
} catch {
|
|
206
|
+
return []
|
|
207
|
+
} finally {
|
|
208
|
+
resolving = false
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/** Resolve a windo's initial state — a static value or an init-ctx function — under `resolving`, degrading to `{}` if it throws. */
|
|
213
|
+
function safeState(def: WindoDefinition): Record<string, unknown> {
|
|
214
|
+
const state = def.state
|
|
215
|
+
if (typeof state !== 'function') return { ...((state as Record<string, unknown> | undefined) ?? {}) }
|
|
216
|
+
resolving = true
|
|
217
|
+
try {
|
|
218
|
+
return { ...((state as (c: WindoInitContext) => unknown)(makeInitCtx()) as Record<string, unknown>) }
|
|
219
|
+
} catch {
|
|
220
|
+
return {}
|
|
221
|
+
} finally {
|
|
222
|
+
resolving = false
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/** Resolve `defaultProps` — a static value or a `ctx => props` function — under `resolving` so it can't drive a render. */
|
|
227
|
+
function safeDefaultProps(def: WindoDefinition, ctx: WindoRenderContext): unknown {
|
|
228
|
+
const dp = def.defaultProps
|
|
229
|
+
if (typeof dp !== 'function') return dp
|
|
230
|
+
resolving = true
|
|
231
|
+
try {
|
|
232
|
+
return (dp as (c: WindoRenderContext) => unknown)(ctx)
|
|
233
|
+
} catch {
|
|
234
|
+
return {}
|
|
235
|
+
} finally {
|
|
236
|
+
resolving = false
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/** Resolve `placement` — a static value or a `ctx => placement` function — under `resolving`, degrading to `center` if it throws. */
|
|
241
|
+
function safePlacement(def: WindoDefinition, ctx: WindoRenderContext): WindoPlacement {
|
|
242
|
+
const p = def.placement
|
|
243
|
+
if (typeof p !== 'function') return p ?? 'center'
|
|
244
|
+
resolving = true
|
|
245
|
+
try {
|
|
246
|
+
return p(ctx)
|
|
247
|
+
} catch {
|
|
248
|
+
return 'center'
|
|
249
|
+
} finally {
|
|
250
|
+
resolving = false
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/** Resolve the gallery variants — a static array or a `ctx => array` function — under `resolving` so it stays a pure read. */
|
|
255
|
+
function safeVariants(def: WindoDefinition): WindoVariant<unknown>[] {
|
|
256
|
+
const v = def.variants
|
|
257
|
+
if (typeof v !== 'function') return v ?? []
|
|
258
|
+
resolving = true
|
|
259
|
+
try {
|
|
260
|
+
return v(makeCtx()) ?? []
|
|
261
|
+
} catch {
|
|
262
|
+
return []
|
|
263
|
+
} finally {
|
|
264
|
+
resolving = false
|
|
139
265
|
}
|
|
140
266
|
}
|
|
141
267
|
|
|
@@ -149,6 +275,7 @@ function postManifest() {
|
|
|
149
275
|
groups: [...config.groups],
|
|
150
276
|
tags: config.tags ? [...config.tags] : [],
|
|
151
277
|
contexts: buildContextMeta(contexts),
|
|
278
|
+
ctxState: cloneableCtxState(),
|
|
152
279
|
})
|
|
153
280
|
}
|
|
154
281
|
|
|
@@ -156,14 +283,14 @@ function postDescribe(id: string, def: WindoDefinition, schema: z.ZodType | unde
|
|
|
156
283
|
const defaults = computeDefaults(schema)
|
|
157
284
|
// variant patches and defaults may carry ReactNodes/functions that postMessage
|
|
158
285
|
// cannot clone — reduce them to JSON-safe forms before they cross the boundary.
|
|
159
|
-
const variants: WindoVariantMeta[] = (def
|
|
286
|
+
const variants: WindoVariantMeta[] = safeVariants(def).map(v => ({ label: v.label, props: toCloneable(v.props) as Record<string, unknown> }))
|
|
160
287
|
postToParent({
|
|
161
288
|
source: WINDO_MSG,
|
|
162
289
|
dir: 'preview',
|
|
163
290
|
type: 'describe',
|
|
164
291
|
id,
|
|
165
292
|
descriptor: describeSchema(schema),
|
|
166
|
-
props: def
|
|
293
|
+
props: safeProps(def),
|
|
167
294
|
variants,
|
|
168
295
|
defaults: toCloneable(defaults),
|
|
169
296
|
code: safeCode(def, defaults),
|
|
@@ -174,12 +301,18 @@ function render() {
|
|
|
174
301
|
if (!currentDef || !root) return
|
|
175
302
|
const ctx = makeCtx()
|
|
176
303
|
const id = currentId ?? ''
|
|
304
|
+
// Resolve the full-ctx fields here, OUTSIDE the React render cycle, so a function
|
|
305
|
+
// field that touches a mutating callback can't re-enter render() synchronously.
|
|
306
|
+
const defaultProps = safeDefaultProps(currentDef, ctx)
|
|
307
|
+
const placement = safePlacement(currentDef, ctx)
|
|
177
308
|
root.render(
|
|
178
309
|
createElement(PreviewRoot, {
|
|
179
310
|
def: currentDef,
|
|
180
311
|
ctx,
|
|
181
312
|
values: currentValues,
|
|
182
313
|
contexts,
|
|
314
|
+
defaultProps,
|
|
315
|
+
placement,
|
|
183
316
|
onStage: dispatchStage,
|
|
184
317
|
onError: (message: string, stack?: string) => {
|
|
185
318
|
postToParent({ source: WINDO_MSG, dir: 'preview', type: 'render-error', id, message, ...(stack === undefined ? {} : { stack }) })
|
|
@@ -195,8 +328,11 @@ function handleSelect(id: string) {
|
|
|
195
328
|
currentDef = match.def
|
|
196
329
|
currentSchema = match.def.configurableProps
|
|
197
330
|
currentValues = computeDefaults(currentSchema)
|
|
198
|
-
// Reset component-local state to the windo's declared initial state.
|
|
199
|
-
|
|
331
|
+
// Reset component-local state to the windo's declared initial state. Resolve it
|
|
332
|
+
// ONCE here — a `state` function runs against the init ctx (no live state/setState)
|
|
333
|
+
// under `resolving`, degrading to `{}` on throw. It is never re-resolved in
|
|
334
|
+
// render/makeCtx/set-env/set-ctx-state, so the author cannot drive a resolution loop.
|
|
335
|
+
currentState = safeState(currentDef)
|
|
200
336
|
render()
|
|
201
337
|
postState()
|
|
202
338
|
postDescribe(id, currentDef, currentSchema)
|
|
@@ -249,6 +385,12 @@ function handleMessage(msg: WindoHostMessage) {
|
|
|
249
385
|
env = msg.env
|
|
250
386
|
render()
|
|
251
387
|
break
|
|
388
|
+
case 'set-ctx-state':
|
|
389
|
+
// Chrome is the source of this update (an editor edit or a reload re-sync),
|
|
390
|
+
// so adopt it and re-render — but don't echo it back, to avoid a ping-pong.
|
|
391
|
+
ctxState = { ...msg.state }
|
|
392
|
+
render()
|
|
393
|
+
break
|
|
252
394
|
case 'invoke-action':
|
|
253
395
|
invokeAction(msg.actionId)
|
|
254
396
|
break
|
package/src/preview/render.tsx
CHANGED
|
@@ -45,15 +45,18 @@ export interface PreviewRootProps {
|
|
|
45
45
|
ctx: WindoRenderContext
|
|
46
46
|
values: unknown
|
|
47
47
|
contexts: WindoContextMap
|
|
48
|
+
/** `defaultProps` resolved outside the render cycle by `index.ts`, so a function field can't re-enter render here. */
|
|
49
|
+
defaultProps: unknown
|
|
50
|
+
/** `placement` resolved outside the render cycle by `index.ts`, for the same reason. */
|
|
51
|
+
placement: WindoPlacement
|
|
48
52
|
onError: (message: string, stack?: string) => void
|
|
49
53
|
/** Fires the windo's pointer-bound actions (`enter`/`exit`/`hover`) as the pointer crosses the stage. */
|
|
50
54
|
onStage: (phase: 'enter' | 'leave') => void
|
|
51
55
|
}
|
|
52
56
|
|
|
53
57
|
export function PreviewRoot(props: PreviewRootProps) {
|
|
54
|
-
const { def, ctx, values, contexts, onError, onStage } = props
|
|
58
|
+
const { def, ctx, values, contexts, defaultProps, placement, onError, onStage } = props
|
|
55
59
|
|
|
56
|
-
const defaultProps = typeof def.defaultProps === 'function' ? (def.defaultProps as (c: WindoRenderContext) => unknown)(ctx) : def.defaultProps
|
|
57
60
|
const finalProps = { ...(defaultProps as object), ...((values as object) ?? {}) }
|
|
58
61
|
|
|
59
62
|
let tree: ReactNode = def.component(finalProps, ctx)
|
|
@@ -77,7 +80,6 @@ export function PreviewRoot(props: PreviewRootProps) {
|
|
|
77
80
|
)
|
|
78
81
|
}
|
|
79
82
|
|
|
80
|
-
const placement: WindoPlacement = def.placement ?? 'center'
|
|
81
83
|
// Placements render flush; the `-padding` suffix opts into frame padding.
|
|
82
84
|
const padded = placement.endsWith('-padding')
|
|
83
85
|
const align = padded ? placement.slice(0, -'-padding'.length) : placement
|
package/src/protocol.ts
CHANGED
|
@@ -13,6 +13,7 @@ export type WindoHostMessage =
|
|
|
13
13
|
| { source: typeof WINDO_MSG; dir: 'host'; type: 'select'; id: string }
|
|
14
14
|
| { source: typeof WINDO_MSG; dir: 'host'; type: 'set-props'; id: string; json: string }
|
|
15
15
|
| { source: typeof WINDO_MSG; dir: 'host'; type: 'set-env'; env: WindoEnvState }
|
|
16
|
+
| { source: typeof WINDO_MSG; dir: 'host'; type: 'set-ctx-state'; state: Record<string, unknown> }
|
|
16
17
|
| { source: typeof WINDO_MSG; dir: 'host'; type: 'invoke-action'; id: string; actionId: string }
|
|
17
18
|
|
|
18
19
|
/** iframe -> chrome */
|
|
@@ -27,6 +28,8 @@ export type WindoPreviewMessage =
|
|
|
27
28
|
groups: WindoGroup[]
|
|
28
29
|
tags: string[]
|
|
29
30
|
contexts: WindoContextMeta[]
|
|
31
|
+
/** Initial shared state from the config — seeds the chrome's editable strip. */
|
|
32
|
+
ctxState: Record<string, unknown>
|
|
30
33
|
}
|
|
31
34
|
| {
|
|
32
35
|
source: typeof WINDO_MSG
|
|
@@ -43,6 +46,8 @@ export type WindoPreviewMessage =
|
|
|
43
46
|
| { source: typeof WINDO_MSG; dir: 'preview'; type: 'parse-error'; id: string; errors: WindoFieldError[] }
|
|
44
47
|
| { source: typeof WINDO_MSG; dir: 'preview'; type: 'log'; entry: WindoLogEntry }
|
|
45
48
|
| { source: typeof WINDO_MSG; dir: 'preview'; type: 'state'; id: string; state: Record<string, unknown>; actions: { id: string; disabled: boolean }[] }
|
|
49
|
+
| { source: typeof WINDO_MSG; dir: 'preview'; type: 'ctx-state'; state: Record<string, unknown> }
|
|
50
|
+
| { source: typeof WINDO_MSG; dir: 'preview'; type: 'color-scheme'; colorScheme: 'light' | 'dark' }
|
|
46
51
|
| { source: typeof WINDO_MSG; dir: 'preview'; type: 'render-error'; id: string; message: string; stack?: string }
|
|
47
52
|
|
|
48
53
|
export type WindoMessage = WindoHostMessage | WindoPreviewMessage
|
package/src/types.ts
CHANGED
|
@@ -131,6 +131,20 @@ export interface WindoRenderContext<State = unknown> {
|
|
|
131
131
|
setState: (patch: Partial<State>) => void
|
|
132
132
|
/** Resolved values of opted-in contexts, keyed by context name. */
|
|
133
133
|
contexts: Record<string, unknown>
|
|
134
|
+
/**
|
|
135
|
+
* Shared, cross-component state seeded from the config's `ctxState`. Unlike
|
|
136
|
+
* `state` (per-windo, reset on selection), this persists across selection and
|
|
137
|
+
* is global: any component can read it and write it via `setCtxState`. Use it
|
|
138
|
+
* to drive providers that wrap every component — e.g. a theme provider toggled
|
|
139
|
+
* from any component on the canvas.
|
|
140
|
+
*/
|
|
141
|
+
ctxState: Record<string, unknown>
|
|
142
|
+
/** Merge a patch into the shared `ctxState` and re-render every consumer. */
|
|
143
|
+
setCtxState: (patch: Record<string, unknown>) => void
|
|
144
|
+
/** Set the canvas colour scheme from a component or action. */
|
|
145
|
+
setColorScheme: (scheme: 'light' | 'dark') => void
|
|
146
|
+
/** Flip the canvas colour scheme between light and dark. */
|
|
147
|
+
toggleTheme: () => void
|
|
134
148
|
}
|
|
135
149
|
|
|
136
150
|
/* ------------------------------------------------------------------ *
|
|
@@ -151,7 +165,13 @@ export interface WindoPropDoc {
|
|
|
151
165
|
desc?: string
|
|
152
166
|
}
|
|
153
167
|
|
|
154
|
-
|
|
168
|
+
/** A value that may be authored statically or as a context-aware `ctx => value` function resolved at render-time. */
|
|
169
|
+
export type Ctxual<T, State = unknown> = T | ((ctx: WindoRenderContext<State>) => T)
|
|
170
|
+
|
|
171
|
+
/** The ctx surface available while resolving a windo's initial state — the render-time `state`/`setState` pair is excluded because it does not exist yet. */
|
|
172
|
+
export type WindoInitContext<State = unknown> = Omit<WindoRenderContext<State>, 'state' | 'setState'>
|
|
173
|
+
|
|
174
|
+
export type WindoDefaultProps<Props, State = unknown> = Ctxual<Props, State>
|
|
155
175
|
|
|
156
176
|
/**
|
|
157
177
|
* The object returned by a `windo(...)` factory.
|
|
@@ -169,9 +189,10 @@ export interface WindoDefinition<Props = unknown, State = unknown, GroupSlug ext
|
|
|
169
189
|
status?: WindoStatus
|
|
170
190
|
description?: string
|
|
171
191
|
deprecation?: string
|
|
172
|
-
placement
|
|
173
|
-
|
|
174
|
-
state
|
|
192
|
+
/** Where the component anchors in the canvas frame. Either a static placement or a function resolved with the live `ctx`. */
|
|
193
|
+
placement?: Ctxual<WindoPlacement, State>
|
|
194
|
+
/** Initial component-local state. Its shape is the `State` generic; `ctx.state`/`ctx.setState` derive from it. Either a static value or a function resolved with the init `ctx` (no `state`/`setState`) when the component is selected. */
|
|
195
|
+
state?: State | ((ctx: WindoInitContext<State>) => State)
|
|
175
196
|
/** Out-of-band actions that drive state: toolbar buttons (`click`) and stage pointer triggers (`enter`/`exit`/`hover`). */
|
|
176
197
|
actions?: WindoAction<State>[]
|
|
177
198
|
/** zod schema: validator + parser for the JSON-editable prop subset. `z.output ⊆ Props`. */
|
|
@@ -180,11 +201,12 @@ export interface WindoDefinition<Props = unknown, State = unknown, GroupSlug ext
|
|
|
180
201
|
defaultProps: WindoDefaultProps<Props, State>
|
|
181
202
|
/** Names of provider contexts this component opts into. */
|
|
182
203
|
uses?: string[]
|
|
183
|
-
variants
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
code
|
|
204
|
+
/** Gallery variants. Either a static array or a function resolved with the live `ctx` when the component is selected. */
|
|
205
|
+
variants?: Ctxual<WindoVariant<Props>[], State>
|
|
206
|
+
/** Authored documentation table (not derived from the schema). Either a static array or a function resolved with the live `ctx` when the component is selected. */
|
|
207
|
+
props?: Ctxual<WindoPropDoc[], State>
|
|
208
|
+
/** Optional authored code snippet for the Code tab, resolved with the JSON-editable values and the live `ctx`. */
|
|
209
|
+
code?: (values: Props, ctx: WindoRenderContext<State>) => string
|
|
188
210
|
/** A local provider wrapping just this windo (in addition to `uses`). */
|
|
189
211
|
providers?: ComponentType<{ children: ReactNode; ctx: WindoRenderContext<State> }>
|
|
190
212
|
component: (props: Props, ctx: WindoRenderContext<State>) => ReactNode
|
|
@@ -220,6 +242,8 @@ export interface WindoConfig<Groups extends readonly WindoGroup[] = readonly Win
|
|
|
220
242
|
contexts?: Contexts
|
|
221
243
|
/** The set of tags components may be assigned. A component's `tags` must be drawn from this list; the sidebar filters by them. */
|
|
222
244
|
tags?: Tags
|
|
245
|
+
/** Initial shared state exposed on `ctx.ctxState`. Global across every component and persisted across selection — write it from any component via `ctx.setCtxState`. */
|
|
246
|
+
ctxState?: Record<string, unknown>
|
|
223
247
|
/** Glob(s) for discovery, relative to project root. Default `**\/*.windo.tsx`. */
|
|
224
248
|
include?: string | string[]
|
|
225
249
|
/** Title shown in the workbench chrome. */
|