@memberjunction/react-runtime 5.50.0 → 6.1.0-edge.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/.turbo/turbo-build.log +26 -18
- package/CHANGELOG.md +50 -0
- package/LICENSE +7 -0
- package/dist/324.runtime.umd.js +12 -12
- package/dist/{490.runtime.umd.js → 456.runtime.umd.js} +40 -40
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/runtime.umd.js +1037 -346
- package/dist/utilities/component-styles.d.ts +2 -1
- package/dist/utilities/component-styles.d.ts.map +1 -1
- package/dist/utilities/component-styles.js +79 -0
- package/dist/utilities/component-styles.js.map +1 -1
- package/dist/utilities/library-loader.d.ts.map +1 -1
- package/dist/utilities/library-loader.js +20 -5
- package/dist/utilities/library-loader.js.map +1 -1
- package/package.json +17 -17
- package/src/__tests__/component-styles.test.ts +252 -0
- package/src/__tests__/library-loader-load-order.test.ts +367 -0
- package/src/index.ts +2 -1
- package/src/utilities/component-styles.ts +130 -2
- package/src/utilities/library-loader.ts +32 -9
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* @module @memberjunction/react-runtime/utilities
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { ComponentStyles } from '@memberjunction/interactive-component-types';
|
|
6
|
+
import { ComponentStyles, StyleOverrides } from '@memberjunction/interactive-component-types';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Creates the default component styles for Skip components
|
|
@@ -27,17 +27,26 @@ export function SetupStyles(): ComponentStyles {
|
|
|
27
27
|
// Status colors
|
|
28
28
|
success: '#10B981',
|
|
29
29
|
successLight: '#D1FAE5',
|
|
30
|
+
successText: '#047857',
|
|
31
|
+
successBorder: '#b7eb8f',
|
|
30
32
|
warning: '#F59E0B',
|
|
31
33
|
warningLight: '#FEF3C7',
|
|
34
|
+
warningText: '#8c6c00',
|
|
35
|
+
warningBorder: '#ffe58f',
|
|
32
36
|
error: '#EF4444',
|
|
33
37
|
errorLight: '#FEE2E2',
|
|
38
|
+
errorText: '#B91C1C',
|
|
39
|
+
errorBorder: '#FECACA',
|
|
34
40
|
info: '#3B82F6',
|
|
35
41
|
infoLight: '#DBEAFE',
|
|
36
|
-
|
|
42
|
+
infoText: '#1D4ED8',
|
|
43
|
+
infoBorder: '#91d5ff',
|
|
44
|
+
|
|
37
45
|
// Base colors
|
|
38
46
|
background: '#FFFFFF',
|
|
39
47
|
surface: '#F8FAFC',
|
|
40
48
|
surfaceHover: '#F1F5F9',
|
|
49
|
+
overlay: 'rgba(0, 0, 0, 0.5)', // Modal scrim
|
|
41
50
|
|
|
42
51
|
// Text colors with better contrast
|
|
43
52
|
text: '#1E293B',
|
|
@@ -125,6 +134,18 @@ export function SetupStyles(): ComponentStyles {
|
|
|
125
134
|
'#2196F3', '#4CAF50', '#FF9800', '#E91E63', '#9C27B0',
|
|
126
135
|
'#00BCD4', '#F44336', '#8BC34A', '#FF5722', '#3F51B5',
|
|
127
136
|
],
|
|
137
|
+
// Default sequential (single-hue) intensity ramp, light-to-dark, for heatmaps
|
|
138
|
+
// and density shading. Overridden per-theme from `--mj-viz-seq-*`.
|
|
139
|
+
sequentialScale: [
|
|
140
|
+
'#E3F2FD', '#BBDEFB', '#90CAF9', '#42A5F5', '#2196F3', '#1976D2', '#0D47A1',
|
|
141
|
+
],
|
|
142
|
+
// Default diverging ramp for measures with a meaningful midpoint.
|
|
143
|
+
// Overridden per-theme from `--mj-viz-div-*`.
|
|
144
|
+
divergingScale: {
|
|
145
|
+
low: '#F44336',
|
|
146
|
+
mid: '#EEEEEE',
|
|
147
|
+
high: '#4CAF50',
|
|
148
|
+
},
|
|
128
149
|
}
|
|
129
150
|
}
|
|
130
151
|
|
|
@@ -143,19 +164,31 @@ const THEME_COLOR_TOKEN_MAP: Record<string, string> = {
|
|
|
143
164
|
primaryHover: '--mj-brand-primary-hover',
|
|
144
165
|
primaryActive: '--mj-brand-primary-active',
|
|
145
166
|
primaryLight: '--mj-brand-primary-light',
|
|
167
|
+
// Secondary
|
|
168
|
+
secondary: '--mj-brand-secondary',
|
|
169
|
+
secondaryHover: '--mj-brand-secondary-hover',
|
|
146
170
|
// Status
|
|
147
171
|
success: '--mj-status-success',
|
|
148
172
|
successLight: '--mj-status-success-bg',
|
|
173
|
+
successText: '--mj-status-success-text',
|
|
174
|
+
successBorder: '--mj-status-success-border',
|
|
149
175
|
warning: '--mj-status-warning',
|
|
150
176
|
warningLight: '--mj-status-warning-bg',
|
|
177
|
+
warningText: '--mj-status-warning-text',
|
|
178
|
+
warningBorder: '--mj-status-warning-border',
|
|
151
179
|
error: '--mj-status-error',
|
|
152
180
|
errorLight: '--mj-status-error-bg',
|
|
181
|
+
errorText: '--mj-status-error-text',
|
|
182
|
+
errorBorder: '--mj-status-error-border',
|
|
153
183
|
info: '--mj-status-info',
|
|
154
184
|
infoLight: '--mj-status-info-bg',
|
|
185
|
+
infoText: '--mj-status-info-text',
|
|
186
|
+
infoBorder: '--mj-status-info-border',
|
|
155
187
|
// Surfaces (MJ: page = tinted, surface = elevated/white in light mode)
|
|
156
188
|
background: '--mj-bg-page',
|
|
157
189
|
surface: '--mj-bg-surface',
|
|
158
190
|
surfaceHover: '--mj-bg-surface-hover',
|
|
191
|
+
overlay: '--mj-bg-overlay',
|
|
159
192
|
// Text
|
|
160
193
|
text: '--mj-text-primary',
|
|
161
194
|
textSecondary: '--mj-text-secondary',
|
|
@@ -173,6 +206,9 @@ const THEME_COLOR_TOKEN_MAP: Record<string, string> = {
|
|
|
173
206
|
/** Number of `--mj-viz-N` categorical tokens the bridge probes for `chartPalette`. */
|
|
174
207
|
const VIZ_TOKEN_COUNT = 10;
|
|
175
208
|
|
|
209
|
+
/** Number of `--mj-viz-seq-N` tokens the bridge probes for `sequentialScale`. */
|
|
210
|
+
const VIZ_SEQ_TOKEN_COUNT = 7;
|
|
211
|
+
|
|
176
212
|
/**
|
|
177
213
|
* Reads the live MJ theme (`--mj-*` custom properties on the document root) and
|
|
178
214
|
* layers it over `SetupStyles()`, producing a `ComponentStyles` that follows the
|
|
@@ -216,5 +252,97 @@ export function BuildStylesFromTheme(root?: Element): ComponentStyles {
|
|
|
216
252
|
base.chartPalette = palette;
|
|
217
253
|
}
|
|
218
254
|
|
|
255
|
+
const sequential: string[] = [];
|
|
256
|
+
for (let i = 1; i <= VIZ_SEQ_TOKEN_COUNT; i++) {
|
|
257
|
+
const value = read(`--mj-viz-seq-${i}`);
|
|
258
|
+
if (value) {
|
|
259
|
+
sequential.push(value);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
// A ramp needs at least two stops to interpolate; a single resolved token is
|
|
263
|
+
// treated as an incomplete theme and left on the default.
|
|
264
|
+
if (sequential.length > 1) {
|
|
265
|
+
base.sequentialScale = sequential;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Endpoints are required for a diverging scale to mean anything; `mid` is
|
|
269
|
+
// optional, so only the low/high pair gates the swap.
|
|
270
|
+
const divLow = read('--mj-viz-div-low');
|
|
271
|
+
const divHigh = read('--mj-viz-div-high');
|
|
272
|
+
if (divLow && divHigh) {
|
|
273
|
+
const divMid = read('--mj-viz-div-mid');
|
|
274
|
+
base.divergingScale = divMid ? { low: divLow, mid: divMid, high: divHigh } : { low: divLow, high: divHigh };
|
|
275
|
+
}
|
|
276
|
+
|
|
219
277
|
return base;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/** Multipliers applied to the whole `fontSize` ladder per `StyleOverrides.fontScale`. */
|
|
281
|
+
const FONT_SCALE_FACTOR: Record<string, number> = { small: 0.875, large: 1.25 };
|
|
282
|
+
|
|
283
|
+
/** Smallest px size the scale may produce, so `small` cannot render text illegible. */
|
|
284
|
+
const MIN_FONT_SIZE_PX = 10;
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Rescales every px value in a `fontSize` token map, leaving anything not expressed
|
|
288
|
+
* in whole px (rem, em, clamp(), a keyword) untouched rather than guessing at it.
|
|
289
|
+
*/
|
|
290
|
+
function scaleFontSizes(fontSize: Record<string, string | undefined>, factor: number): Record<string, string | undefined> {
|
|
291
|
+
const scaled: Record<string, string | undefined> = {};
|
|
292
|
+
for (const [key, value] of Object.entries(fontSize)) {
|
|
293
|
+
const px = typeof value === 'string' ? /^\s*(\d+(?:\.\d+)?)px\s*$/.exec(value) : null;
|
|
294
|
+
scaled[key] = px
|
|
295
|
+
? `${Math.max(MIN_FONT_SIZE_PX, Math.round(parseFloat(px[1]) * factor))}px`
|
|
296
|
+
: value;
|
|
297
|
+
}
|
|
298
|
+
return scaled;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Layers explicitly user-requested styling (`ComponentSpec.styleOverrides`) over
|
|
303
|
+
* theme-resolved styles, producing the `styles` a component actually receives.
|
|
304
|
+
*
|
|
305
|
+
* This is what lets "make the charts blue" be honored without a color literal ever
|
|
306
|
+
* entering generated code: the request is carried as spec data and resolved here,
|
|
307
|
+
* above the org theme, so the component keeps reading `styles.chartPalette` and
|
|
308
|
+
* friends. Of the color slots only visualization ones are merged — the override
|
|
309
|
+
* contract deliberately has no background/text/border slots, since those cannot be
|
|
310
|
+
* flipped for dark mode without derived per-mode variants.
|
|
311
|
+
*
|
|
312
|
+
* `fontScale` is the one non-color slot, and it rescales the `typography.fontSize`
|
|
313
|
+
* ladder in place. Doing it here rather than in the generator is what makes a type
|
|
314
|
+
* scale hold: every token moves at once, so text inside registry components and
|
|
315
|
+
* third-party libraries scales along with the generated markup.
|
|
316
|
+
*
|
|
317
|
+
* Returns `base` unchanged when there are no overrides, and never mutates `base`.
|
|
318
|
+
*
|
|
319
|
+
* @param base theme-resolved styles (from `BuildStylesFromTheme()` or `SetupStyles()`)
|
|
320
|
+
* @param overrides the spec's `styleOverrides`, if any
|
|
321
|
+
*/
|
|
322
|
+
export function ApplyStyleOverrides<T extends Partial<ComponentStyles>>(base: T, overrides?: StyleOverrides): T {
|
|
323
|
+
if (!overrides) {
|
|
324
|
+
return base;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const hasChartPalette = Array.isArray(overrides.chartPalette) && overrides.chartPalette.length > 0;
|
|
328
|
+
// A ramp needs at least two stops to interpolate between.
|
|
329
|
+
const hasSequential = Array.isArray(overrides.sequentialScale) && overrides.sequentialScale.length > 1;
|
|
330
|
+
const hasDiverging = !!overrides.divergingScale?.low && !!overrides.divergingScale?.high;
|
|
331
|
+
// 'normal' — and any unrecognized value — leaves the ladder alone.
|
|
332
|
+
const fontFactor = overrides.fontScale ? FONT_SCALE_FACTOR[overrides.fontScale] : undefined;
|
|
333
|
+
const hasFontScale = !!fontFactor && !!base.typography?.fontSize;
|
|
334
|
+
|
|
335
|
+
if (!hasChartPalette && !hasSequential && !hasDiverging && !hasFontScale) {
|
|
336
|
+
return base;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
return {
|
|
340
|
+
...base,
|
|
341
|
+
...(hasChartPalette ? { chartPalette: overrides.chartPalette } : {}),
|
|
342
|
+
...(hasSequential ? { sequentialScale: overrides.sequentialScale } : {}),
|
|
343
|
+
...(hasDiverging ? { divergingScale: overrides.divergingScale } : {}),
|
|
344
|
+
...(hasFontScale
|
|
345
|
+
? { typography: { ...base.typography!, fontSize: scaleFontSizes(base.typography!.fontSize, fontFactor!) } }
|
|
346
|
+
: {}),
|
|
347
|
+
};
|
|
220
348
|
}
|
|
@@ -133,16 +133,39 @@ export class LibraryLoader {
|
|
|
133
133
|
* Load libraries based on the current configuration
|
|
134
134
|
*/
|
|
135
135
|
static async loadLibrariesFromConfig(options?: ConfigLoadOptions, debug?: boolean): Promise<LibraryLoadResult> {
|
|
136
|
-
//
|
|
136
|
+
// Load core runtime libraries in dependency order.
|
|
137
|
+
// ReactDOM's UMD factory captures `window.React` at execution time,
|
|
138
|
+
// so React MUST execute before ReactDOM. Loading them in parallel with
|
|
139
|
+
// async scripts causes an intermittent race condition where ReactDOM
|
|
140
|
+
// executes first and gets `undefined` for React, permanently breaking
|
|
141
|
+
// `createRoot` on that object instance.
|
|
137
142
|
const coreLibraries = getCoreRuntimeLibraries(debug);
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
);
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
143
|
+
const reactLib = coreLibraries.find(lib => lib.globalVariable === 'React');
|
|
144
|
+
const reactDOMLib = coreLibraries.find(lib => lib.globalVariable === 'ReactDOM');
|
|
145
|
+
const babelLib = coreLibraries.find(lib => lib.globalVariable === 'Babel');
|
|
146
|
+
|
|
147
|
+
// Phase 1: React must load first (ReactDOM depends on it)
|
|
148
|
+
let React: unknown;
|
|
149
|
+
if (reactLib) {
|
|
150
|
+
React = await this.loadScript(reactLib.cdnUrl, reactLib.globalVariable, debug, reactLib.fallbackCdnUrls);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Phase 2: ReactDOM and Babel can load in parallel (both only depend on React)
|
|
154
|
+
const phase2Promises: Promise<unknown>[] = [];
|
|
155
|
+
const phase2Labels: string[] = [];
|
|
156
|
+
|
|
157
|
+
if (reactDOMLib) {
|
|
158
|
+
phase2Promises.push(this.loadScript(reactDOMLib.cdnUrl, reactDOMLib.globalVariable, debug, reactDOMLib.fallbackCdnUrls));
|
|
159
|
+
phase2Labels.push('ReactDOM');
|
|
160
|
+
}
|
|
161
|
+
if (babelLib) {
|
|
162
|
+
phase2Promises.push(this.loadScript(babelLib.cdnUrl, babelLib.globalVariable, debug, babelLib.fallbackCdnUrls));
|
|
163
|
+
phase2Labels.push('Babel');
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const phase2Results = await Promise.all(phase2Promises);
|
|
167
|
+
const ReactDOM = phase2Labels.indexOf('ReactDOM') >= 0 ? phase2Results[phase2Labels.indexOf('ReactDOM')] : undefined;
|
|
168
|
+
const Babel = phase2Labels.indexOf('Babel') >= 0 ? phase2Results[phase2Labels.indexOf('Babel')] : undefined;
|
|
146
169
|
|
|
147
170
|
// Expose React and ReactDOM as globals for UMD libraries that expect them
|
|
148
171
|
// Many React component libraries (Recharts, Victory, etc.) expect these as globals
|