@dfosco/storyboard-react 1.17.3 → 1.20.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dfosco/storyboard-react",
3
- "version": "1.17.3",
3
+ "version": "1.20.0",
4
4
  "type": "module",
5
5
  "dependencies": {
6
6
  "@dfosco/storyboard-core": "*",
@@ -0,0 +1,16 @@
1
+ import { useSyncExternalStore } from 'react'
2
+ import { getFlag, subscribeToHash, getHashSnapshot, subscribeToStorage, getStorageSnapshot } from '@dfosco/storyboard-core'
3
+
4
+ /**
5
+ * React hook for reading a feature flag value.
6
+ * Re-renders when the flag changes (via hash or localStorage).
7
+ *
8
+ * @param {string} key - Flag key (without "flag." prefix)
9
+ * @returns {boolean} Current resolved flag value
10
+ */
11
+ export function useFeatureFlag(key) {
12
+ // Subscribe to both hash and storage changes for reactivity
13
+ useSyncExternalStore(subscribeToHash, getHashSnapshot)
14
+ useSyncExternalStore(subscribeToStorage, getStorageSnapshot)
15
+ return getFlag(key)
16
+ }
package/src/index.js CHANGED
@@ -19,6 +19,7 @@ export { useRecordOverride } from './hooks/useRecordOverride.js'
19
19
  export { useLocalStorage } from './hooks/useLocalStorage.js'
20
20
  export { useHideMode } from './hooks/useHideMode.js'
21
21
  export { useUndoRedo } from './hooks/useUndoRedo.js'
22
+ export { useFeatureFlag } from './hooks/useFeatureFlag.js'
22
23
 
23
24
  // React Router integration
24
25
  export { installHashPreserver } from './hashPreserver.js'
@@ -59,7 +59,21 @@ function buildIndex(root) {
59
59
  * Reads each data file, parses JSONC at build time, and emits pre-parsed
60
60
  * JavaScript objects — no runtime parsing needed.
61
61
  */
62
- function generateModule(index) {
62
+ /**
63
+ * Read storyboard.config.json from the project root (if it exists).
64
+ * Returns the parsed config object, or null if not found.
65
+ */
66
+ function readConfig(root) {
67
+ const configPath = path.resolve(root, 'storyboard.config.json')
68
+ try {
69
+ const raw = fs.readFileSync(configPath, 'utf-8')
70
+ return { config: parseJsonc(raw), configPath }
71
+ } catch {
72
+ return { config: null, configPath }
73
+ }
74
+ }
75
+
76
+ function generateModule(index, root) {
63
77
  const declarations = []
64
78
  const entries = { scene: [], object: [], record: [] }
65
79
  let i = 0
@@ -74,8 +88,24 @@ function generateModule(index) {
74
88
  }
75
89
  }
76
90
 
91
+ const imports = [`import { init } from '@dfosco/storyboard-core'`]
92
+ const initCalls = [`init({ scenes, objects, records })`]
93
+
94
+ // Feature flags from storyboard.config.json
95
+ const { config } = readConfig(root)
96
+ if (config?.featureFlags && Object.keys(config.featureFlags).length > 0) {
97
+ imports.push(`import { initFeatureFlags } from '@dfosco/storyboard-core'`)
98
+ initCalls.push(`initFeatureFlags(${JSON.stringify(config.featureFlags)})`)
99
+ }
100
+
101
+ // Plugin configuration from storyboard.config.json
102
+ if (config?.plugins && Object.keys(config.plugins).length > 0) {
103
+ imports.push(`import { initPlugins } from '@dfosco/storyboard-core'`)
104
+ initCalls.push(`initPlugins(${JSON.stringify(config.plugins)})`)
105
+ }
106
+
77
107
  return [
78
- `import { init } from '@dfosco/storyboard-core'`,
108
+ imports.join('\n'),
79
109
  '',
80
110
  declarations.join('\n'),
81
111
  '',
@@ -83,7 +113,7 @@ function generateModule(index) {
83
113
  `const objects = {\n${entries.object.join(',\n')}\n}`,
84
114
  `const records = {\n${entries.record.join(',\n')}\n}`,
85
115
  '',
86
- `init({ scenes, objects, records })`,
116
+ initCalls.join('\n'),
87
117
  '',
88
118
  `export { scenes, objects, records }`,
89
119
  `export const index = { scenes, objects, records }`,
@@ -126,7 +156,7 @@ export default function storyboardDataPlugin() {
126
156
  load(id) {
127
157
  if (id !== RESOLVED_ID) return null
128
158
  if (!index) index = buildIndex(root)
129
- return generateModule(index)
159
+ return generateModule(index, root)
130
160
  },
131
161
 
132
162
  configureServer(server) {
@@ -145,9 +175,26 @@ export default function storyboardDataPlugin() {
145
175
  }
146
176
  }
147
177
 
178
+ // Watch storyboard.config.json for changes
179
+ const { configPath } = readConfig(root)
180
+ watcher.add(configPath)
181
+ const invalidateConfig = (filePath) => {
182
+ if (path.resolve(filePath) === configPath) {
183
+ index = null
184
+ const mod = server.moduleGraph.getModuleById(RESOLVED_ID)
185
+ if (mod) {
186
+ server.moduleGraph.invalidateModule(mod)
187
+ server.ws.send({ type: 'full-reload' })
188
+ }
189
+ }
190
+ }
191
+
148
192
  watcher.on('add', invalidate)
149
193
  watcher.on('unlink', invalidate)
150
- watcher.on('change', invalidate)
194
+ watcher.on('change', (filePath) => {
195
+ invalidate(filePath)
196
+ invalidateConfig(filePath)
197
+ })
151
198
  },
152
199
 
153
200
  // Rebuild index on each build start