@multiplatform.one/theme 9.0.2 → 9.0.4
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/cjs/theme/createThemes.cjs +151 -106
- package/dist/cjs/theme/createThemes.native.js +256 -197
- package/dist/cjs/theme/createThemes.native.js.map +1 -1
- package/dist/esm/theme/createThemes.mjs +151 -106
- package/dist/esm/theme/createThemes.mjs.map +1 -1
- package/dist/esm/theme/createThemes.native.js +256 -197
- package/dist/esm/theme/createThemes.native.js.map +1 -1
- package/package.json +5 -5
- package/src/theme/createThemes.ts +229 -135
- package/types/theme/createThemes.d.ts.map +1 -1
|
@@ -312,99 +312,149 @@ function holdSelectionSurface(themes) {
|
|
|
312
312
|
}
|
|
313
313
|
return out;
|
|
314
314
|
}
|
|
315
|
+
function ownerScope(scopes, name) {
|
|
316
|
+
return scopes.filter(scope => name === scope || name.startsWith(`${scope}_`)).reduce((longest, scope) => scope.length > longest.length ? scope : longest, "");
|
|
317
|
+
}
|
|
318
|
+
function clearsDarkGrounds(color, grounds) {
|
|
319
|
+
const brightest = Math.max(...grounds);
|
|
320
|
+
const luminance = luminanceOf(color);
|
|
321
|
+
return luminance !== null && luminance > brightest && grounds.every(ground => (0, import_colorRules.contrastRatio)(luminance, ground) >= import_builderOptions.darkHairlineContrast);
|
|
322
|
+
}
|
|
323
|
+
function darkHairlineOf(scope) {
|
|
324
|
+
const ramp = Array.from({
|
|
325
|
+
length: 12
|
|
326
|
+
}, (_, index) => scope[`color${index + 1}`]);
|
|
327
|
+
const grounds = [scope.background, scope.color4].map(color => luminanceOf(color));
|
|
328
|
+
if (grounds.some(ground => ground === null)) return void 0;
|
|
329
|
+
const hairline = ramp.findIndex((color, index) => index < 11 && clearsDarkGrounds(color, grounds));
|
|
330
|
+
return hairline < 0 ? void 0 : {
|
|
331
|
+
ramp,
|
|
332
|
+
hairline
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
const darkEdgeStateKeys = ["borderColor", "borderColorHover", "borderColorFocus", "borderColorPress"];
|
|
336
|
+
function darkEdgesOf(name, theme, scopeName, scope, {
|
|
337
|
+
ramp,
|
|
338
|
+
hairline
|
|
339
|
+
}) {
|
|
340
|
+
const medium = hairline + 1;
|
|
341
|
+
const surface = name === scopeName || theme.background === scope.color4;
|
|
342
|
+
const step = ramp.indexOf(theme.borderColor);
|
|
343
|
+
const shift = surface && step >= 0 ? medium - step : 0;
|
|
344
|
+
const moved = {};
|
|
345
|
+
if ("borderColorHairline" in theme || name === scopeName) moved.borderColorHairline = ramp[hairline];
|
|
346
|
+
if (shift <= 0) return moved;
|
|
347
|
+
for (const key of darkEdgeStateKeys) {
|
|
348
|
+
const at = ramp.indexOf(theme[key]);
|
|
349
|
+
if (at >= 0) moved[key] = ramp[Math.min(11, at + shift)];
|
|
350
|
+
}
|
|
351
|
+
return moved;
|
|
352
|
+
}
|
|
315
353
|
function holdDarkEdges(themes) {
|
|
316
354
|
const out = {
|
|
317
355
|
...themes
|
|
318
356
|
};
|
|
319
357
|
const scopes = Object.keys(themes).filter(name => /^dark(_[a-z]+)?$/.test(name));
|
|
320
|
-
const ownerOf = name => scopes.filter(scope => name === scope || name.startsWith(`${scope}_`)).reduce((longest, scope) => scope.length > longest.length ? scope : longest, "");
|
|
321
358
|
for (const scopeName of scopes) {
|
|
322
359
|
if (/_(accent|active)$/.test(scopeName)) continue;
|
|
323
360
|
const scope = themes[scopeName];
|
|
324
|
-
const
|
|
325
|
-
|
|
326
|
-
}, (_, index) => scope[`color${index + 1}`]);
|
|
327
|
-
const grounds = [scope.background, scope.color4].map(color => luminanceOf(color));
|
|
328
|
-
if (grounds.some(ground => ground === null)) continue;
|
|
329
|
-
const brightest = Math.max(...grounds);
|
|
330
|
-
const clears = color => {
|
|
331
|
-
const luminance = luminanceOf(color);
|
|
332
|
-
return luminance !== null && luminance > brightest && grounds.every(ground => (0, import_colorRules.contrastRatio)(luminance, ground) >= import_builderOptions.darkHairlineContrast);
|
|
333
|
-
};
|
|
334
|
-
const hairline = ramp.findIndex((color, index) => index < 11 && clears(color));
|
|
335
|
-
if (hairline < 0) continue;
|
|
336
|
-
const medium = hairline + 1;
|
|
361
|
+
const pick = darkHairlineOf(scope);
|
|
362
|
+
if (!pick) continue;
|
|
337
363
|
for (const [name, theme] of Object.entries(themes)) {
|
|
338
|
-
if (
|
|
339
|
-
const surface = name === scopeName || theme.background === scope.color4;
|
|
340
|
-
const step = ramp.indexOf(theme.borderColor);
|
|
341
|
-
const shift = surface && step >= 0 ? medium - step : 0;
|
|
342
|
-
const moved = {};
|
|
343
|
-
if ("borderColorHairline" in theme || name === scopeName) moved.borderColorHairline = ramp[hairline];
|
|
344
|
-
if (shift > 0) {
|
|
345
|
-
for (const key of ["borderColor", "borderColorHover", "borderColorFocus", "borderColorPress"]) {
|
|
346
|
-
const at = ramp.indexOf(theme[key]);
|
|
347
|
-
if (at >= 0) moved[key] = ramp[Math.min(11, at + shift)];
|
|
348
|
-
}
|
|
349
|
-
}
|
|
364
|
+
if (ownerScope(scopes, name) !== scopeName) continue;
|
|
350
365
|
out[name] = {
|
|
351
366
|
...out[name],
|
|
352
|
-
...
|
|
367
|
+
...darkEdgesOf(name, theme, scopeName, scope, pick)
|
|
353
368
|
};
|
|
354
369
|
}
|
|
355
370
|
}
|
|
356
371
|
return out;
|
|
357
372
|
}
|
|
373
|
+
function firstClearing(neutral, grounds) {
|
|
374
|
+
const lums = grounds.map(ground => luminanceOf(ground));
|
|
375
|
+
if (lums.some(lum => lum === null)) return -1;
|
|
376
|
+
const darkest = Math.min(...lums);
|
|
377
|
+
const brightest = Math.max(...lums);
|
|
378
|
+
const light = (0, import_colorRules.isLightGround)(grounds[0]);
|
|
379
|
+
return neutral.findIndex(color => {
|
|
380
|
+
const lum = luminanceOf(color);
|
|
381
|
+
return lum !== null && (light ? lum < darkest : lum > brightest) && lums.every(ground => (0, import_colorRules.contrastRatio)(lum, ground) >= import_colorRules.minContrastRatio);
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
function inheritedValue(out, themes, name, key) {
|
|
385
|
+
for (let at = name; at; at = at.slice(0, Math.max(0, at.lastIndexOf("_")))) {
|
|
386
|
+
const value = (out[at] ?? themes[at])?.[key];
|
|
387
|
+
if (value !== void 0) return value;
|
|
388
|
+
if (!at.includes("_")) break;
|
|
389
|
+
}
|
|
390
|
+
return void 0;
|
|
391
|
+
}
|
|
392
|
+
function nearestStep(ramp, color) {
|
|
393
|
+
const lum = luminanceOf(color);
|
|
394
|
+
if (lum === null) return -1;
|
|
395
|
+
return ramp.reduce((best, step, index) => Math.abs((luminanceOf(step) ?? 2) - lum) < Math.abs((luminanceOf(ramp[best]) ?? 2) - lum) ? index : best, 0);
|
|
396
|
+
}
|
|
397
|
+
function fieldEdgesOf(themes, out, neutral, scopeName) {
|
|
398
|
+
const scope = themes[scopeName];
|
|
399
|
+
const field = `${scopeName}_Input`;
|
|
400
|
+
const edge = firstClearing(neutral, [scope.background, inheritedValue(out, themes, field, "background")]);
|
|
401
|
+
if (edge < 0) return void 0;
|
|
402
|
+
const ramp = Array.from({
|
|
403
|
+
length: 12
|
|
404
|
+
}, (_, index) => scope[`color${index + 1}`]);
|
|
405
|
+
const rest = nearestStep(ramp, inheritedValue(out, themes, field, "borderColor"));
|
|
406
|
+
const hover = nearestStep(ramp, inheritedValue(out, themes, field, "borderColorHover"));
|
|
407
|
+
return {
|
|
408
|
+
borderColorField: neutral[edge],
|
|
409
|
+
borderColorFieldHover: neutral[Math.min(11, Math.max(edge, edge + hover - rest))]
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
function intentFieldEdges(themes, out, scopeName, pages) {
|
|
413
|
+
const scope = themes[scopeName];
|
|
414
|
+
const field = `${scopeName}_Input`;
|
|
415
|
+
const ramp = Array.from({
|
|
416
|
+
length: 12
|
|
417
|
+
}, (_, index) => scope[`color${index + 1}`]);
|
|
418
|
+
const edge = firstClearing(ramp, [...pages, inheritedValue(out, themes, field, "background")]);
|
|
419
|
+
if (edge < 0) return {
|
|
420
|
+
borderColorField: inheritedValue(out, themes, field, "borderColor"),
|
|
421
|
+
borderColorFieldHover: inheritedValue(out, themes, field, "borderColorHover")
|
|
422
|
+
};
|
|
423
|
+
return {
|
|
424
|
+
borderColorField: ramp[edge],
|
|
425
|
+
borderColorFieldHover: ramp[Math.min(11, edge + 1)]
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
function controlEdgePatch(themes, out, scopeName, {
|
|
429
|
+
scheme,
|
|
430
|
+
neutral,
|
|
431
|
+
pages,
|
|
432
|
+
rootFields,
|
|
433
|
+
rootControl
|
|
434
|
+
}) {
|
|
435
|
+
const scope = themes[scopeName];
|
|
436
|
+
const patch = {};
|
|
437
|
+
if (scheme === "dark" && "switchTrackOff" in scope) patch.switchTrackOffBorder = scope.switchTrackOff;
|
|
438
|
+
if (semanticIntentNames.has(scopeName.slice(scheme.length + 1))) return Object.assign(patch, intentFieldEdges(themes, out, scopeName, pages));
|
|
439
|
+
const fields = fieldEdgesOf(themes, out, neutral, scopeName);
|
|
440
|
+
if (fields) {
|
|
441
|
+
for (const [key, value] of Object.entries(fields)) if (value !== rootFields[key]) patch[key] = value;
|
|
442
|
+
}
|
|
443
|
+
const control = firstClearing(neutral, [scope.background]);
|
|
444
|
+
if (control >= 0 && control !== rootControl) patch.borderColorControl = neutral[control];
|
|
445
|
+
return patch;
|
|
446
|
+
}
|
|
358
447
|
function holdControlEdges(themes) {
|
|
359
448
|
const out = {
|
|
360
449
|
...themes
|
|
361
450
|
};
|
|
362
|
-
const firstClearing = (neutral, grounds) => {
|
|
363
|
-
const lums = grounds.map(ground => luminanceOf(ground));
|
|
364
|
-
if (lums.some(lum => lum === null)) return -1;
|
|
365
|
-
const darkest = Math.min(...lums);
|
|
366
|
-
const brightest = Math.max(...lums);
|
|
367
|
-
const light = (0, import_colorRules.isLightGround)(grounds[0]);
|
|
368
|
-
return neutral.findIndex(color => {
|
|
369
|
-
const lum = luminanceOf(color);
|
|
370
|
-
return lum !== null && (light ? lum < darkest : lum > brightest) && lums.every(ground => (0, import_colorRules.contrastRatio)(lum, ground) >= import_colorRules.minContrastRatio);
|
|
371
|
-
});
|
|
372
|
-
};
|
|
373
|
-
const resolved = (name, key) => {
|
|
374
|
-
for (let at = name; at; at = at.slice(0, Math.max(0, at.lastIndexOf("_")))) {
|
|
375
|
-
const value = (out[at] ?? themes[at])?.[key];
|
|
376
|
-
if (value !== void 0) return value;
|
|
377
|
-
if (!at.includes("_")) break;
|
|
378
|
-
}
|
|
379
|
-
return void 0;
|
|
380
|
-
};
|
|
381
451
|
for (const scheme of ["light", "dark"]) {
|
|
382
452
|
const root = themes[scheme];
|
|
383
453
|
if (!root) continue;
|
|
384
454
|
const neutral = Array.from({
|
|
385
455
|
length: 12
|
|
386
456
|
}, (_, index) => root[`color${index + 1}`]);
|
|
387
|
-
const
|
|
388
|
-
const scope = themes[scopeName];
|
|
389
|
-
const field = `${scopeName}_Input`;
|
|
390
|
-
const edge = firstClearing(neutral, [scope.background, resolved(field, "background")]);
|
|
391
|
-
if (edge < 0) return void 0;
|
|
392
|
-
const ramp = Array.from({
|
|
393
|
-
length: 12
|
|
394
|
-
}, (_, index) => scope[`color${index + 1}`]);
|
|
395
|
-
const stepOf = color => {
|
|
396
|
-
const lum = luminanceOf(color);
|
|
397
|
-
if (lum === null) return -1;
|
|
398
|
-
return ramp.reduce((best, step, index) => Math.abs((luminanceOf(step) ?? 2) - lum) < Math.abs((luminanceOf(ramp[best]) ?? 2) - lum) ? index : best, 0);
|
|
399
|
-
};
|
|
400
|
-
const rest = stepOf(resolved(field, "borderColor"));
|
|
401
|
-
const hover = stepOf(resolved(field, "borderColorHover"));
|
|
402
|
-
return {
|
|
403
|
-
borderColorField: neutral[edge],
|
|
404
|
-
borderColorFieldHover: neutral[Math.min(11, Math.max(edge, edge + hover - rest))]
|
|
405
|
-
};
|
|
406
|
-
};
|
|
407
|
-
const rootFields = fieldEdges(scheme);
|
|
457
|
+
const rootFields = fieldEdgesOf(themes, out, neutral, scheme);
|
|
408
458
|
const rootControl = firstClearing(neutral, [root.background]);
|
|
409
459
|
if (!rootFields || rootControl < 0) continue;
|
|
410
460
|
out[scheme] = {
|
|
@@ -415,26 +465,15 @@ function holdControlEdges(themes) {
|
|
|
415
465
|
};
|
|
416
466
|
const scopes = Object.keys(themes).filter(name => new RegExp(`^${scheme}_[a-z]+$`).test(name) && !/_(accent|active)$/.test(name));
|
|
417
467
|
const pages = [root.background, ...scopes.filter(name => !semanticIntentNames.has(name.slice(scheme.length + 1))).map(name => themes[name].background)];
|
|
468
|
+
const context = {
|
|
469
|
+
scheme,
|
|
470
|
+
neutral,
|
|
471
|
+
pages,
|
|
472
|
+
rootFields,
|
|
473
|
+
rootControl
|
|
474
|
+
};
|
|
418
475
|
for (const scopeName of scopes) {
|
|
419
|
-
const
|
|
420
|
-
const patch = {};
|
|
421
|
-
if (scheme === "dark" && "switchTrackOff" in scope) patch.switchTrackOffBorder = scope.switchTrackOff;
|
|
422
|
-
if (semanticIntentNames.has(scopeName.slice(scheme.length + 1))) {
|
|
423
|
-
const field = `${scopeName}_Input`;
|
|
424
|
-
const ramp = Array.from({
|
|
425
|
-
length: 12
|
|
426
|
-
}, (_, index) => scope[`color${index + 1}`]);
|
|
427
|
-
const edge = firstClearing(ramp, [...pages, resolved(field, "background")]);
|
|
428
|
-
patch.borderColorField = edge < 0 ? resolved(field, "borderColor") : ramp[edge];
|
|
429
|
-
patch.borderColorFieldHover = edge < 0 ? resolved(field, "borderColorHover") : ramp[Math.min(11, edge + 1)];
|
|
430
|
-
} else {
|
|
431
|
-
const fields = fieldEdges(scopeName);
|
|
432
|
-
if (fields) {
|
|
433
|
-
for (const [key, value] of Object.entries(fields)) if (value !== rootFields[key]) patch[key] = value;
|
|
434
|
-
}
|
|
435
|
-
const control = firstClearing(neutral, [scope.background]);
|
|
436
|
-
if (control >= 0 && control !== rootControl) patch.borderColorControl = neutral[control];
|
|
437
|
-
}
|
|
476
|
+
const patch = controlEdgePatch(themes, out, scopeName, context);
|
|
438
477
|
if (Object.keys(patch).length > 0) out[scopeName] = {
|
|
439
478
|
...out[scopeName],
|
|
440
479
|
...patch
|
|
@@ -527,11 +566,7 @@ function holdSwitchTrack(themes) {
|
|
|
527
566
|
if (scheme !== "dark" || !paint) continue;
|
|
528
567
|
for (const intent of semanticIntentNames) {
|
|
529
568
|
const scope = themes[`${scheme}_${intent}`];
|
|
530
|
-
const
|
|
531
|
-
if (!scope?.color4 || !scope.color5 || off === null) continue;
|
|
532
|
-
const holds = (0, import_colorRules.inSwitchOffBand)(paint.switchTrackOff, scope.color4) && [scope.color4, scope.color5].every(ground => (0, import_colorRules.contrastRatio)(off, luminanceOf(ground) ?? off) >= import_colorRules.plateContrastRatio);
|
|
533
|
-
if (holds) continue;
|
|
534
|
-
const track = switchTrackPaint(base, scope.accentBackground ?? base.accentBackground, scope.color4, [base.color4, scope.color4, scope.color5]);
|
|
569
|
+
const track = intentSwitchTrack(base, scope, paint);
|
|
535
570
|
if (track) out[`${scheme}_${intent}`] = {
|
|
536
571
|
...scope,
|
|
537
572
|
...track
|
|
@@ -540,6 +575,13 @@ function holdSwitchTrack(themes) {
|
|
|
540
575
|
}
|
|
541
576
|
return out;
|
|
542
577
|
}
|
|
578
|
+
function intentSwitchTrack(base, scope, paint) {
|
|
579
|
+
const off = luminanceOf(paint.switchTrackOff);
|
|
580
|
+
if (!scope?.color4 || !scope.color5 || off === null) return void 0;
|
|
581
|
+
const holds = (0, import_colorRules.inSwitchOffBand)(paint.switchTrackOff, scope.color4) && [scope.color4, scope.color5].every(ground => (0, import_colorRules.contrastRatio)(off, luminanceOf(ground) ?? off) >= import_colorRules.plateContrastRatio);
|
|
582
|
+
if (holds) return void 0;
|
|
583
|
+
return switchTrackPaint(base, scope.accentBackground ?? base.accentBackground, scope.color4, [base.color4, scope.color4, scope.color5]);
|
|
584
|
+
}
|
|
543
585
|
function switchTrackPaint(base, onFill, ground, raised = []) {
|
|
544
586
|
const on = luminanceOf(onFill);
|
|
545
587
|
if (!ground || on === null) return void 0;
|
|
@@ -837,26 +879,29 @@ function holdHouseIdentityInk(themes) {
|
|
|
837
879
|
const base = themes[scheme];
|
|
838
880
|
const blueInk = themes[`${scheme}_blue`]?.identityInk;
|
|
839
881
|
if (!base?.identityInk || !blueInk || base.identityInk === blueInk) continue;
|
|
840
|
-
|
|
841
|
-
length: 12
|
|
842
|
-
}, (_, index) => index + 1).every(step => base[`accent${step}`] !== void 0 && base[`accent${step}`] === base[`blue${step}`]);
|
|
843
|
-
if (!held) continue;
|
|
882
|
+
if (!accentIsBlue(base)) continue;
|
|
844
883
|
for (const [name, theme] of Object.entries(themes)) {
|
|
845
|
-
if (name
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
const values = (0, import_dedupeInherited.resolveThemeThroughParents)(themes, name);
|
|
850
|
-
if ((0, import_accentInk.pickIdentityInk)([blueInk], (0, import_accentInk.pageSurfaces)(name, values)) === blueInk) {
|
|
851
|
-
out[name] = {
|
|
852
|
-
...theme,
|
|
853
|
-
identityInk: blueInk
|
|
854
|
-
};
|
|
855
|
-
}
|
|
884
|
+
if (takesBlueInk(themes, scheme, name, theme, base, blueInk)) out[name] = {
|
|
885
|
+
...theme,
|
|
886
|
+
identityInk: blueInk
|
|
887
|
+
};
|
|
856
888
|
}
|
|
857
889
|
}
|
|
858
890
|
return out;
|
|
859
891
|
}
|
|
892
|
+
function accentIsBlue(base) {
|
|
893
|
+
return Array.from({
|
|
894
|
+
length: 12
|
|
895
|
+
}, (_, index) => index + 1).every(step => base[`accent${step}`] !== void 0 && base[`accent${step}`] === base[`blue${step}`]);
|
|
896
|
+
}
|
|
897
|
+
function takesBlueInk(themes, scheme, name, theme, base, blueInk) {
|
|
898
|
+
if (name !== scheme && !name.startsWith(`${scheme}_`)) return false;
|
|
899
|
+
const scope = name.split("_")[1];
|
|
900
|
+
if (scope === "accent" || childrenHues.includes(scope)) return false;
|
|
901
|
+
if (theme.identityInk !== base.identityInk) return false;
|
|
902
|
+
const values = (0, import_dedupeInherited.resolveThemeThroughParents)(themes, name);
|
|
903
|
+
return (0, import_accentInk.pickIdentityInk)([blueInk], (0, import_accentInk.pageSurfaces)(name, values)) === blueInk;
|
|
904
|
+
}
|
|
860
905
|
const defaultLightShadows = {
|
|
861
906
|
shadow1: "rgba(0,0,0,0.04)",
|
|
862
907
|
shadow2: "rgba(0,0,0,0.08)",
|