@jsenv/dom 0.5.0 → 0.5.2

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/jsenv_dom.js CHANGED
@@ -316,6 +316,9 @@ const pxProperties = [
316
316
  "borderTopRightRadius",
317
317
  "borderBottomLeftRadius",
318
318
  "borderBottomRightRadius",
319
+ "gap",
320
+ "rowGap",
321
+ "columnGap",
319
322
  ];
320
323
 
321
324
  // Properties that need deg units
@@ -435,13 +438,67 @@ const normalizeNumber = (value, context, unit, propertyName) => {
435
438
 
436
439
  // Normalize styles for DOM application
437
440
  const normalizeStyles = (styles, context = "js") => {
441
+ if (typeof styles === "string") {
442
+ styles = parseStyleString(styles);
443
+ return styles;
444
+ }
438
445
  const normalized = {};
439
- for (const [key, value] of Object.entries(styles)) {
446
+ for (const key of Object.keys(styles)) {
447
+ const value = styles[key];
440
448
  normalized[key] = normalizeStyle(value, key, context);
441
449
  }
442
450
  return normalized;
443
451
  };
444
452
 
453
+ /**
454
+ * Parses a CSS style string into a style object.
455
+ * Handles CSS properties with proper camelCase conversion.
456
+ *
457
+ * @param {string} styleString - CSS style string like "color: red; font-size: 14px;"
458
+ * @returns {object} Style object with camelCase properties
459
+ */
460
+ const parseStyleString = (styleString, context = "js") => {
461
+ const style = {};
462
+
463
+ if (!styleString || typeof styleString !== "string") {
464
+ return style;
465
+ }
466
+
467
+ // Split by semicolon and process each declaration
468
+ const declarations = styleString.split(";");
469
+
470
+ for (let declaration of declarations) {
471
+ declaration = declaration.trim();
472
+ if (!declaration) continue;
473
+
474
+ const colonIndex = declaration.indexOf(":");
475
+ if (colonIndex === -1) continue;
476
+
477
+ const property = declaration.slice(0, colonIndex).trim();
478
+ const value = declaration.slice(colonIndex + 1).trim();
479
+
480
+ if (property && value) {
481
+ // CSS custom properties (starting with --) should NOT be converted to camelCase
482
+ if (property.startsWith("--")) {
483
+ style[property] = normalizeStyle(value, property, context);
484
+ } else {
485
+ // Convert kebab-case to camelCase (e.g., "font-size" -> "fontSize")
486
+ const camelCaseProperty = property.replace(
487
+ /-([a-z])/g,
488
+ (match, letter) => letter.toUpperCase(),
489
+ );
490
+ style[camelCaseProperty] = normalizeStyle(
491
+ value,
492
+ camelCaseProperty,
493
+ context,
494
+ );
495
+ }
496
+ }
497
+ }
498
+
499
+ return style;
500
+ };
501
+
445
502
  // Convert transform object to CSS string
446
503
  const stringifyCSSTransform = (transformObj) => {
447
504
  const transforms = [];
@@ -602,15 +659,30 @@ const parseSimple2DMatrix = (a, b, c, d, e, f) => {
602
659
  };
603
660
 
604
661
  // Merge two style objects, handling special cases like transform
605
- const mergeStyles = (stylesA, stylesB) => {
606
- const result = { ...stylesA };
607
- for (const key of Object.keys(stylesB)) {
608
- if (key === "transform") {
609
- result[key] = mergeOneStyle(stylesA[key], stylesB[key], key);
662
+ const mergeStyles = (stylesA, stylesB, context = "js") => {
663
+ if (!stylesA) {
664
+ return normalizeStyles(stylesB, context);
665
+ }
666
+ if (!stylesB) {
667
+ return normalizeStyles(stylesA, context);
668
+ }
669
+ const result = {};
670
+ const aKeys = Object.keys(stylesA);
671
+ // in case stylesB is a string we first parse it
672
+ stylesB = normalizeStyles(stylesB, context);
673
+ const bKeyToVisitSet = new Set(Object.keys(stylesB));
674
+ for (const aKey of aKeys) {
675
+ const bHasKey = bKeyToVisitSet.has(aKey);
676
+ if (bHasKey) {
677
+ bKeyToVisitSet.delete(aKey);
678
+ result[aKey] = mergeOneStyle(stylesA[aKey], stylesB[aKey], aKey, context);
610
679
  } else {
611
- result[key] = stylesB[key];
680
+ result[aKey] = normalizeStyle(stylesA[aKey], aKey, context);
612
681
  }
613
682
  }
683
+ for (const bKey of bKeyToVisitSet) {
684
+ result[bKey] = stylesB[bKey];
685
+ }
614
686
  return result;
615
687
  };
616
688
 
@@ -772,9 +844,9 @@ const createStyleController = (name = "anonymous") => {
772
844
  throw new Error("styles must be an object");
773
845
  }
774
846
 
775
- const normalizedStylesToSet = normalizeStyles(stylesToSet, "js");
776
847
  const elementData = elementWeakMap.get(element);
777
848
  if (!elementData) {
849
+ const normalizedStylesToSet = normalizeStyles(stylesToSet, "js");
778
850
  const animation = createAnimationForStyles(
779
851
  element,
780
852
  normalizedStylesToSet,
@@ -789,7 +861,7 @@ const createStyleController = (name = "anonymous") => {
789
861
  }
790
862
 
791
863
  const { styles, animation } = elementData;
792
- const mergedStyles = mergeStyles(styles, normalizedStylesToSet);
864
+ const mergedStyles = mergeStyles(styles, stylesToSet);
793
865
  elementData.styles = mergedStyles;
794
866
  updateAnimationStyles(animation, mergedStyles);
795
867
  };
@@ -10478,10 +10550,9 @@ const useResizeStatus = (elementRef, { as = "number" } = {}) => {
10478
10550
  installImportMetaCss(import.meta);
10479
10551
  import.meta.css = /* css */ `
10480
10552
  .ui_transition_container {
10553
+ position: relative;
10481
10554
  display: inline-flex;
10482
10555
  flex: 1;
10483
- position: relative;
10484
- overflow: hidden;
10485
10556
  }
10486
10557
 
10487
10558
  .ui_transition_outer_wrapper {
@@ -10490,7 +10561,6 @@ import.meta.css = /* css */ `
10490
10561
  }
10491
10562
 
10492
10563
  .ui_transition_measure_wrapper {
10493
- overflow: hidden;
10494
10564
  display: inline-flex;
10495
10565
  flex: 1;
10496
10566
  }
@@ -11911,4 +11981,4 @@ const crossFade = {
11911
11981
  },
11912
11982
  };
11913
11983
 
11914
- export { EASING, activeElementSignal, addActiveElementEffect, addAttributeEffect, addWillChange, allowWheelThrough, canInterceptKeys, captureScrollState, createDragGestureController, createDragToMoveGestureController, createHeightTransition, createIterableWeakSet, createOpacityTransition, createPubSub, createStyleController, createTimelineTransition, createTransition, createTranslateXTransition, createValueEffect, createWidthTransition, cubicBezier, dragAfterThreshold, elementIsFocusable, elementIsVisibleForFocus, elementIsVisuallyVisible, findAfter, findAncestor, findBefore, findDescendant, findFocusable, getAvailableHeight, getAvailableWidth, getBorderSizes, getContrastRatio, getDefaultStyles, getDragCoordinates, getDropTargetInfo, getFirstVisuallyVisibleAncestor, getFocusVisibilityInfo, getHeight, getInnerHeight, getInnerWidth, getMarginSizes, getMaxHeight, getMaxWidth, getMinHeight, getMinWidth, getPaddingSizes, getPositionedParent, getPreferedColorScheme, getScrollContainer, getScrollContainerSet, getScrollRelativeRect, getSelfAndAncestorScrolls, getStyle, getVisuallyVisibleInfo, getWidth, initFlexDetailsSet, initFocusGroup, initPositionSticky, initUITransition, isScrollable, parseCSSColor, pickLightOrDark, pickPositionRelativeTo, prefersDarkColors, prefersLightColors, preventFocusNav, preventFocusNavViaKeyboard, resolveCSSColor, resolveCSSSize, setAttribute, setAttributes, setStyles, startDragToResizeGesture, stickyAsRelativeCoords, stringifyCSSColor, trapFocusInside, trapScrollInside, useActiveElement, useAvailableHeight, useAvailableWidth, useMaxHeight, useMaxWidth, useResizeStatus, visibleRectEffect };
11984
+ export { EASING, activeElementSignal, addActiveElementEffect, addAttributeEffect, addWillChange, allowWheelThrough, canInterceptKeys, captureScrollState, createDragGestureController, createDragToMoveGestureController, createHeightTransition, createIterableWeakSet, createOpacityTransition, createPubSub, createStyleController, createTimelineTransition, createTransition, createTranslateXTransition, createValueEffect, createWidthTransition, cubicBezier, dragAfterThreshold, elementIsFocusable, elementIsVisibleForFocus, elementIsVisuallyVisible, findAfter, findAncestor, findBefore, findDescendant, findFocusable, getAvailableHeight, getAvailableWidth, getBorderSizes, getContrastRatio, getDefaultStyles, getDragCoordinates, getDropTargetInfo, getFirstVisuallyVisibleAncestor, getFocusVisibilityInfo, getHeight, getInnerHeight, getInnerWidth, getMarginSizes, getMaxHeight, getMaxWidth, getMinHeight, getMinWidth, getPaddingSizes, getPositionedParent, getPreferedColorScheme, getScrollContainer, getScrollContainerSet, getScrollRelativeRect, getSelfAndAncestorScrolls, getStyle, getVisuallyVisibleInfo, getWidth, initFlexDetailsSet, initFocusGroup, initPositionSticky, initUITransition, isScrollable, mergeStyles, normalizeStyles, parseCSSColor, pickLightOrDark, pickPositionRelativeTo, prefersDarkColors, prefersLightColors, preventFocusNav, preventFocusNavViaKeyboard, resolveCSSColor, resolveCSSSize, setAttribute, setAttributes, setStyles, startDragToResizeGesture, stickyAsRelativeCoords, stringifyCSSColor, trapFocusInside, trapScrollInside, useActiveElement, useAvailableHeight, useAvailableWidth, useMaxHeight, useMaxWidth, useResizeStatus, visibleRectEffect };
package/index.js CHANGED
@@ -5,8 +5,10 @@ export { createValueEffect } from "./src/value_effect.js";
5
5
 
6
6
  // style
7
7
  export { addWillChange, getStyle, setStyles } from "./src/style/dom_styles.js";
8
+ export { mergeStyles } from "./src/style/style_composition.js";
8
9
  export { createStyleController } from "./src/style/style_controller.js";
9
10
  export { getDefaultStyles } from "./src/style/style_default.js";
11
+ export { normalizeStyles } from "./src/style/style_parsing.js";
10
12
 
11
13
  // attributes
12
14
  export { addAttributeEffect } from "./src/attr/add_attribute_effect.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsenv/dom",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "description": "DOM utilities for writing frontend code",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,15 +1,35 @@
1
- import { parseCSSTransform, stringifyCSSTransform } from "./style_parsing.js";
1
+ import {
2
+ normalizeStyle,
3
+ normalizeStyles,
4
+ parseCSSTransform,
5
+ stringifyCSSTransform,
6
+ } from "./style_parsing.js";
2
7
 
3
8
  // Merge two style objects, handling special cases like transform
4
- export const mergeStyles = (stylesA, stylesB) => {
5
- const result = { ...stylesA };
6
- for (const key of Object.keys(stylesB)) {
7
- if (key === "transform") {
8
- result[key] = mergeOneStyle(stylesA[key], stylesB[key], key);
9
+ export const mergeStyles = (stylesA, stylesB, context = "js") => {
10
+ if (!stylesA) {
11
+ return normalizeStyles(stylesB, context);
12
+ }
13
+ if (!stylesB) {
14
+ return normalizeStyles(stylesA, context);
15
+ }
16
+ const result = {};
17
+ const aKeys = Object.keys(stylesA);
18
+ // in case stylesB is a string we first parse it
19
+ stylesB = normalizeStyles(stylesB, context);
20
+ const bKeyToVisitSet = new Set(Object.keys(stylesB));
21
+ for (const aKey of aKeys) {
22
+ const bHasKey = bKeyToVisitSet.has(aKey);
23
+ if (bHasKey) {
24
+ bKeyToVisitSet.delete(aKey);
25
+ result[aKey] = mergeOneStyle(stylesA[aKey], stylesB[aKey], aKey, context);
9
26
  } else {
10
- result[key] = stylesB[key];
27
+ result[aKey] = normalizeStyle(stylesA[aKey], aKey, context);
11
28
  }
12
29
  }
30
+ for (const bKey of bKeyToVisitSet) {
31
+ result[bKey] = stylesB[bKey];
32
+ }
13
33
  return result;
14
34
  };
15
35
 
@@ -94,9 +94,9 @@ export const createStyleController = (name = "anonymous") => {
94
94
  throw new Error("styles must be an object");
95
95
  }
96
96
 
97
- const normalizedStylesToSet = normalizeStyles(stylesToSet, "js");
98
97
  const elementData = elementWeakMap.get(element);
99
98
  if (!elementData) {
99
+ const normalizedStylesToSet = normalizeStyles(stylesToSet, "js");
100
100
  const animation = createAnimationForStyles(
101
101
  element,
102
102
  normalizedStylesToSet,
@@ -111,7 +111,7 @@ export const createStyleController = (name = "anonymous") => {
111
111
  }
112
112
 
113
113
  const { styles, animation } = elementData;
114
- const mergedStyles = mergeStyles(styles, normalizedStylesToSet);
114
+ const mergedStyles = mergeStyles(styles, stylesToSet);
115
115
  elementData.styles = mergedStyles;
116
116
  updateAnimationStyles(animation, mergedStyles);
117
117
  };
@@ -34,6 +34,9 @@ const pxProperties = [
34
34
  "borderTopRightRadius",
35
35
  "borderBottomLeftRadius",
36
36
  "borderBottomRightRadius",
37
+ "gap",
38
+ "rowGap",
39
+ "columnGap",
37
40
  ];
38
41
 
39
42
  // Properties that need deg units
@@ -153,13 +156,67 @@ const normalizeNumber = (value, context, unit, propertyName) => {
153
156
 
154
157
  // Normalize styles for DOM application
155
158
  export const normalizeStyles = (styles, context = "js") => {
159
+ if (typeof styles === "string") {
160
+ styles = parseStyleString(styles);
161
+ return styles;
162
+ }
156
163
  const normalized = {};
157
- for (const [key, value] of Object.entries(styles)) {
164
+ for (const key of Object.keys(styles)) {
165
+ const value = styles[key];
158
166
  normalized[key] = normalizeStyle(value, key, context);
159
167
  }
160
168
  return normalized;
161
169
  };
162
170
 
171
+ /**
172
+ * Parses a CSS style string into a style object.
173
+ * Handles CSS properties with proper camelCase conversion.
174
+ *
175
+ * @param {string} styleString - CSS style string like "color: red; font-size: 14px;"
176
+ * @returns {object} Style object with camelCase properties
177
+ */
178
+ export const parseStyleString = (styleString, context = "js") => {
179
+ const style = {};
180
+
181
+ if (!styleString || typeof styleString !== "string") {
182
+ return style;
183
+ }
184
+
185
+ // Split by semicolon and process each declaration
186
+ const declarations = styleString.split(";");
187
+
188
+ for (let declaration of declarations) {
189
+ declaration = declaration.trim();
190
+ if (!declaration) continue;
191
+
192
+ const colonIndex = declaration.indexOf(":");
193
+ if (colonIndex === -1) continue;
194
+
195
+ const property = declaration.slice(0, colonIndex).trim();
196
+ const value = declaration.slice(colonIndex + 1).trim();
197
+
198
+ if (property && value) {
199
+ // CSS custom properties (starting with --) should NOT be converted to camelCase
200
+ if (property.startsWith("--")) {
201
+ style[property] = normalizeStyle(value, property, context);
202
+ } else {
203
+ // Convert kebab-case to camelCase (e.g., "font-size" -> "fontSize")
204
+ const camelCaseProperty = property.replace(
205
+ /-([a-z])/g,
206
+ (match, letter) => letter.toUpperCase(),
207
+ );
208
+ style[camelCaseProperty] = normalizeStyle(
209
+ value,
210
+ camelCaseProperty,
211
+ context,
212
+ );
213
+ }
214
+ }
215
+ }
216
+
217
+ return style;
218
+ };
219
+
163
220
  // Convert transform object to CSS string
164
221
  export const stringifyCSSTransform = (transformObj) => {
165
222
  const transforms = [];
@@ -58,10 +58,9 @@ import { createGroupTransitionController } from "../transition/group_transition.
58
58
 
59
59
  import.meta.css = /* css */ `
60
60
  .ui_transition_container {
61
+ position: relative;
61
62
  display: inline-flex;
62
63
  flex: 1;
63
- position: relative;
64
- overflow: hidden;
65
64
  }
66
65
 
67
66
  .ui_transition_outer_wrapper {
@@ -70,7 +69,6 @@ import.meta.css = /* css */ `
70
69
  }
71
70
 
72
71
  .ui_transition_measure_wrapper {
73
- overflow: hidden;
74
72
  display: inline-flex;
75
73
  flex: 1;
76
74
  }