@jsenv/dom 0.5.0 → 0.5.1

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,28 @@ 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
+ const bKeyToVisitSet = new Set(Object.keys(stylesB));
672
+ for (const aKey of aKeys) {
673
+ const bHasKey = bKeyToVisitSet.has(aKey);
674
+ if (bHasKey) {
675
+ bKeyToVisitSet.delete(aKey);
676
+ result[aKey] = mergeOneStyle(stylesA[aKey], stylesB[aKey], aKey, context);
610
677
  } else {
611
- result[key] = stylesB[key];
678
+ result[aKey] = normalizeStyle(stylesA[aKey], aKey, context);
612
679
  }
613
680
  }
681
+ for (const bKey of bKeyToVisitSet) {
682
+ result[bKey] = normalizeStyle(stylesB[bKey], bKey, context);
683
+ }
614
684
  return result;
615
685
  };
616
686
 
@@ -772,9 +842,9 @@ const createStyleController = (name = "anonymous") => {
772
842
  throw new Error("styles must be an object");
773
843
  }
774
844
 
775
- const normalizedStylesToSet = normalizeStyles(stylesToSet, "js");
776
845
  const elementData = elementWeakMap.get(element);
777
846
  if (!elementData) {
847
+ const normalizedStylesToSet = normalizeStyles(stylesToSet, "js");
778
848
  const animation = createAnimationForStyles(
779
849
  element,
780
850
  normalizedStylesToSet,
@@ -789,7 +859,7 @@ const createStyleController = (name = "anonymous") => {
789
859
  }
790
860
 
791
861
  const { styles, animation } = elementData;
792
- const mergedStyles = mergeStyles(styles, normalizedStylesToSet);
862
+ const mergedStyles = mergeStyles(styles, stylesToSet);
793
863
  elementData.styles = mergedStyles;
794
864
  updateAnimationStyles(animation, mergedStyles);
795
865
  };
@@ -10478,10 +10548,9 @@ const useResizeStatus = (elementRef, { as = "number" } = {}) => {
10478
10548
  installImportMetaCss(import.meta);
10479
10549
  import.meta.css = /* css */ `
10480
10550
  .ui_transition_container {
10551
+ position: relative;
10481
10552
  display: inline-flex;
10482
10553
  flex: 1;
10483
- position: relative;
10484
- overflow: hidden;
10485
10554
  }
10486
10555
 
10487
10556
  .ui_transition_outer_wrapper {
@@ -10490,7 +10559,6 @@ import.meta.css = /* css */ `
10490
10559
  }
10491
10560
 
10492
10561
  .ui_transition_measure_wrapper {
10493
- overflow: hidden;
10494
10562
  display: inline-flex;
10495
10563
  flex: 1;
10496
10564
  }
@@ -11911,4 +11979,4 @@ const crossFade = {
11911
11979
  },
11912
11980
  };
11913
11981
 
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 };
11982
+ 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.1",
4
4
  "description": "DOM utilities for writing frontend code",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,15 +1,33 @@
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
+ const bKeyToVisitSet = new Set(Object.keys(stylesB));
19
+ for (const aKey of aKeys) {
20
+ const bHasKey = bKeyToVisitSet.has(aKey);
21
+ if (bHasKey) {
22
+ bKeyToVisitSet.delete(aKey);
23
+ result[aKey] = mergeOneStyle(stylesA[aKey], stylesB[aKey], aKey, context);
9
24
  } else {
10
- result[key] = stylesB[key];
25
+ result[aKey] = normalizeStyle(stylesA[aKey], aKey, context);
11
26
  }
12
27
  }
28
+ for (const bKey of bKeyToVisitSet) {
29
+ result[bKey] = normalizeStyle(stylesB[bKey], bKey, context);
30
+ }
13
31
  return result;
14
32
  };
15
33
 
@@ -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
  }