@playgenx/components 0.2.0 → 0.2.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/index.d.mts CHANGED
@@ -281,11 +281,18 @@ declare function usePlaygroundState(): PlaygroundState;
281
281
  *
282
282
  * Implementation note: this hook ALWAYS calls the same number of
283
283
  * React hooks regardless of whether `key` is set or a Provider is in
284
- * scope. The "no binding" / "no Provider" branch uses dummy local
285
- * state so the hook count stays aligned across renders. That means a
284
+ * scope. The "no binding" / "no Provider" branch returns a static
285
+ * tuple so the hook count stays aligned across renders. That means a
286
286
  * component can flip `stateKey` from defined to undefined (or move in
287
287
  * or out of a Provider) without crashing with a hooks-order error.
288
288
  *
289
+ * The hook uses React 19's `useSyncExternalStore` so SSR renders the
290
+ * Provider's current value synchronously — no "value=fallback on
291
+ * server, value=real after hydration" surprise. The hook count drops
292
+ * from 5 (useContext + useState + useState + useEffect + useCallback)
293
+ * to 3 (useContext + useSyncExternalStore + useCallback) as a side
294
+ * benefit.
295
+ *
289
296
  * Components that accept a `stateKey` prop should call this internally
290
297
  * and use the returned tuple as a drop-in for the underlying
291
298
  * value/onChange pair. The return type widens to `readonly [...] | undefined`
@@ -298,6 +305,98 @@ declare function usePlaygroundState(): PlaygroundState;
298
305
  */
299
306
  declare function useBoundValueOrUndefined<T>(key: string | undefined, fallback: T): readonly [T, (next: T) => void] | undefined;
300
307
  declare function useBoundValue<T>(key: string, fallback: T): readonly [T, (next: T) => void];
308
+ /**
309
+ * Non-subscribing accessor for the store. Returns a stable tuple of
310
+ * imperative API methods (`get`, `set`, `subscribe`, `snapshot`,
311
+ * `replaceAll`) plus the raw `state` ref — none of which trigger a
312
+ * re-render when used.
313
+ *
314
+ * When to use this instead of `useBoundValue`:
315
+ *
316
+ * - You need to read state inside an event handler (no render needed).
317
+ * - You need to set state from many different handlers, not one bound prop.
318
+ * - You're implementing a custom hook that wraps the store.
319
+ * - You want to subscribe imperatively (for analytics, side-effects).
320
+ *
321
+ * When NOT to use this: if you need the component to RE-RENDER on
322
+ * state changes, use `useBoundValue` (which subscribes for you).
323
+ *
324
+ * @example
325
+ * function LogoutButton() {
326
+ * const { get, set } = useStoreSnapshot();
327
+ * return (
328
+ * <button onClick={() => {
329
+ * if (get<boolean>('confirmLogout')) set('confirmLogout', false);
330
+ * doLogout();
331
+ * }}>Log out</button>
332
+ * );
333
+ * }
334
+ */
335
+ declare function useStoreSnapshot(): {
336
+ readonly state: PlaygroundState;
337
+ readonly get: <T = unknown>(key: string) => T | undefined;
338
+ readonly set: <T = unknown>(key: string, value: T) => void;
339
+ readonly subscribe: (key: string, cb: (value: unknown) => void) => () => void;
340
+ readonly snapshot: () => Readonly<Record<string, unknown>>;
341
+ readonly replaceAll: (values: Record<string, unknown>) => void;
342
+ };
343
+ /**
344
+ * Bind a state key with a selector transform. Returns the SELECTED
345
+ * value, not the raw value. The component only re-renders when the
346
+ * selected slice changes — not when OTHER fields on the same key
347
+ * change.
348
+ *
349
+ * Useful for normalized state where one key holds an object and you
350
+ * only care about one property:
351
+ *
352
+ * @example
353
+ * interface User { name: string; age: number; email: string; }
354
+ * const userName = useBoundSelector<User, string>(
355
+ * 'currentUser',
356
+ * (u) => u?.name ?? '(anonymous)',
357
+ * 'guest'
358
+ * );
359
+ * // Setting `currentUser.age` does NOT re-render this component.
360
+ * // Setting `currentUser.name` DOES re-render this component.
361
+ *
362
+ * The setter writes the SELECTED value as-is to the store. For
363
+ * partial-merge semantics (e.g., "set only the name, keep other
364
+ * fields"), use `useStoreSnapshot()` directly and merge yourself.
365
+ *
366
+ * If the selector throws, the error propagates and the component
367
+ * unmounts via the nearest error boundary. Wrap your selector in
368
+ * a try/catch if you need resilience.
369
+ */
370
+ declare function useBoundSelector<T, U>(key: string, selector: (raw: T | undefined) => U, fallback: U): readonly [U, (next: U) => void];
371
+ /**
372
+ * Bind multiple keys at once. Returns a `Record<key, [value, setter]>`
373
+ * for the keys you specify. Each setter writes back to its own key.
374
+ *
375
+ * Useful for forms and dashboards where multiple bound inputs share
376
+ * a single Provider.
377
+ *
378
+ * @example
379
+ * const form = useMultiBoundValue({
380
+ * name: { fallback: '' },
381
+ * age: { fallback: 0 },
382
+ * email: { fallback: '' },
383
+ * });
384
+ * // form.name[0] === current name string
385
+ * // form.age[1](42) writes 42 to state.age
386
+ *
387
+ * The hook subscribes ONCE per key (so 5 keys = 5 subscriptions,
388
+ * not a single batched subscription). If you bind a LOT of keys,
389
+ * consider using `useStoreSnapshot()` instead — it doesn't subscribe.
390
+ *
391
+ * @example
392
+ * // 50-row table, each row needs its own value
393
+ * const { get, set } = useStoreSnapshot();
394
+ * rows.map(row => <input value={get(`cell-${row.id}`) ?? ''}
395
+ * onChange={e => set(`cell-${row.id}`, e.target.value)} />);
396
+ */
397
+ declare function useMultiBoundValue<K extends string>(bindings: Record<K, {
398
+ fallback: unknown;
399
+ }>): { readonly [P in K]: readonly [unknown, (next: unknown) => void]; };
301
400
  /**
302
401
  * State-binding action shape. Used by `<Button onClickAction={...}>`.
303
402
  * { set: { count: 1 } } -> sets state.count = 1
@@ -506,5 +605,5 @@ declare const componentMap: ComponentMap;
506
605
  */
