@ulu/frontend 0.6.28 → 0.6.30

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.
@@ -13,6 +13,49 @@ export function dataAttributeToDatasetKey(attribute) {
13
13
  return kebabToCamel(attribute.replace(/^data-/, ""));
14
14
  }
15
15
 
16
+ /**
17
+ * Groups a parent's children into rows based on their visual coordinates (top and left positions).
18
+ * Filters out hidden elements and sorts them visually to support CSS grid order properties.
19
+ * @param {HTMLElement} parent The grid/parent container
20
+ * @returns {HTMLElement[][]} A 2D array of rows, where each row is an array of elements in visual order.
21
+ */
22
+ export function getVisualRows(parent) {
23
+ const children = [...parent.children];
24
+
25
+ // Map to bounding client rects and filter out hidden elements (e.g. display: none)
26
+ const items = children
27
+ .map(child => ({ child, rect: child.getBoundingClientRect() }))
28
+ .filter(item => item.rect.width !== 0 || item.rect.height !== 0);
29
+
30
+ if (items.length === 0) {
31
+ return [];
32
+ }
33
+
34
+ // Sort items visually: top-to-bottom, then left-to-right to support CSS order properties.
35
+ // Use Math.abs because comparison elements can be passed in either order; if a difference
36
+ // exists in either direction (> 1px threshold for sub-pixel rounding), sort by top offset.
37
+ items.sort((a, b) => {
38
+ if (Math.abs(a.rect.top - b.rect.top) > 1) {
39
+ return a.rect.top - b.rect.top;
40
+ }
41
+ return a.rect.left - b.rect.left;
42
+ });
43
+
44
+ const rows = [];
45
+ let currentY = null;
46
+
47
+ // Group into rows
48
+ items.forEach(({ child, rect }) => {
49
+ if (currentY === null || Math.abs(rect.top - currentY) > 1) {
50
+ rows.push([]);
51
+ currentY = rect.top;
52
+ }
53
+ rows[rows.length - 1].push(child);
54
+ });
55
+
56
+ return rows;
57
+ }
58
+
16
59
  /**
17
60
  * Sets up the positional classes that would come from the equal
18
61
  * height module. Needs to be rerun by user when layout changes
@@ -24,37 +67,28 @@ export function dataAttributeToDatasetKey(attribute) {
24
67
  * @param {Object} classes Override the default equal heights classes
25
68
  */
