@storystick/react 0.0.0-stage → 0.2.0-rc.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.
Files changed (102) hide show
  1. package/LICENSE +112 -0
  2. package/NOTICE +17 -0
  3. package/PUBLIC-API.md +36 -0
  4. package/README.md +226 -2
  5. package/dist/.source-hash +1 -0
  6. package/dist/file.d.ts +18 -0
  7. package/dist/file.js +44 -0
  8. package/dist/hydrate-slice.d.ts +3 -0
  9. package/dist/hydrate-slice.js +1 -0
  10. package/dist/image-component.d.ts +26 -0
  11. package/dist/image-component.js +79 -0
  12. package/dist/image-element.d.ts +6 -0
  13. package/dist/image-element.js +68 -0
  14. package/dist/image.d.ts +3 -0
  15. package/dist/image.js +3 -0
  16. package/dist/index.d.ts +1 -0
  17. package/dist/index.js +2 -0
  18. package/dist/link-adapter.d.ts +2 -0
  19. package/dist/link-adapter.js +2 -0
  20. package/dist/link-component.d.ts +53 -0
  21. package/dist/link-component.js +97 -0
  22. package/dist/link.d.ts +3 -0
  23. package/dist/link.js +3 -0
  24. package/dist/preview-generation.d.ts +21 -0
  25. package/dist/preview-generation.js +134 -0
  26. package/dist/preview-host/preview-assistant-continuation.d.ts +9 -0
  27. package/dist/preview-host/preview-assistant-continuation.js +39 -0
  28. package/dist/preview-host/preview-authoring-client.d.ts +12 -0
  29. package/dist/preview-host/preview-authoring-client.js +100 -0
  30. package/dist/preview-host/preview-editor-continuation.d.ts +13 -0
  31. package/dist/preview-host/preview-editor-continuation.js +68 -0
  32. package/dist/preview-host/preview-standalone-controls.d.ts +18 -0
  33. package/dist/preview-host/preview-standalone-controls.js +518 -0
  34. package/dist/preview-host/preview-studio-destination.d.ts +20 -0
  35. package/dist/preview-host/preview-studio-destination.js +55 -0
  36. package/dist/preview-host/use-standalone-presentation.d.ts +15 -0
  37. package/dist/preview-host/use-standalone-presentation.js +223 -0
  38. package/dist/preview-icons.d.ts +10 -0
  39. package/dist/preview-icons.js +8 -0
  40. package/dist/preview-interaction-control.d.ts +6 -0
  41. package/dist/preview-interaction-control.js +39 -0
  42. package/dist/preview-panel-state.d.ts +15 -0
  43. package/dist/preview-panel-state.js +28 -0
  44. package/dist/preview-provenance-publisher-model.d.ts +5 -0
  45. package/dist/preview-provenance-publisher-model.js +9 -0
  46. package/dist/preview-provenance-publisher.d.ts +15 -0
  47. package/dist/preview-provenance-publisher.js +68 -0
  48. package/dist/preview-provenance.d.ts +6 -0
  49. package/dist/preview-provenance.js +4 -0
  50. package/dist/preview-scope.d.ts +128 -0
  51. package/dist/preview-scope.js +161 -0
  52. package/dist/preview-slice-scope.d.ts +32 -0
  53. package/dist/preview-slice-scope.js +33 -0
  54. package/dist/preview-text-props.d.ts +18 -0
  55. package/dist/preview-text-props.js +13 -0
  56. package/dist/preview-toolbar-layout.d.ts +42 -0
  57. package/dist/preview-toolbar-layout.js +139 -0
  58. package/dist/preview-toolbar-panel.d.ts +13 -0
  59. package/dist/preview-toolbar-panel.js +6 -0
  60. package/dist/preview-toolbar-resize-handles.d.ts +9 -0
  61. package/dist/preview-toolbar-resize-handles.js +14 -0
  62. package/dist/preview-toolbar-shell.d.ts +18 -0
  63. package/dist/preview-toolbar-shell.js +138 -0
  64. package/dist/public-rich-text.d.ts +15 -0
  65. package/dist/public-rich-text.js +11 -0
  66. package/dist/public-slice-zone.d.ts +7 -0
  67. package/dist/public-slice-zone.js +6 -0
  68. package/dist/query-cache.d.ts +14 -0
  69. package/dist/query-cache.js +289 -0
  70. package/dist/query-preview-loader.d.ts +9 -0
  71. package/dist/query-preview-loader.js +28 -0
  72. package/dist/query-preview-runtime.d.ts +3 -0
  73. package/dist/query-preview-runtime.js +3 -0
  74. package/dist/rendering.d.ts +29 -0
  75. package/dist/rendering.js +17 -0
  76. package/dist/resize-edge.d.ts +8 -0
  77. package/dist/resize-edge.js +33 -0
  78. package/dist/rich-text.d.ts +150 -0
  79. package/dist/rich-text.js +618 -0
  80. package/dist/slice-render-failure.d.ts +8 -0
  81. package/dist/slice-render-failure.js +46 -0
  82. package/dist/slice-zone.d.ts +82 -0
  83. package/dist/slice-zone.js +273 -0
  84. package/dist/slice.d.ts +16 -0
  85. package/dist/slice.js +46 -0
  86. package/dist/storystick-mark.d.ts +3 -0
  87. package/dist/storystick-mark.js +5 -0
  88. package/dist/use-generated-query.d.ts +37 -0
  89. package/dist/use-generated-query.js +308 -0
  90. package/dist/use-momentary-preview-navigate-mode.d.ts +11 -0
  91. package/dist/use-momentary-preview-navigate-mode.js +158 -0
  92. package/dist/use-preview-perspective-intent.d.ts +53 -0
  93. package/dist/use-preview-perspective-intent.js +227 -0
  94. package/dist/use-preview-pointer-drag.d.ts +16 -0
  95. package/dist/use-preview-pointer-drag.js +98 -0
  96. package/dist/use-preview-toolbar-layout.d.ts +36 -0
  97. package/dist/use-preview-toolbar-layout.js +141 -0
  98. package/dist/use-render-mode-debounce.d.ts +31 -0
  99. package/dist/use-render-mode-debounce.js +143 -0
  100. package/dist/video.d.ts +21 -0
  101. package/dist/video.js +63 -0
  102. package/package.json +146 -3
