@webmate-studio/builder 0.2.149 → 0.2.152

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": "@webmate-studio/builder",
3
- "version": "0.2.149",
3
+ "version": "0.2.152",
4
4
  "type": "module",
5
5
  "description": "Webmate Studio Component Builder",
6
6
  "keywords": [
@@ -43,7 +43,7 @@
43
43
  "access": "public"
44
44
  },
45
45
  "dependencies": {
46
- "@webmate-studio/builder": "^0.2.132",
46
+ "@webmate-studio/builder": "^0.2.150",
47
47
  "@webmate-studio/core": "^0.2.7",
48
48
  "@webmate-studio/parser": "^0.2.8",
49
49
  "dom-serializer": "^2.0.0",
@@ -296,6 +296,8 @@ function generateTypographyVariables(t, lines, responsiveLines) {
296
296
  const textStyles = t.typography.textStyles;
297
297
  if (!textStyles) return;
298
298
 
299
+ const fluidConfig = t.typography.fluidConfig;
300
+
299
301
  for (const voice of TEXT_VOICES) {
300
302
  if (!textStyles[voice]) continue;
301
303
 
@@ -314,38 +316,94 @@ function generateTypographyVariables(t, lines, responsiveLines) {
314
316
  lines.push(` ${prefix}-font: "${style.font}", ${fallback};`);
315
317
  }
316
318
 
317
- // fontWeight, letterSpacing, textTransform — nicht responsive
318
319
  if (style.fontWeight != null) lines.push(` ${prefix}-weight: ${style.fontWeight};`);
319
320
  if (style.textTransform) lines.push(` ${prefix}-transform: ${style.textTransform};`);
320
321
 
321
- // fontSize — kann responsive sein
322
- addResponsiveVar(prefix + '-size', style.fontSize, lines, responsiveLines);
322
+ // fontSize — fluid clamp() oder statisch
323
+ addFluidVar(prefix + '-size', style.fontSize, lines, fluidConfig);
323
324
 
324
- // lineHeight — kann responsive sein
325
- addResponsiveVar(prefix + '-line-height', style.lineHeight, lines, responsiveLines);
325
+ // lineHeight — fluid clamp() oder statisch
326
+ addFluidVar(prefix + '-line-height', style.lineHeight, lines, fluidConfig);
326
327
 
327
- // letterSpacing — kann responsive sein
328
- addResponsiveVar(prefix + '-letter-spacing', style.letterSpacing, lines, responsiveLines);
328
+ // letterSpacing — fluid clamp() oder statisch
329
+ addFluidVar(prefix + '-letter-spacing', style.letterSpacing, lines, fluidConfig);
329
330
  }
330
331
  }
331
332
  }
332
333
 