26
69
  export function setPositionClasses(parent, classes = {
27
- columnFirst: 'position-column-first',
28
- columnLast: 'position-column-last',
29
- rowFirst: 'position-row-first',
30
- rowLast: 'position-row-last'
70
+ columnFirst: "position-column-first",
71
+ columnLast: "position-column-last",
72
+ rowFirst: "position-row-first",
73
+ rowLast: "position-row-last"
31
74
  }) {
32
75
  const children = [...parent.children];
33
- const rows = [];
34
- let lastY;
35
- // Check element against last
36
- // If they don't match it's a new row create a new array
37
- // Then push into the last array in the rows array
38
- children.forEach((child) => {
39
- const y = child.getBoundingClientRect().y;
40
- if (lastY !== y) rows.push([]);
41
- rows[rows.length - 1].push(child);
42
- lastY = y;
43
- child.classList.remove(...Object.values(classes)); // Remove previously set classes
44
- });
45
- // Apply Classes
46
- rows.forEach((row, index) => {
47
- if (index === 0)
48
- row.forEach(child => child.classList.add(classes.rowFirst));
49
- if (index == rows.length - 1)
50
- row.forEach(child => child.classList.add(classes.rowLast));
76
+
77
+ // Remove previously set classes from all children first to avoid stale states
78
+ children.forEach(child => child.classList.remove(...Object.values(classes)));
79
+
80
+ const rows = getVisualRows(parent);
81
+
82
+ if (rows.length === 0) return;
83
+
84
+ // Apply row classes
85
+ rows[0].forEach(child => child.classList.add(classes.rowFirst));
86
+ rows[rows.length - 1].forEach(child => child.classList.add(classes.rowLast));
51
87
 
52
- row.forEach((child, childIndex) => {
53
- if (childIndex === 0)
54
- child.classList.add(classes.columnFirst);
55
- if (childIndex == row.length - 1)
56
- child.classList.add(classes.columnLast);
57
- });
88
+ // Apply column classes
89
+ rows.forEach((row) => {
90
+ row[0].classList.add(classes.columnFirst);
91
+ row[row.length - 1].classList.add(classes.columnLast);
58
92
  });
59
93
  }
60
94
 
@@ -3,15 +3,21 @@
3
3
  description: A responsive grid that automatically fills rows with cards.
4
4
  -->
5
5
  <div class="card-grid">
6
- <article class="card">
7
- <div class="card__body"><div class="card__main"><h3 class="card__title">Card 1</h3><p>Content for the first card.</p></div></div>
8
- </article>
9
- <article class="card">
10
- <div class="card__body"><div class="card__main"><h3 class="card__title">Card 2</h3><p>Content for the second card.</p></div></div>
11
- </article>
12
- <article class="card">
13
- <div class="card__body"><div class="card__main"><h3 class="card__title">Card 3</h3><p>Content for the third card.</p></div></div>
14
- </article>
6
+ <div class="card-grid__item">
7
+ <article class="card">
8
+ <div class="card__body"><div class="card__main"><h3 class="card__title">Card 1</h3><p>Content for the first card.</p></div></div>
9
+ </article>
10
+ </div>
11
+ <div class="card-grid__item">
12
+ <article class="card">
13
+ <div class="card__body"><div class="card__main"><h3 class="card__title">Card 2</h3><p>Content for the second card.</p></div></div>
14
+ </article>
15
+ </div>
16
+ <div class="card-grid__item">
17
+ <article class="card">
18
+ <div class="card__body"><div class="card__main"><h3 class="card__title">Card 3</h3><p>Content for the third card.</p></div></div>
19
+ </article>
20
+ </div>
15
21
  </div>
16
22
 
17
23
  <!-- @ulu-demo
@@ -19,16 +25,24 @@
19
25
  description: A grid with smaller minimum column widths and tighter gaps.
20
26
  -->
21
27
  <div class="card-grid card-grid--compact">
22
- <article class="card">
23
- <div class="card__body"><div class="card__main"><h3 class="card__title">Compact 1</h3></div></div>
24
- </article>
25
- <article class="card">
26
- <div class="card__body"><div class="card__main"><h3 class="card__title">Compact 2</h3></div></div>
27
- </article>
28
- <article class="card">
29
- <div class="card__body"><div class="card__main"><h3 class="card__title">Compact 3</h3></div></div>
30
- </article>
31
- <article class="card">
32
- <div class="card__body"><div class="card__main"><h3 class="card__title">Compact 4</h3></div></div>
33
- </article>
28
+ <div class="card-grid__item">
29
+ <article class="card">
30
+ <div class="card__body"><div class="card__main"><h3 class="card__title">Compact 1</h3></div></div>
31
+ </article>
32
+ </div>
33
+ <div class="card-grid__item">
34
+ <article class="card">
35
+ <div class="card__body"><div class="card__main"><h3 class="card__title">Compact 2</h3></div></div>
36
+ </article>
37
+ </div>
38
+ <div class="card-grid__item">
39
+ <article class="card">
40
+ <div class="card__body"><div class="card__main"><h3 class="card__title">Compact 3</h3></div></div>
41
+ </article>
42
+ </div>
43
+ <div class="card-grid__item">
44
+ <article class="card">
45
+ <div class="card__body"><div class="card__main"><h3 class="card__title">Compact 4</h3></div></div>
46
+ </article>
47
+ </div>
34
48
  </div>
@@ -36,6 +36,10 @@ $-fallbacks: (
36
36
  "function" : meta.get-function("get", false, "element"),
37
37
  "property" : "icon-centered-vertical-offset",
38
38
  ),
39
+ "title-line-height" : (
40
+ "function" : meta.get-function("get", false, "typography"),
41
+ "property" : "line-height-dense",
42
+ )
39
43
  );
40
44
 
41
45
  /// Module Settings
@@ -79,6 +83,7 @@ $-fallbacks: (
79
83
  /// @prop {CssValue} title-font-weight [bold] Font weight of the title.
80
84
  /// @prop {CssValue} title-font-family [null] Font family for title
81
85
  /// @prop {Dimension} title-icon-margin [0.5em] The margin of the title icon
86
+ /// @prop {Boolean|CssValue} title-line-height [true] If true will default to typography "line-height-dense", or set your own value
82
87
  /// @prop {String} title-size [large] The font-size of the title. This uses typography.scss, so the value of this options should be a variable from typography.scss.
83
88
  /// @prop {CssValue} title-text-transform [null] Transform option for the title.
84
89
  /// @prop {Map} sizes [Map] Size options to enable unique stylings.
@@ -129,6 +134,7 @@ $config: (
129
134
  "title-font-family" : null,
130
135
  "title-icon-margin" : 0.5em,
131
136
  "title-size" : "large",
137
+ "title-line-height" : true,
132
138
  "title-text-transform" : null,
133
139
  "breakpoint" : true,
134
140
  "frame-aspect-ratio" : list.slash(16, 9),
@@ -239,6 +245,7 @@ $config: (
239
245
  font-weight: get("title-font-weight");
240
246
  font-family: get("title-font-family");
241
247
  text-transform: get("title-text-transform");
248
+ line-height: get("title-line-height");
242
249
  @if (get("title-size")) {
243
250
  @include typography.size(get("title-size"), $only-font-size: true);
244
251
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ulu/frontend",
3
- "version": "0.6.28",
3
+ "version": "0.6.30",
4
4
  "description": "A framework-agnostic frontend toolkit providing a modular, tree-shakable library of accessible components and utilities. Designed for seamless integration, it features a highly configurable SCSS system for any environment and vanilla JavaScript modules optimized for traditional websites and content management systems.",
5
5
  "type": "module",
6
6
  "files": [