507
606
  declare function createRegistry(overrides: Partial<ComponentMap>): ComponentMap;
508
607
  //#endregion
509
- export { ArtifactErrorBoundary, type ArtifactErrorBoundaryProps, Button, type ButtonProps, Card, type CardProps, Chart, type ChartKind, type ChartProps, Code, type CodeProps, type ComponentMap, Container, type ContainerProps, Heading, type HeadingProps, List, type ListProps, type PlaygroundState, PlaygroundStateProvider, type PlaygroundStateProviderProps, ShowSource, type ShowSourceProps, Slider, type SliderProps, type StateAction, type StateDiff, type StateEnvelope, type Step, Stepper, type StepperProps, Text, TextField, type TextFieldProps, type TextProps, batch, clearState, componentMap, createPlaygroundState, createRegistry, diffSnapshots, diffState, dumpState, useBoundValue, useBoundValueOrUndefined, usePlaygroundState, useStateAction, validateStateKey };
608
+ export { ArtifactErrorBoundary, type ArtifactErrorBoundaryProps, Button, type ButtonProps, Card, type CardProps, Chart, type ChartKind, type ChartProps, Code, type CodeProps, type ComponentMap, Container, type ContainerProps, Heading, type HeadingProps, List, type ListProps, type PlaygroundState, PlaygroundStateProvider, type PlaygroundStateProviderProps, ShowSource, type ShowSourceProps, Slider, type SliderProps, type StateAction, type StateDiff, type StateEnvelope, type Step, Stepper, type StepperProps, Text, TextField, type TextFieldProps, type TextProps, batch, clearState, componentMap, createPlaygroundState, createRegistry, diffSnapshots, diffState, dumpState, useBoundSelector, useBoundValue, useBoundValueOrUndefined, useMultiBoundValue, usePlaygroundState, useStateAction, useStoreSnapshot, validateStateKey };
510
609
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/Button.tsx","../src/TextField.tsx","../src/Slider.tsx","../src/Chart.tsx","../src/Container.tsx","../src/Code.tsx","../src/Heading.tsx","../src/Text.tsx","../src/Stepper.tsx","../src/Card.tsx","../src/List.tsx","../src/state.tsx","../src/stateHelpers.ts","../src/ArtifactErrorBoundary.tsx","../src/ShowSource.tsx","../src/createRegistry.ts"],"mappings":"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"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/Button.tsx","../src/TextField.tsx","../src/Slider.tsx","../src/Chart.tsx","../src/Container.tsx","../src/Code.tsx","../src/Heading.tsx","../src/Text.tsx","../src/Stepper.tsx","../src/Card.tsx","../src/List.tsx","../src/state.tsx","../src/stateHelpers.ts","../src/ArtifactErrorBoundary.tsx","../src/ShowSource.tsx","../src/createRegistry.ts"],"mappings":"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"}
package/dist/index.mjs CHANGED
@@ -308,11 +308,18 @@ function usePlaygroundState() {
308
308
  *
309
309
  * Implementation note: this hook ALWAYS calls the same number of
310
310
  * React hooks regardless of whether `key` is set or a Provider is in
311
- * scope. The "no binding" / "no Provider" branch uses dummy local
312
- * state so the hook count stays aligned across renders. That means a
311
+ * scope. The "no binding" / "no Provider" branch returns a static
312
+ * tuple so the hook count stays aligned across renders. That means a
313
313
  * component can flip `stateKey` from defined to undefined (or move in
314
314
  * or out of a Provider) without crashing with a hooks-order error.
315
315
  *
316
+ * The hook uses React 19's `useSyncExternalStore` so SSR renders the
317
+ * Provider's current value synchronously — no "value=fallback on
318
+ * server, value=real after hydration" surprise. The hook count drops
319
+ * from 5 (useContext + useState + useState + useEffect + useCallback)
320
+ * to 3 (useContext + useSyncExternalStore + useCallback) as a side
321
+ * benefit.
322
+ *
316
323
  * Components that accept a `stateKey` prop should call this internally
317
324
  * and use the returned tuple as a drop-in for the underlying
318
325
  * value/onChange pair. The return type widens to `readonly [...] | undefined`
@@ -326,21 +333,30 @@ function usePlaygroundState() {
326
333
  function useBoundValueOrUndefined(key, fallback) {
327
334
  const ctx = React.useContext(PlaygroundStateContext);
328
335
  const bound = key !== void 0 && ctx !== null;
329
- const [value, setValue] = React.useState(fallback);
330
- const [, force] = React.useState(0);
331
- React.useEffect(() => {
332
- if (!bound || key === void 0 || ctx === null) return;
333
- const initial = ctx.state.get(key) ?? fallback;
334
- setValue(initial);
335
- return ctx.state.subscribe(key, (next) => {
336
- setValue(next);
337
- force((n) => n + 1);
338
- });
336
+ const value = React.useSyncExternalStore(React.useCallback((onStoreChange) => {
337
+ if (!bound || key === void 0 || ctx === null) return () => {};
338
+ return ctx.state.subscribe(key, onStoreChange);
339
339
  }, [
340
340
  bound,
341
341
  ctx,
342
342
  key
343
- ]);
343
+ ]), React.useCallback(() => {
344
+ if (!bound || key === void 0 || ctx === null) return fallback;
345
+ return ctx.state.get(key) ?? fallback;
346
+ }, [
347
+ bound,
348
+ ctx,
349
+ key,
350
+ fallback
351
+ ]), React.useCallback(() => {
352
+ if (!bound || key === void 0 || ctx === null) return fallback;
353
+ return ctx.state.get(key) ?? fallback;
354
+ }, [
355
+ bound,
356
+ ctx,
357
+ key,
358
+ fallback
359
+ ]));
344
360
  const set = React.useCallback((next) => {
345
361
  if (!bound || key === void 0 || ctx === null) return;
346
362
  ctx.state.set(key, next);
@@ -358,6 +374,124 @@ function useBoundValue(key, fallback) {
358
374
  return bound;
359
375
  }
360
376
  /**
377
+ * Non-subscribing accessor for the store. Returns a stable tuple of
378
+ * imperative API methods (`get`, `set`, `subscribe`, `snapshot`,
379
+ * `replaceAll`) plus the raw `state` ref — none of which trigger a
380
+ * re-render when used.
381
+ *
382
+ * When to use this instead of `useBoundValue`:
383
+ *
384
+ * - You need to read state inside an event handler (no render needed).
385
+ * - You need to set state from many different handlers, not one bound prop.
386
+ * - You're implementing a custom hook that wraps the store.
387
+ * - You want to subscribe imperatively (for analytics, side-effects).
388
+ *
389
+ * When NOT to use this: if you need the component to RE-RENDER on
390
+ * state changes, use `useBoundValue` (which subscribes for you).
391
+ *
392
+ * @example
393
+ * function LogoutButton() {
394
+ * const { get, set } = useStoreSnapshot();
395
+ * return (
396
+ * <button onClick={() => {
397
+ * if (get<boolean>('confirmLogout')) set('confirmLogout', false);
398
+ * doLogout();
399
+ * }}>Log out</button>
400
+ * );
401
+ * }
402
+ */
403
+ function useStoreSnapshot() {
404
+ const ctx = React.useContext(PlaygroundStateContext);
405
+ if (ctx === null) throw new Error("useStoreSnapshot() must be called inside a <PlaygroundStateProvider>.");
406
+ return {
407
+ state: ctx.state,
408
+ get: ctx.state.get,
409
+ set: ctx.state.set,
410
+ subscribe: ctx.state.subscribe,
411
+ snapshot: ctx.state.snapshot,
412
+ replaceAll: ctx.state.replaceAll
413
+ };
414
+ }
415
+ /**
416
+ * Bind a state key with a selector transform. Returns the SELECTED
417
+ * value, not the raw value. The component only re-renders when the
418
+ * selected slice changes — not when OTHER fields on the same key
419
+ * change.
420
+ *
421
+ * Useful for normalized state where one key holds an object and you
422
+ * only care about one property:
423
+ *
424
+ * @example
425
+ * interface User { name: string; age: number; email: string; }
426
+ * const userName = useBoundSelector<User, string>(
427
+ * 'currentUser',
428
+ * (u) => u?.name ?? '(anonymous)',
429
+ * 'guest'
430
+ * );
431
+ * // Setting `currentUser.age` does NOT re-render this component.
432
+ * // Setting `currentUser.name` DOES re-render this component.
433
+ *
434
+ * The setter writes the SELECTED value as-is to the store. For
435
+ * partial-merge semantics (e.g., "set only the name, keep other
436
+ * fields"), use `useStoreSnapshot()` directly and merge yourself.
437
+ *
438
+ * If the selector throws, the error propagates and the component
439
+ * unmounts via the nearest error boundary. Wrap your selector in
440
+ * a try/catch if you need resilience.
441
+ */
442
+ function useBoundSelector(key, selector, fallback) {
443
+ const bound = useBoundValueOrUndefined(key, fallback);
444
+ const raw = bound === void 0 ? void 0 : bound[0];
445
+ const value = React.useMemo(() => selector(raw), [
446
+ raw,
447
+ selector,
448
+ fallback
449
+ ]);
450
+ const set = React.useCallback((next) => {
451
+ if (bound === void 0) return;
452
+ bound[1](next);
453
+ }, [bound]);
454
+ if (bound === void 0) throw new Error("useBoundSelector requires a non-undefined `key` and a <PlaygroundStateProvider> ancestor.");
455
+ return [value, set];
456
+ }
457
+ /**
458
+ * Bind multiple keys at once. Returns a `Record<key, [value, setter]>`
459
+ * for the keys you specify. Each setter writes back to its own key.
460
+ *
461
+ * Useful for forms and dashboards where multiple bound inputs share
462
+ * a single Provider.
463
+ *
464
+ * @example
465
+ * const form = useMultiBoundValue({
466
+ * name: { fallback: '' },
467
+ * age: { fallback: 0 },
468
+ * email: { fallback: '' },
469
+ * });
470
+ * // form.name[0] === current name string
471
+ * // form.age[1](42) writes 42 to state.age
472
+ *
473
+ * The hook subscribes ONCE per key (so 5 keys = 5 subscriptions,
474
+ * not a single batched subscription). If you bind a LOT of keys,
475
+ * consider using `useStoreSnapshot()` instead — it doesn't subscribe.
476
+ *
477
+ * @example
478
+ * // 50-row table, each row needs its own value
479
+ * const { get, set } = useStoreSnapshot();
480
+ * rows.map(row => <input value={get(`cell-${row.id}`) ?? ''}
481
+ * onChange={e => set(`cell-${row.id}`, e.target.value)} />);
482
+ */
483
+ function useMultiBoundValue(bindings) {
484
+ if (React.useContext(PlaygroundStateContext) === null) throw new Error("useMultiBoundValue() must be called inside a <PlaygroundStateProvider>.");
485
+ const result = {};
486
+ const keys = Object.keys(bindings);
487
+ for (const k of keys) {
488
+ const b = useBoundValueOrUndefined(k, bindings[k].fallback);
489
+ if (b === void 0) result[k] = [bindings[k].fallback, () => {}];
490
+ else result[k] = b;
491
+ }
492
+ return result;
493
+ }
494
+ /**
361
495
  * Apply a StateAction to the nearest PlaygroundState. Returns an
362
496
  * onClick handler that fires the action. If action is undefined
363
497
  * the handler is a no-op.
@@ -1184,6 +1318,6 @@ function createRegistry(overrides) {
1184
1318
  return Object.freeze(merged);
1185
1319
  }
1186
1320
  //#endregion
1187
- export { ArtifactErrorBoundary, Button, Card, Chart, Code, Container, Heading, List, PlaygroundStateProvider, ShowSource, Slider, Stepper, Text, TextField, batch, clearState, componentMap, createPlaygroundState, createRegistry, diffSnapshots, diffState, dumpState, useBoundValue, useBoundValueOrUndefined, usePlaygroundState, useStateAction, validateStateKey };
1321
+ export { ArtifactErrorBoundary, Button, Card, Chart, Code, Container, Heading, List, PlaygroundStateProvider, ShowSource, Slider, Stepper, Text, TextField, batch, clearState, componentMap, createPlaygroundState, createRegistry, diffSnapshots, diffState, dumpState, useBoundSelector, useBoundValue, useBoundValueOrUndefined, useMultiBoundValue, usePlaygroundState, useStateAction, useStoreSnapshot, validateStateKey };
1188
1322
 
1189
1323
  //# sourceMappingURL=index.mjs.map