333
- function addResponsiveVar(varName, value, lines, responsiveLines) {
334
+ /**
335
+ * Generates a CSS variable with optional clamp() for fluid values.
336
+ * Static string → plain value. Fluid { min, max } → clamp().
337
+ * Legacy { base, md, lg } → treated as { min: base, max: lg||md } for backward compat.
338
+ */
339
+ function addFluidVar(varName, value, lines, fluidConfig) {
334
340
  if (value == null) return;
335
341
 
336
342
  if (typeof value === 'object') {
337
- if (value.base) lines.push(` ${varName}: ${value.base};`);
338
- for (const [bp, bpValue] of Object.entries(value)) {
339
- if (bp !== 'base' && bpValue) {
340
- responsiveLines[bp] = responsiveLines[bp] || [];
341
- responsiveLines[bp].push(` ${varName}: ${bpValue};`);
342
- }
343
+ let min, max;
344
+ if ('min' in value) {
345
+ min = value.min;
346
+ max = value.max || value.min;
347
+ } else if ('base' in value) {
348
+ // Legacy backward compatibility
349
+ min = value.base;
350
+ max = value.lg || value.md || value.base;
351
+ } else {
352
+ return;
353
+ }
354
+
355
+ if (min === max || !max) {
356
+ lines.push(` ${varName}: ${min};`);
357
+ } else {
358
+ const clampValue = buildClampValue(min, max, fluidConfig);
359
+ lines.push(` ${varName}: ${clampValue};`);
343
360
  }
344
361
  } else {
345
362
  lines.push(` ${varName}: ${value};`);
346
363
  }
347
364
  }
348
365
 
366
+ /**
367
+ * Builds a CSS clamp() expression (Utopia formula).
368
+ * clamp(min, intercept + slope·vw, max)
369
+ * slope = (max - min) / (maxVP - minVP)
370
+ * intercept = min - slope * minVP
371
+ */
372
+ function buildClampValue(min, max, fluidConfig) {
373
+ const minVP = parseFloat(fluidConfig?.minViewport || '20');
374
+ const maxVP = parseFloat(fluidConfig?.maxViewport || '90');
375
+
376
+ const minNum = parseFloat(min);
377
+ const maxNum = parseFloat(max);
378
+ const unit = min.replace(/[\d.\-]/g, '') || 'rem';
379
+
380
+ // Unitless values (like lineHeight '1.5') can't use vw-based clamp
381
+ if (!unit || unit === '') {
382
+ return min;
383
+ }
384
+
385
+ if (isNaN(minNum) || isNaN(maxNum) || minVP >= maxVP) {
386
+ return min;
387
+ }
388
+
389
+ const slope = (maxNum - minNum) / (maxVP - minVP);
390
+ const intercept = minNum - slope * minVP;
391
+
392
+ const slopeVw = +(slope * 100).toFixed(3);
393
+ const interceptUnit = +intercept.toFixed(3);
394
+
395
+ let preferred;
396
+ if (interceptUnit === 0) {
397
+ preferred = `${slopeVw}vw`;
398
+ } else if (interceptUnit > 0) {
399
+ preferred = `${interceptUnit}${unit} + ${slopeVw}vw`;
400
+ } else {
401
+ preferred = `${interceptUnit}${unit} + ${slopeVw}vw`;
402
+ }
403
+
404
+ return `clamp(${min}, ${preferred}, ${max})`;
405
+ }
406
+
349
407
  function findFont(t, fontName) {
350
408
  if (!t.typography?.fonts) return null;
351
409
  return t.typography.fonts.find(f => f.name === fontName);
@@ -22,6 +22,27 @@ import {
22
22
  } from './design-tokens-v2.js';
23
23
 
24
24
 
25
+ // ─── Fluid Typography Migration ──────────────────────────────────────────────
26
+
27
+ /**
28
+ * Migriert einen responsiven Wert von Breakpoint-Format zu Fluid-Format.
29
+ * { base: '2.5rem', md: '3.5rem', lg: '4.5rem' } → { min: '2.5rem', max: '4.5rem' }
30
+ * Bereits im Fluid-Format ({ min, max }) → unverändert.
31
+ * Statische Werte (Strings) → unverändert.
32
+ */
33
+ export function migrateResponsiveToFluid(value) {
34
+ if (value == null || typeof value !== 'object') return value;
35
+ if ('min' in value) return value; // Already fluid
36
+ if ('base' in value) {
37
+ const min = value.base;
38
+ const max = value.lg || value.md || value.base;
39
+ if (min === max) return min; // Collapse to static
40
+ return { min, max };
41
+ }
42
+ return value;
43
+ }
44
+
45
+
25
46
  // ─── Hauptfunktionen ────────────────────────────────────────────────────────
26
47
 
27
48
  /**
@@ -112,8 +133,30 @@ export function validateDesignTokensV2(tokens) {
112
133
  }
113
134
  }
114
135
 
136
+ // Migrate breakpoint-based responsive values to fluid { min, max }
137
+ for (const voice of TEXT_VOICES) {
138
+ for (const level of TEXT_LEVELS) {
139
+ const style = v2.typography.textStyles[voice]?.[level];
140
+ if (style) {
141
+ style.fontSize = migrateResponsiveToFluid(style.fontSize);
142
+ style.lineHeight = migrateResponsiveToFluid(style.lineHeight);
143
+ style.letterSpacing = migrateResponsiveToFluid(style.letterSpacing);
144
+ }
145
+ }
146
+ }
147
+
148
+ // Ensure fluidConfig exists
149
+ if (!v2.typography.fluidConfig) {
150
+ v2.typography.fluidConfig = { minViewport: '20rem', maxViewport: '90rem' };
151
+ }
152
+
115
153
  // Aliases
116
154
  if (!v2.typography.aliases) v2.typography.aliases = { ...DEFAULT_TEXT_ALIASES };
155
+ // Migrate fine → lead
156
+ if (v2.typography.aliases.fine && !v2.typography.aliases.lead) {
157
+ v2.typography.aliases.lead = 'body-1';
158
+ delete v2.typography.aliases.fine;
159
+ }
117
160
 
118
161
  // ── Buttons validieren ──
119
162
  if (!v2.buttons) v2.buttons = structuredClone(defaultDesignTokensV2.buttons);
@@ -663,7 +663,7 @@ export const DEFAULT_TEXT_ALIASES = {
663
663
  label: 'ui-3',
664
664
  caption: 'body-4',
665
665
  overline: 'accent-4',
666
- fine: 'body-5'
666
+ lead: 'body-1'
667
667
  };
668
668
 
669
669
 
@@ -748,6 +748,10 @@ export const defaultDesignTokensV2 = {
748
748
 
749
749
  // ── Typografie ──
750
750
  typography: {
751
+ fluidConfig: {
752
+ minViewport: '20rem', // 320px
753
+ maxViewport: '90rem' // 1440px
754
+ },
751
755
  fonts: [
752
756
  {
753
757
  name: 'Inter',
@@ -766,15 +770,15 @@ export const defaultDesignTokensV2 = {
766
770
  display: {
767
771
  1: {
768
772
  font: 'Inter',
769
- fontSize: { base: '2.5rem', md: '3.5rem', lg: '4.5rem' },
773
+ fontSize: { min: '2.5rem', max: '4.5rem' },
770
774
  fontWeight: 700,
771
- lineHeight: { base: '1.15', md: '1.1' },
775
+ lineHeight: '1.1',
772
776
  letterSpacing: '-0.02em',
773
777
  textTransform: 'none'
774
778
  },
775
779
  2: {
776
780
  font: 'Inter',
777
- fontSize: { base: '2rem', md: '2.5rem', lg: '3rem' },
781
+ fontSize: { min: '2rem', max: '3rem' },
778
782
  fontWeight: 700,
779
783
  lineHeight: '1.15',
780
784
  letterSpacing: '-0.015em',
@@ -782,7 +786,7 @@ export const defaultDesignTokensV2 = {
782
786
  },
783
787
  3: {
784
788
  font: 'Inter',
785
- fontSize: { base: '1.5rem', md: '1.75rem', lg: '2rem' },
789
+ fontSize: { min: '1.5rem', max: '2rem' },
786
790
  fontWeight: 600,
787
791
  lineHeight: '1.2',
788
792
  letterSpacing: '-0.01em',
@@ -790,7 +794,7 @@ export const defaultDesignTokensV2 = {
790
794
  },
791
795
  4: {
792
796
  font: 'Inter',
793
- fontSize: { base: '1.25rem', md: '1.5rem' },
797
+ fontSize: { min: '1.25rem', max: '1.5rem' },
794
798
  fontWeight: 600,
795
799
  lineHeight: '1.3',
796
800
  letterSpacing: '-0.005em',
@@ -798,7 +802,7 @@ export const defaultDesignTokensV2 = {
798
802
  },
799
803
  5: {
800
804
  font: 'Inter',
801
- fontSize: { base: '1.125rem', md: '1.25rem' },
805
+ fontSize: { min: '1.125rem', max: '1.25rem' },
802
806
  fontWeight: 600,
803
807
  lineHeight: '1.3',
804
808
  letterSpacing: '0em',
@@ -808,7 +812,7 @@ export const defaultDesignTokensV2 = {
808
812
  body: {
809
813
  1: {
810
814
  font: 'Inter',
811
- fontSize: { base: '1.125rem', md: '1.25rem' },
815
+ fontSize: { min: '1.125rem', max: '1.25rem' },
812
816
  fontWeight: 400,
813
817
  lineHeight: '1.6',
814
818
  letterSpacing: '0em',
@@ -892,7 +896,7 @@ export const defaultDesignTokensV2 = {
892
896
  accent: {
893
897
  1: {
894
898
  font: 'Inter',
895
- fontSize: { base: '1.5rem', md: '1.75rem', lg: '2rem' },
899
+ fontSize: { min: '1.5rem', max: '2rem' },
896
900
  fontWeight: 400,
897
901
  lineHeight: '1.5',
898
902
  letterSpacing: '0em',
@@ -900,7 +904,7 @@ export const defaultDesignTokensV2 = {
900
904
  },
901
905
  2: {
902
906
  font: 'Inter',
903
- fontSize: { base: '1.25rem', md: '1.5rem' },
907
+ fontSize: { min: '1.25rem', max: '1.5rem' },
904
908
  fontWeight: 400,
905
909
  lineHeight: '1.4',
906
910
  letterSpacing: '0em',
package/src/index.js CHANGED
@@ -11,7 +11,7 @@ import { defaultDesignTokens, generateTailwindV4Theme, generateTailwindConfig, g
11
11
  // V2 Design Tokens
12
12
  import { defaultDesignTokensV2, generateColorScale, generateDarkColorScale, calculateOnColor, isV1Format, isV2Format, COLOR_WORLDS, SEMANTIC_COLOR_WORLDS, TEXT_VOICES, TEXT_LEVELS, BUTTON_VARIANTS, BUTTON_SIZES, DEFAULT_SEMANTIC_MAPPINGS, DEFAULT_STATUS_SEMANTIC_MAPPINGS, getDefaultMappingsForWorld, DEFAULT_TEXT_ALIASES } from './design-tokens-v2.js';
13
13
  import { generateTailwindV4ThemeV2, generateCSSFromTokensV2, generateFontImportsV2 } from './design-tokens-v2-css.js';
14
- import { migrateDesignTokensV1toV2, validateDesignTokensV2 } from './design-tokens-v2-migrate.js';
14
+ import { migrateDesignTokensV1toV2, validateDesignTokensV2, migrateResponsiveToFluid } from './design-tokens-v2-migrate.js';
15
15
 
16
16
  import { readFileSync } from 'fs';
17
17
  import { fileURLToPath } from 'url';
@@ -29,4 +29,4 @@ function getMotionRuntime() {
29
29
  export { build, generateComponentCSS, generateTailwindCSS, extractTailwindClasses, cleanComponentHTML, bundleIsland, bundleComponentIslands, deduplicateCSS, markdownToHtml, processMarkdownProps, SafeHtml, getMotionRuntime, TemplateProcessor, templateProcessor, defaultDesignTokens, generateTailwindV4Theme, generateTailwindConfig, generateCSSFromTokens, generateFontImports };
30
30
 
31
31
  // V2 exports
32
- export { defaultDesignTokensV2, generateTailwindV4ThemeV2, generateCSSFromTokensV2, generateFontImportsV2, migrateDesignTokensV1toV2, validateDesignTokensV2, generateColorScale, generateDarkColorScale, calculateOnColor, isV1Format, isV2Format, COLOR_WORLDS, SEMANTIC_COLOR_WORLDS, TEXT_VOICES, TEXT_LEVELS, BUTTON_VARIANTS, BUTTON_SIZES, DEFAULT_SEMANTIC_MAPPINGS, DEFAULT_STATUS_SEMANTIC_MAPPINGS, getDefaultMappingsForWorld, DEFAULT_TEXT_ALIASES };
32
+ export { defaultDesignTokensV2, generateTailwindV4ThemeV2, generateCSSFromTokensV2, generateFontImportsV2, migrateDesignTokensV1toV2, validateDesignTokensV2, migrateResponsiveToFluid, generateColorScale, generateDarkColorScale, calculateOnColor, isV1Format, isV2Format, COLOR_WORLDS, SEMANTIC_COLOR_WORLDS, TEXT_VOICES, TEXT_LEVELS, BUTTON_VARIANTS, BUTTON_SIZES, DEFAULT_SEMANTIC_MAPPINGS, DEFAULT_STATUS_SEMANTIC_MAPPINGS, getDefaultMappingsForWorld, DEFAULT_TEXT_ALIASES };