@richardmcquiston01/dashboard-widgets-toolkit 0.12.0 → 0.13.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/dist/core.cjs CHANGED
@@ -3236,6 +3236,255 @@ function createLayoutPersistence(adapter, options = {}) {
3236
3236
  };
3237
3237
  }
3238
3238
 
3239
+ // src/core/theme.ts
3240
+ var THEME_TOKEN_VARIABLES = {
3241
+ font: "--dwt-font",
3242
+ surface: "--dwt-surface",
3243
+ page: "--dwt-page",
3244
+ text: "--dwt-text",
3245
+ textSecondary: "--dwt-text-secondary",
3246
+ textMuted: "--dwt-text-muted",
3247
+ border: "--dwt-border",
3248
+ grid: "--dwt-grid",
3249
+ baseline: "--dwt-baseline",
3250
+ crosshair: "--dwt-crosshair",
3251
+ link: "--dwt-link",
3252
+ focus: "--dwt-focus",
3253
+ good: "--dwt-good",
3254
+ bad: "--dwt-bad",
3255
+ meterTrack: "--dwt-meter-track",
3256
+ barTrack: "--dwt-bar-track",
3257
+ buttonHover: "--dwt-button-hover",
3258
+ radius: "--dwt-radius",
3259
+ gap: "--dwt-gap",
3260
+ padding: "--dwt-padding",
3261
+ minColumn: "--dwt-min-column"
3262
+ };
3263
+ var LENGTH_KEYS = /* @__PURE__ */ new Set([
3264
+ "radius",
3265
+ "gap",
3266
+ "padding",
3267
+ "minColumn"
3268
+ ]);
3269
+ function kindOf(key) {
3270
+ if (key === "font") {
3271
+ return "font";
3272
+ }
3273
+ return LENGTH_KEYS.has(key) ? "length" : "color";
3274
+ }
3275
+ var NAME_PATTERN = /^[a-z][a-z0-9-]*$/;
3276
+ var SCOPE_PATTERN = /^[A-Za-z0-9_\-[\]=.# '"]+$/;
3277
+ var HEX_COLOR = /^#(?:[0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/i;
3278
+ var COLOR_FUNCTION = /^(?:rgba?|hsla?|hwb|lab|lch|oklab|oklch|color|color-mix|light-dark)\(.+\)$/i;
3279
+ var VAR_REFERENCE = /^var\(\s*--[A-Za-z0-9_-]+\s*(?:,.+)?\)$/;
3280
+ var COLOR_KEYWORD = /^[A-Za-z]+$/;
3281
+ var LENGTH_VALUE = /^(?:0|-?(?:\d+\.?\d*|\.\d+)(?:px|rem|em|%|ch|vw|vh|cqw|cqh))$/;
3282
+ var LENGTH_FUNCTION = /^(?:calc|min|max|clamp)\(.+\)$/i;
3283
+ var FONT_VALUE = /^[A-Za-z0-9 ,'"_-]+$/;
3284
+ function unsafeReason(value) {
3285
+ if ([...value].some(
3286
+ (character) => character.charCodeAt(0) < 32 || character.charCodeAt(0) === 127
3287
+ )) {
3288
+ return "contains a control character or line break";
3289
+ }
3290
+ if (/[;{}<>\\@]/.test(value)) {
3291
+ return "contains one of ; { } < > \\ @";
3292
+ }
3293
+ const lower = value.toLowerCase();
3294
+ if (lower.includes("/*") || lower.includes("*/")) {
3295
+ return "contains a comment marker";
3296
+ }
3297
+ if (lower.includes("url(") || lower.includes("expression(")) {
3298
+ return "contains url( or expression(";
3299
+ }
3300
+ let depth = 0;
3301
+ for (const character of value) {
3302
+ if (character === "(") {
3303
+ depth += 1;
3304
+ } else if (character === ")") {
3305
+ depth -= 1;
3306
+ if (depth < 0) {
3307
+ return "has unbalanced parentheses";
3308
+ }
3309
+ }
3310
+ }
3311
+ return depth === 0 ? null : "has unbalanced parentheses";
3312
+ }
3313
+ function countOf(value, character) {
3314
+ return value.split(character).length - 1;
3315
+ }
3316
+ function invalidReason(kind, value) {
3317
+ if (value !== value.trim() || value === "") {
3318
+ return "must not be empty or have leading or trailing spaces";
3319
+ }
3320
+ if (kind === "font") {
3321
+ if (!FONT_VALUE.test(value)) {
3322
+ return "allows only letters, digits, spaces, commas, hyphens, underscores and quotes";
3323
+ }
3324
+ if (countOf(value, "'") % 2 !== 0 || countOf(value, '"') % 2 !== 0) {
3325
+ return "has an unclosed quote";
3326
+ }
3327
+ return null;
3328
+ }
3329
+ const unsafe = unsafeReason(value);
3330
+ if (unsafe !== null) {
3331
+ return unsafe;
3332
+ }
3333
+ if (value.includes("'") || value.includes('"')) {
3334
+ return "must not contain quotes";
3335
+ }
3336
+ if (kind === "color") {
3337
+ const valid2 = HEX_COLOR.test(value) || COLOR_FUNCTION.test(value) || VAR_REFERENCE.test(value) || COLOR_KEYWORD.test(value);
3338
+ return valid2 ? null : "is not a color (use #hex, rgb(), hsl(), oklch() and similar, var(--name) or a keyword)";
3339
+ }
3340
+ const valid = LENGTH_VALUE.test(value) || LENGTH_FUNCTION.test(value) || VAR_REFERENCE.test(value);
3341
+ return valid ? null : "is not a length (use a number with px, rem, em, %, ch, vw or vh, 0, calc(), min(), max(), clamp() or var(--name))";
3342
+ }
3343
+ function collectVariables(themeName, mode, tokens) {
3344
+ const variables = {};
3345
+ if (tokens === void 0) {
3346
+ return ok(variables);
3347
+ }
3348
+ const label = (key) => `createTheme "${themeName}": ${mode}.${key}`;
3349
+ const knownKeys = [
3350
+ ...Object.keys(THEME_TOKEN_VARIABLES),
3351
+ "series"
3352
+ ];
3353
+ for (const key of Object.keys(tokens)) {
3354
+ if (!knownKeys.includes(key)) {
3355
+ return err(
3356
+ `${label(key)} is not a theme token. Known tokens: ${knownKeys.join(", ")}.`
3357
+ );
3358
+ }
3359
+ }
3360
+ for (const key of Object.keys(THEME_TOKEN_VARIABLES)) {
3361
+ const value = tokens[key];
3362
+ if (value === void 0) {
3363
+ continue;
3364
+ }
3365
+ if (typeof value !== "string") {
3366
+ return err(`${label(key)} must be a string, got ${typeof value}.`);
3367
+ }
3368
+ const reason = invalidReason(kindOf(key), value);
3369
+ if (reason !== null) {
3370
+ return err(`${label(key)} "${value}" ${reason}.`);
3371
+ }
3372
+ variables[THEME_TOKEN_VARIABLES[key]] = value;
3373
+ }
3374
+ const series = tokens.series;
3375
+ if (series !== void 0) {
3376
+ if (!Array.isArray(series) || series.length === 0) {
3377
+ return err(`${label("series")} must be a non-empty array of colors.`);
3378
+ }
3379
+ if (series.length > MAX_GRAPH_SERIES) {
3380
+ return err(
3381
+ `${label("series")} has ${series.length} colors; charts draw at most ${MAX_GRAPH_SERIES} series.`
3382
+ );
3383
+ }
3384
+ for (const [index, value] of series.entries()) {
3385
+ const reason = typeof value === "string" ? invalidReason("color", value) : "must be a string";
3386
+ if (reason !== null) {
3387
+ return err(
3388
+ `${label(`series[${index}]`)} "${String(value)}" ${reason}.`
3389
+ );
3390
+ }
3391
+ variables[`--dwt-series-${index + 1}`] = value;
3392
+ }
3393
+ }
3394
+ return ok(variables);
3395
+ }
3396
+ function declarations(variables) {
3397
+ return Object.entries(variables).map(([variable, value]) => ` ${variable}: ${value};`).join("\n");
3398
+ }
3399
+ function targets(prefix, scope) {
3400
+ const lead = prefix === "" ? "" : `${prefix} `;
3401
+ return `${lead}${scope} .dwt-dashboard,
3402
+ ${lead}${scope} .dwt-card`;
3403
+ }
3404
+ function rule(prefix, scope, variables) {
3405
+ return `${targets(prefix, scope)} {
3406
+ ${declarations(variables)}
3407
+ }`;
3408
+ }
3409
+ function createTheme(options) {
3410
+ const name = options.name;
3411
+ if (typeof name !== "string" || !NAME_PATTERN.test(name)) {
3412
+ return err(
3413
+ `createTheme: name "${String(name)}" must start with a lower-case letter and contain only lower-case letters, digits and hyphens.`
3414
+ );
3415
+ }
3416
+ const scope = (options.scope ?? `[data-dwt-theme='${name}']`).trim();
3417
+ if (scope === "" || !SCOPE_PATTERN.test(scope)) {
3418
+ return err(
3419
+ `createTheme "${name}": scope "${scope}" must be a selector of letters, digits and _ - [ ] = . # ' " or spaces (no commas, colons or combinators).`
3420
+ );
3421
+ }
3422
+ const modes = ["base", "light", "dark"];
3423
+ const variables = {
3424
+ base: {},
3425
+ light: {},
3426
+ dark: {}
3427
+ };
3428
+ for (const mode of modes) {
3429
+ const collected = collectVariables(
3430
+ name,
3431
+ mode,
3432
+ options[mode]
3433
+ );
3434
+ if (!collected.ok) {
3435
+ return collected;
3436
+ }
3437
+ variables[mode] = collected.value;
3438
+ }
3439
+ if (modes.every((mode) => Object.keys(variables[mode]).length === 0)) {
3440
+ return err(
3441
+ `createTheme "${name}": no tokens were set. Pass at least one value in base, light or dark.`
3442
+ );
3443
+ }
3444
+ const blocks = [];
3445
+ if (Object.keys(variables.base).length > 0) {
3446
+ blocks.push(rule("", scope, variables.base));
3447
+ }
3448
+ if (Object.keys(variables.light).length > 0) {
3449
+ blocks.push(
3450
+ rule(":root:where([data-theme='light'])", scope, variables.light)
3451
+ );
3452
+ blocks.push(
3453
+ `@media (prefers-color-scheme: light) {
3454
+ ${indent(
3455
+ rule(
3456
+ ":root:where(:not([data-theme='dark'], .dark))",
3457
+ scope,
3458
+ variables.light
3459
+ )
3460
+ )}
3461
+ }`
3462
+ );
3463
+ }
3464
+ if (Object.keys(variables.dark).length > 0) {
3465
+ blocks.push(
3466
+ rule(":root:where([data-theme='dark'], .dark)", scope, variables.dark)
3467
+ );
3468
+ blocks.push(
3469
+ `@media (prefers-color-scheme: dark) {
3470
+ ${indent(
3471
+ rule(":root:where(:not([data-theme='light']))", scope, variables.dark)
3472
+ )}
3473
+ }`
3474
+ );
3475
+ }
3476
+ return ok({
3477
+ name,
3478
+ scope,
3479
+ css: `${blocks.join("\n\n")}
3480
+ `,
3481
+ variables
3482
+ });
3483
+ }
3484
+ function indent(block) {
3485
+ return block.split("\n").map((line) => ` ${line}`).join("\n");
3486
+ }
3487
+
3239
3488
  exports.DATE_RANGE_PRESETS = DATE_RANGE_PRESETS;
3240
3489
  exports.DEFAULT_COMPARISON_LABEL = DEFAULT_COMPARISON_LABEL;
3241
3490
  exports.DEFAULT_CURRENCY = DEFAULT_CURRENCY;
@@ -3258,6 +3507,7 @@ exports.MAX_WIDGET_OPTIONS = MAX_WIDGET_OPTIONS;
3258
3507
  exports.MAX_WIDGET_WIDTH = MAX_WIDGET_WIDTH;
3259
3508
  exports.MIN_WIDGET_WIDTH = MIN_WIDGET_WIDTH;
3260
3509
  exports.OPTION_KEY_PATTERN = OPTION_KEY_PATTERN;
3510
+ exports.THEME_TOKEN_VARIABLES = THEME_TOKEN_VARIABLES;
3261
3511
  exports.VALUE_FORMATS = VALUE_FORMATS;
3262
3512
  exports.WIDGET_FILLS = WIDGET_FILLS;
3263
3513
  exports.WIDGET_KINDS = WIDGET_KINDS;
@@ -3271,6 +3521,7 @@ exports.baseColumnSpan = baseColumnSpan;
3271
3521
  exports.cacheKeyFor = cacheKeyFor;
3272
3522
  exports.coerceOptionValue = coerceOptionValue;
3273
3523
  exports.createLayoutPersistence = createLayoutPersistence;
3524
+ exports.createTheme = createTheme;
3274
3525
  exports.createWidgetLoader = createWidgetLoader;
3275
3526
  exports.defaultDetailQuery = defaultDetailQuery;
3276
3527
  exports.defaultOptionValue = defaultOptionValue;