@dfosco/storyboard-react 1.17.2 → 1.18.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.2",
3
+ "version": "1.18.0",
4
4
  "type": "module",
5
5
  "dependencies": {
6
6
  "@dfosco/storyboard-core": "*",
@@ -179,6 +179,9 @@
179
179
  background-repeat: no-repeat;
180
180
  background-position: right 12px center;
181
181
  min-width: 140px;
182
+ max-width: 220px;
183
+ text-overflow: ellipsis;
184
+ overflow: hidden;
182
185
  transition: border-color 0.15s ease;
183
186
  }
184
187
 
@@ -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,18 @@ 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
+
77
101
  return [
78
- `import { init } from '@dfosco/storyboard-core'`,
102
+ imports.join('\n'),
79
103
  '',
80
104
  declarations.join('\n'),
81
105
  '',
@@ -83,7 +107,7 @@ function generateModule(index) {
83
107
  `const objects = {\n${entries.object.join(',\n')}\n}`,
84
108
  `const records = {\n${entries.record.join(',\n')}\n}`,
85
109
  '',
86
- `init({ scenes, objects, records })`,
110
+ initCalls.join('\n'),
87
111
  '',
88
112
  `export { scenes, objects, records }`,
89
113
  `export const index = { scenes, objects, records }`,
@@ -126,7 +150,7 @@ export default function storyboardDataPlugin() {
126
150
  load(id) {
127
151
  if (id !== RESOLVED_ID) return null
128
152
  if (!index) index = buildIndex(root)
129
- return generateModule(index)
153
+ return generateModule(index, root)
130
154
  },
131
155
 
132
156
  configureServer(server) {
@@ -145,9 +169,26 @@ export default function storyboardDataPlugin() {
145
169
  }
146
170
  }
147
171
 
172
+ // Watch storyboard.config.json for changes
173
+ const { configPath } = readConfig(root)
174
+ watcher.add(configPath)
175
+ const invalidateConfig = (filePath) => {
176
+ if (path.resolve(filePath) === configPath) {
177
+ index = null
178
+ const mod = server.moduleGraph.getModuleById(RESOLVED_ID)
179
+ if (mod) {
180
+ server.moduleGraph.invalidateModule(mod)
181
+ server.ws.send({ type: 'full-reload' })
182
+ }
183
+ }
184
+ }
185
+
148
186
  watcher.on('add', invalidate)
149
187
  watcher.on('unlink', invalidate)
150
- watcher.on('change', invalidate)
188
+ watcher.on('change', (filePath) => {
189
+ invalidate(filePath)
190
+ invalidateConfig(filePath)
191
+ })
151
192
  },
152
193
 
153
194
  // Rebuild index on each build start