@@ -0,0 +1,143 @@
1
+ 'use client';
2
+ /**
3
+ * useRenderModeDebounce — render-mode-aware value debounce.
4
+ *
5
+ * Wraps a reactive value and returns a copy that only updates after
6
+ * the debounce window appropriate for the current render mode:
7
+ *
8
+ * - Navigation mode (`render-mode: 'navigation'` postMessage from
9
+ * the Studio) → 100ms debounce. The editor reads STRUCTURE
10
+ * (section order, slice type, slice count), not typography —
11
+ * debouncing typographic updates by 100ms is imperceptible
12
+ * in the miniature and reduces paint pressure during fast
13
+ * typing.
14
+ *
15
+ * - Device mode → 0ms (no debounce, real-time feedback).
16
+ *
17
+ * Spec: ADR-059 §Technical commitments;
18
+ * spec/preview-system.md §"Convex subscription debounced 100ms in
19
+ * Navigation mode".
20
+ *
21
+ * Implementation note: `@storystick/preview` is dynamically imported
22
+ * (it's a `private: true` package and may not be installed in
23
+ * production consumer apps). When the import fails, the hook falls
24
+ * back to a passthrough — no debounce, no error. Production builds
25
+ * never load preview anyway.
26
+ */
27
+ import { useEffect, useState, useRef } from 'react';
28
+ let previewModuleCache = undefined;
29
+ async function loadPreviewModule() {
30
+ if (previewModuleCache !== undefined)
31
+ return previewModuleCache;
32
+ try {
33
+ const mod = (await import('@storystick/preview'));
34
+ if (typeof mod.getRenderMode === 'function' &&
35
+ typeof mod.subscribeRenderMode === 'function' &&
36
+ typeof mod.debounceForRenderMode === 'function') {
37
+ previewModuleCache = mod;
38
+ return mod;
39
+ }
40
+ previewModuleCache = null;
41
+ return null;
42
+ }
43
+ catch {
44
+ previewModuleCache = null;
45
+ return null;
46
+ }
47
+ }
48
+ /**
49
+ * ADR-125 D3 — synchronous best-effort field-Proxy wrap at the client
50
+ * query seam. Co-located with the preview-module loader so there is ONE
51
+ * dynamic-import discipline + ONE "production never loads preview" doc
52
+ * (anti-spaghetti). Behaviour:
53
+ *
54
+ * - Module not cached yet (first renders, or non-preview where it
55
+ * never loads) → returns `data` unchanged. `useRenderModeDebounce`
56
+ * (called by the generated-query hook) kicks the async load;
57
+ * once cached, subsequent renders wrap. The preview iframe re-renders
58
+ * on `contentRevision` so the model populates well within the
59
+ * first-visible window.
60
+ * - Cached but the SDK reports tracking inactive (not embedded, SSR,
61
+ * production) → `wrapWithProxy` itself is a referential identity
62
+ * no-op (Lane G). Zero overhead, zero allocation.
63
+ *
64
+ * RSC residual (ADR-125 §5/§7): a pure-RSC customer (Next App Router
65
+ * `createServerClient()` — the next-marketing/Coast dogfood) never
66
+ * calls this hook, so its field model is never registered. Field
67
+ * auto-annotation there degrades to `data-ss-doc` (Lane A, serialized)
68
+ * + section overlays (PR1b) + the `data-ss` developer escape hatch
69
+ * (Lane C). Closing it needs the SDK to observe the resolved doc
70
+ * client-side — an ADR-031 Pattern-C extension, deliberately out of PR2.
71
+ */
72
+ export function tryWrapResultForPreview(docs) {
73
+ if (previewModuleCache == null)
74
+ return docs;
75
+ const wrap = previewModuleCache.wrapWithProxy;
76
+ if (typeof wrap !== 'function')
77
+ return docs;
78
+ if (!Array.isArray(docs))
79
+ return docs;
80
+ // Wrap each QueryResult under its own documentId scope. `wrap` is an
81
+ // identity no-op when tracking is inactive, so this is safe in tests
82
+ // (jsdom is not an embedded iframe) and production (module unloaded).
83
+ return docs.map((d) => {
84
+ const docId = d && typeof d === 'object' && typeof d.documentId === 'string'
85
+ ? d.documentId
86
+ : null;
87
+ return wrap(d, docId);
88
+ });
89
+ }
90
+ export function useRenderModeDebounce(value) {
91
+ const [renderMode, setRenderMode] = useState('device');
92
+ const [debounced, setDebounced] = useState(value);
93
+ const timerRef = useRef(null);
94
+ useEffect(() => {
95
+ let unsub;
96
+ let cancelled = false;
97
+ void loadPreviewModule().then((mod) => {
98
+ if (cancelled || !mod)
99
+ return;
100
+ // Seed with current state first so we don't strand on 'device'
101
+ // if the runtime already flipped before this hook mounted.
102
+ setRenderMode(mod.getRenderMode());
103
+ unsub = mod.subscribeRenderMode(setRenderMode);
104
+ });
105
+ return () => {
106
+ cancelled = true;
107
+ unsub?.();
108
+ };
109
+ }, []);
110
+ useEffect(() => {
111
+ const ms = renderMode === 'navigation' ? 100 : 0;
112
+ if (ms === 0) {
113
+ // Flush any pending tail update synchronously so we don't
114
+ // strand the user on a stale value when they switch modes.
115
+ if (timerRef.current !== null) {
116
+ clearTimeout(timerRef.current);
117
+ timerRef.current = null;
118
+ }
119
+ setDebounced(value);
120
+ return;
121
+ }
122
+ if (timerRef.current !== null)
123
+ clearTimeout(timerRef.current);
124
+ timerRef.current = setTimeout(() => {
125
+ timerRef.current = null;
126
+ setDebounced(value);
127
+ }, ms);
128
+ return () => {
129
+ if (timerRef.current !== null) {
130
+ clearTimeout(timerRef.current);
131
+ timerRef.current = null;
132
+ }
133
+ };
134
+ }, [value, renderMode]);
135
+ return debounced;
136
+ }
137
+ /**
138
+ * Test-only — flushes the dynamic-import cache so tests can inject
139
+ * fake preview modules. Not exported from index.ts.
140
+ */
141
+ export function _resetPreviewModuleCacheForTests() {
142
+ previewModuleCache = undefined;
143
+ }
@@ -0,0 +1,21 @@
1
+ import * as React from 'react';
2
+ import { type ResolveVideoOptions, type ResolvedStorystickVideo, type StorystickVideoField } from '@storystick/media/video';
3
+ /** Resolve a Video field from an explicit or value-owned asset metadata. */
4
+ export { resolveVideo } from '@storystick/media/video';
5
+ type VideoElementProps = Omit<React.ComponentPropsWithRef<'video'>, 'src' | 'poster' | 'width' | 'height' | 'children'>;
6
+ export type StorystickVideoComponentProps = VideoElementProps & {
7
+ video: StorystickVideoField | null | undefined;
8
+ options?: ResolveVideoOptions;
9
+ children?: React.ReactNode;
10
+ fallback?: StorystickVideoFallback;
11
+ };
12
+ export type StorystickVideoFailure = Exclude<ResolvedStorystickVideo, {
13
+ readonly status: 'ok';
14
+ }>;
15
+ export type StorystickVideoFallback = React.ReactNode | ((resolution: StorystickVideoFailure) => React.ReactNode);
16
+ /**
17
+ * Server-safe native video with provider-owned source, optional poster,
18
+ * resolved WebVTT tracks and Preview provenance carriers.
19
+ */
20
+ export declare function StorystickVideo(input: StorystickVideoComponentProps): React.ReactElement | null;
21
+ export type { ResolvedStorystickVideo, ResolvedStorystickVideoPoster, ResolvedStorystickVideoTrack, ResolveVideoOptions, StorystickVideoField, StorystickVideoProps, StorystickVideoTrack, StorystickVideoTrackProps, VideoPosterStatus, VideoStatus, VideoTrackStatus, } from '@storystick/media/video';
package/dist/video.js ADDED
@@ -0,0 +1,63 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { emitMediaDiagnostic, isProduction } from '@storystick/media/internal/render-diagnostics';
3
+ import { cleanPreviewAttributes } from '@storystick/types/internal/preview-provenance';
4
+ import { resolveVideo, } from '@storystick/media/video';
5
+ /** Resolve a Video field from an explicit or value-owned asset metadata. */
6
+ export { resolveVideo } from '@storystick/media/video';
7
+ /**
8
+ * Server-safe native video with provider-owned source, optional poster,
9
+ * resolved WebVTT tracks and Preview provenance carriers.
10
+ */
11
+ export function StorystickVideo(input) {
12
+ const { video, options = {}, children, fallback, ...elementProps } = input;
13
+ const diagnostics = [];
14
+ const resolved = resolveVideo(video, {
15
+ ...options,
16
+ warn: (message) => diagnostics.push(message),
17
+ });
18
+ if (resolved.status !== 'ok') {
19
+ if (!isProduction() && resolved.status !== 'no-video') {
20
+ emitMediaDiagnostic(`StorystickWarning: video resolution status=${resolved.status}.${diagnostics.length > 0 ? ` ${diagnostics.join(' ')}` : ''}`, options.warn);
21
+ }
22
+ if (fallback !== undefined) {
23
+ return (_jsx(_Fragment, { children: typeof fallback === 'function' ? fallback(resolved) : fallback }));
24
+ }
25
+ if (isProduction() || resolved.status === 'no-video')
26
+ return null;
27
+ return (_jsx("span", { "aria-hidden": "true", "data-storystick-video-status": resolved.status, ...('data-ss-e' in resolved
28
+ ? { 'data-ss-e': resolved['data-ss-e'] }
29
+ : {}) }));
30
+ }
31
+ if (!isProduction()) {
32
+ for (const message of diagnostics)
33
+ emitMediaDiagnostic(message, options.warn);
34
+ }
35
+ return (_jsxs("video", { ...videoElementProps(resolved.props, elementProps), children: [resolved.tracks.map((track) => track.status === 'ok' ? (_jsx("track", { ...track.props }, track._key)) : null), children] }));
36
+ }
37
+ function videoElementProps(resolved, input) {
38
+ const forwarded = { ...input };
39
+ const protectedProps = forwarded;
40
+ for (const key of [
41
+ 'src',
42
+ 'poster',
43
+ 'width',
44
+ 'height',
45
+ 'data-ss-e',
46
+ 'children',
47
+ 'dangerouslySetInnerHTML',
48
+ ]) {
49
+ delete protectedProps[key];
50
+ }
51
+ return {
52
+ ...cleanPreviewAttributes(forwarded),
53
+ src: resolved.src,
54
+ ...(resolved.poster ? { poster: resolved.poster } : {}),
55
+ width: resolved.width,
56
+ height: resolved.height,
57
+ crossOrigin: input.crossOrigin ?? resolved.crossOrigin,
58
+ controls: input.controls ?? resolved.controls,
59
+ preload: input.preload ?? resolved.preload,
60
+ playsInline: input.playsInline ?? resolved.playsInline,
61
+ ...('data-ss-e' in resolved ? { 'data-ss-e': resolved['data-ss-e'] } : {}),
62
+ };
63
+ }
package/package.json CHANGED
@@ -1,6 +1,149 @@
1
1
  {
2
2
  "name": "@storystick/react",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.2.0-rc.0",
4
+ "license": "FSL-1.1-MIT",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "react-server": "./dist/rendering.js",
12
+ "import": "./dist/index.js"
13
+ },
14
+ "./preview-text": {
15
+ "types": "./dist/preview-text-props.d.ts",
16
+ "import": "./dist/preview-text-props.js"
17
+ },
18
+ "./image": {
19
+ "types": "./dist/image.d.ts",
20
+ "import": "./dist/image.js"
21
+ },
22
+ "./file": {
23
+ "types": "./dist/file.d.ts",
24
+ "import": "./dist/file.js"
25
+ },
26
+ "./video": {
27
+ "types": "./dist/video.d.ts",
28
+ "import": "./dist/video.js",
29
+ "default": "./dist/video.js"
30
+ },
31
+ "./slice": {
32
+ "types": "./dist/slice.d.ts",
33
+ "import": "./dist/slice.js"
34
+ },
35
+ "./link": {
36
+ "types": "./dist/link.d.ts",
37
+ "import": "./dist/link.js"
38
+ },
39
+ "./query": {
40
+ "types": "./dist/use-generated-query.d.ts",
41
+ "import": "./dist/use-generated-query.js"
42
+ },
43
+ "./rich-text": {
44
+ "types": "./dist/public-rich-text.d.ts",
45
+ "import": "./dist/public-rich-text.js"
46
+ },
47
+ "./slice-zone": {
48
+ "types": "./dist/public-slice-zone.d.ts",
49
+ "import": "./dist/public-slice-zone.js"
50
+ },
51
+ "./internal/preview-provenance": {
52
+ "types": "./dist/preview-provenance.d.ts",
53
+ "import": "./dist/preview-provenance.js"
54
+ },
55
+ "./internal/link-adapter": {
56
+ "types": "./dist/link-adapter.d.ts",
57
+ "import": "./dist/link-adapter.js"
58
+ },
59
+ "./internal/storystick-mark": {
60
+ "types": "./dist/storystick-mark.d.ts",
61
+ "import": "./dist/storystick-mark.js"
62
+ },
63
+ "./internal/preview-interaction-control": {
64
+ "types": "./dist/preview-interaction-control.d.ts",
65
+ "import": "./dist/preview-interaction-control.js"
66
+ },
67
+ "./internal/use-preview-perspective-intent": {
68
+ "types": "./dist/use-preview-perspective-intent.d.ts",
69
+ "import": "./dist/use-preview-perspective-intent.js"
70
+ },
71
+ "./internal/use-momentary-preview-navigate-mode": {
72
+ "types": "./dist/use-momentary-preview-navigate-mode.d.ts",
73
+ "import": "./dist/use-momentary-preview-navigate-mode.js"
74
+ },
75
+ "./internal/resize-edge": {
76
+ "types": "./dist/resize-edge.d.ts",
77
+ "import": "./dist/resize-edge.js"
78
+ },
79
+ "./internal/preview-toolbar-shell": {
80
+ "types": "./dist/preview-toolbar-shell.d.ts",
81
+ "import": "./dist/preview-toolbar-shell.js"
82
+ },
83
+ "./internal/preview-panel-state": {
84
+ "types": "./dist/preview-panel-state.d.ts",
85
+ "import": "./dist/preview-panel-state.js"
86
+ },
87
+ "./internal/preview-host": {
88
+ "types": "./dist/preview-host/preview-standalone-controls.d.ts",
89
+ "import": "./dist/preview-host/preview-standalone-controls.js"
90
+ }
91
+ },
92
+ "files": [
93
+ "dist",
94
+ "LICENSE",
95
+ "README.md",
96
+ "NOTICE",
97
+ "PUBLIC-API.md"
98
+ ],
99
+ "publishConfig": {
100
+ "access": "public",
101
+ "provenance": false,
102
+ "tag": "beta"
103
+ },
104
+ "dependencies": {
105
+ "@tanstack/query-core": "5.101.4",
106
+ "@base-ui/react": "1.3.0",
107
+ "@storystick/preview": "0.2.0-rc.0",
108
+ "@storystick/media": "0.2.0-rc.0",
109
+ "@storystick/types": "0.1.0-rc.0",
110
+ "@storystick/client": "0.2.0-rc.0"
111
+ },
112
+ "peerDependencies": {
113
+ "react": "^19.2.8",
114
+ "react-dom": "^19.2.8"
115
+ },
116
+ "devDependencies": {
117
+ "@testing-library/react": "^16.3.2",
118
+ "@types/node": "^22.13.10",
119
+ "@types/react": "^19.2.18",
120
+ "@types/react-dom": "^19.2.5",
121
+ "jsdom": "^29.0.2",
122
+ "react": "^19.2.8",
123
+ "react-dom": "^19.2.8",
124
+ "typescript": "^5.8.2",
125
+ "vitest": "^3.1.1"
126
+ },
127
+ "engines": {
128
+ "node": ">=24.19.0 <25"
129
+ },
130
+ "repository": {
131
+ "type": "git",
132
+ "url": "git+https://github.com/withstorystick/storystick.git",
133
+ "directory": "packages/react"
134
+ },
135
+ "homepage": "https://storystick.app",
136
+ "bugs": {
137
+ "url": "https://storystick.app"
138
+ },
139
+ "sideEffects": [
140
+ "**/*.css"
141
+ ],
142
+ "scripts": {
143
+ "build": "node ../../scripts/package-dist.mjs clean && tsc && node ../../scripts/package-dist.mjs seal",
144
+ "test": "vitest run",
145
+ "test:dist-fresh": "node ../../scripts/package-dist.mjs assert",
146
+ "test:types": "tsc -p tsconfig.type-tests.json --noEmit",
147
+ "test:watch": "vitest"
148
+ }
6
149
  }