@rettangoli/ui 0.1.2-rc30 → 0.1.2-rc32

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": "@rettangoli/ui",
3
- "version": "0.1.2-rc30",
3
+ "version": "0.1.2-rc32",
4
4
  "description": "A UI component library for building web interfaces.",
5
5
  "main": "dist/rettangoli-esm.min.js",
6
6
  "type": "module",
@@ -49,7 +49,7 @@
49
49
  "homepage": "https://github.com/yuusoft-org/rettangoli#readme",
50
50
  "dependencies": {
51
51
  "@floating-ui/dom": "^1.6.13",
52
- "@rettangoli/fe": "0.0.7-rc12",
52
+ "@rettangoli/fe": "0.0.7-rc14",
53
53
  "commander": "^13.1.0",
54
54
  "jempl": "0.1.4-rc1",
55
55
  "js-yaml": "^4.1.0",
package/src/common.js CHANGED
@@ -159,14 +159,23 @@ const spacing = {
159
159
 
160
160
  function convertObjectToCssString(styleObject, selector = ':host') {
161
161
  let result = "";
162
- for (const [size, mediaQuery] of Object.entries(mediaQueries)) {
162
+ // Process in correct order for max-width media queries: default first, then largest to smallest
163
+ const orderedSizes = ["default", "xl", "lg", "md", "sm"];
164
+
165
+ for (const size of orderedSizes) {
166
+ const mediaQuery = mediaQueries[size];
167
+ if (!styleObject[size] || Object.keys(styleObject[size]).length === 0) {
168
+ continue;
169
+ }
170
+
163
171
  if (size !== "default") {
164
172
  result += `${mediaQuery} {\n`;
165
173
  }
166
174
  let cssString = "";
167
175
  for (const [key, value] of Object.entries(styleObject[size])) {
168
176
  if (value !== undefined && value !== null) {
169
- cssString += `${key}: ${value};\n`;
177
+ // Add !important to override imported styles
178
+ cssString += `${key}: ${value} !important;\n`;
170
179
  }
171
180
  }
172
181
  result += `${selector} {
@@ -3,9 +3,10 @@
3
3
  /**
4
4
  *
5
5
  * @param {*} headingElements
6
+ * @param {*} offsetTop
6
7
  * @param {*} deps
7
8
  */
8
- const updateToLatestCurrentId = (headingElements, deps) => {
9
+ const updateToLatestCurrentId = (headingElements, offsetTop, deps) => {
9
10
  const { store, render } = deps;
10
11
 
11
12
  let currentHeadingId;
@@ -14,7 +15,9 @@ const updateToLatestCurrentId = (headingElements, deps) => {
14
15
  headingElements.forEach((heading) => {
15
16
  const rect = heading.getBoundingClientRect();
16
17
 
17
- if (rect.top <= 20) {
18
+ // A heading is "current" if it's at or above the offset line
19
+ // We want the heading that's closest to the offset but still above it
20
+ if (rect.top <= offsetTop) {
18
21
  if (rect.top > closestTopPosition) {
19
22
  closestTopPosition = rect.top;
20
23
  currentHeadingId = heading.id;
@@ -22,46 +25,83 @@ const updateToLatestCurrentId = (headingElements, deps) => {
22
25
  }
23
26
  });
24
27
 
28
+ // If no heading is above the threshold, select the first visible heading below it
29
+ if (!currentHeadingId) {
30
+ let lowestTop = Infinity;
31
+ headingElements.forEach((heading) => {
32
+ const rect = heading.getBoundingClientRect();
33
+ if (rect.top > offsetTop && rect.top < lowestTop) {
34
+ lowestTop = rect.top;
35
+ currentHeadingId = heading.id;
36
+ }
37
+ });
38
+ }
39
+
25
40
  if (currentHeadingId && currentHeadingId !== store.selectCurrentId()) {
26
41
  store.setCurrentId(currentHeadingId);
27
42
  render();
28
43
  }
29
44
  };
30
45
 
31
- const startListening = (contentContainer, deps) => {
46
+ const startListening = (contentContainer, scrollContainer, offsetTop, deps) => {
32
47
  const { store, render } = deps;
33
48
 
34
49
  // Extract headings
35
- const headings = contentContainer.querySelectorAll("rtgl-text[id]");
50
+ const headings = contentContainer.querySelectorAll("h1[id], h2[id], h3[id], h4[id], rtgl-text[id]");
36
51
  const headingElements = Array.from(headings);
37
52
 
38
- const items = headingElements.map((heading) => ({
39
- id: heading.id,
40
- href: `#${heading.id}`,
41
- title: heading.textContent
42
- }));
53
+ const items = headingElements.map((heading) => {
54
+ let level = 1;
55
+ const tagName = heading.tagName.toLowerCase();
56
+
57
+ if (tagName === 'h1') level = 1;
58
+ else if (tagName === 'h2') level = 2;
59
+ else if (tagName === 'h3') level = 3;
60
+ else if (tagName === 'h4') level = 4;
61
+ else if (tagName === 'rtgl-text') {
62
+ // For rtgl-text, check if it has a data-level attribute or default to 1
63
+ level = parseInt(heading.getAttribute('data-level') || '1', 10);
64
+ }
65
+
66
+ return {
67
+ id: heading.id,
68
+ href: `#${heading.id}`,
69
+ title: heading.textContent,
70
+ level: level
71
+ };
72
+ });
43
73
 
44
74
  store.setItems(items);
45
- updateToLatestCurrentId(headingElements, deps);
75
+ updateToLatestCurrentId(headingElements, offsetTop, deps);
46
76
  render();
47
77
 
48
- const boundCheckCurrentHeading = updateToLatestCurrentId.bind(this, headingElements, deps);
78
+ const boundCheckCurrentHeading = updateToLatestCurrentId.bind(this, headingElements, offsetTop, deps);
49
79
 
50
- // Add scroll listener to the content container
51
- contentContainer.addEventListener("scroll", boundCheckCurrentHeading, {
80
+ // Add scroll listener to the scroll container
81
+ scrollContainer.addEventListener("scroll", boundCheckCurrentHeading, {
52
82
  passive: true,
53
83
  });
54
84
 
55
85
  return () => {
56
- contentContainer.removeEventListener("scroll", boundCheckCurrentHeading);
86
+ scrollContainer.removeEventListener("scroll", boundCheckCurrentHeading);
57
87
  }
58
88
  };
59
89
 
60
90
  export const handleBeforeMount = (deps) => {
61
91
  const { attrs } = deps;
62
92
  requestAnimationFrame(() => {
63
- const targetElement = document.getElementById(attrs['target-id'])
64
- const stopListening = startListening(targetElement, deps)
93
+ const targetElement = document.getElementById(attrs['target-id']);
94
+
95
+ // Get scroll container - default to window for page scroll if not specified
96
+ let scrollContainer = window;
97
+ if (attrs['scroll-container-id']) {
98
+ scrollContainer = document.getElementById(attrs['scroll-container-id']) || window;
99
+ }
100
+
101
+ // Get offset top - default to 100px if not specified
102
+ const offsetTop = parseInt(attrs['offset-top'] || '100', 10);
103
+
104
+ const stopListening = startListening(targetElement, scrollContainer, offsetTop, deps);
65
105
  return () => {
66
106
  stopListening();
67
107
  }
@@ -5,11 +5,56 @@ export const INITIAL_STATE = Object.freeze({
5
5
  });
6
6
 
7
7
  export const toViewData = ({ state }) => {
8
+ // Find all parent IDs for the current active item
9
+ const getActiveParentIds = (items, currentId) => {
10
+ const activeParentIds = new Set();
11
+ const currentIndex = items.findIndex(item => item.id === currentId);
12
+
13
+ if (currentIndex === -1) return activeParentIds;
14
+
15
+ const currentLevel = items[currentIndex].level;
16
+
17
+ // Look backwards for all parents (items with lower level)
18
+ for (let i = currentIndex - 1; i >= 0; i--) {
19
+ if (items[i].level < currentLevel) {
20
+ // This is a parent - mark all ancestors
21
+ let ancestorLevel = items[i].level;
22
+ activeParentIds.add(items[i].id);
23
+
24
+ // Continue looking for grandparents
25
+ for (let j = i - 1; j >= 0; j--) {
26
+ if (items[j].level < ancestorLevel) {
27
+ activeParentIds.add(items[j].id);
28
+ ancestorLevel = items[j].level;
29
+ }
30
+ }
31
+ break; // Found the immediate parent chain
32
+ }
33
+ }
34
+
35
+ return activeParentIds;
36
+ };
37
+
38
+ const activeParentIds = getActiveParentIds(state.items, state.currentId);
39
+
8
40
  return {
9
41
  items: state.items.map((item) => {
42
+ const mlValues = {
43
+ 1: '0',
44
+ 2: '12px',
45
+ 3: '24px',
46
+ 4: '32px'
47
+ };
48
+
49
+ const isDirectlyActive = item.id === state.currentId;
50
+ const isParentActive = activeParentIds.has(item.id);
51
+ const active = isDirectlyActive || isParentActive;
52
+
10
53
  return {
11
54
  ...item,
12
- c: item.id === state.currentId ? 'fg' : 'mu-fg'
55
+ c: active ? 'fg' : 'mu-fg',
56
+ ml: mlValues[item.level] || '',
57
+ bc: active ? "fg" : "mu-fg"
13
58
  }
14
59
  }),
15
60
  currentId: state.currentId
@@ -27,8 +27,10 @@ events:
27
27
  type: object
28
28
 
29
29
  template:
30
- - rtgl-view h=f w=272:
31
- - rtgl-view w=f g=sm mt=xl:
32
- - $for item, i in items:
33
- - rtgl-view pv=xs av=c href=${item.href}:
34
- - rtgl-text s=sm c=${item.c} h-c=fg: ${item.title}
30
+ - rtgl-view h=f w=272 pr=md:
31
+ - rtgl-view w=f mt=xl:
32
+ - $for item, i in items:
33
+ - rtgl-view d=h bwl=xs bc="${item.bc}" pv=sm av=c href=${item.href} pl=md:
34
+ - rtgl-view w=${item.ml}:
35
+ - rtgl-text s=sm c=${item.c} h-c=fg: ${item.title}
36
+
@@ -81,8 +81,8 @@ class RettangoliColorPickerElement extends HTMLElement {
81
81
  "wh",
82
82
  "w",
83
83
  "h",
84
- "hidden",
85
- "visible",
84
+ "hide",
85
+ "show",
86
86
  "op",
87
87
  "z",
88
88
  ])
@@ -177,11 +177,11 @@ class RettangoliColorPickerElement extends HTMLElement {
177
177
  this._styles[size]["max-height"] = height;
178
178
  }
179
179
 
180
- if (this.hasAttribute(addSizePrefix("hidden"))) {
180
+ if (this.hasAttribute(addSizePrefix("hide"))) {
181
181
  this._styles[size].display = "none !important";
182
182
  }
183
183
 
184
- if (this.hasAttribute(addSizePrefix("visible"))) {
184
+ if (this.hasAttribute(addSizePrefix("show"))) {
185
185
  this._styles[size].display = "block !important";
186
186
  }
187
187
  });
@@ -85,7 +85,8 @@ class RettangoliImageElement extends HTMLElement {
85
85
  "wh",
86
86
  "w",
87
87
  "h",
88
- "hidden",
88
+ "hide",
89
+ "show",
89
90
  "height",
90
91
  "width",
91
92
  "z",
@@ -198,11 +199,11 @@ class RettangoliImageElement extends HTMLElement {
198
199
  this._styles[size]["max-height"] = height;
199
200
  }
200
201
 
201
- if (this.hasAttribute(addSizePrefix("hidden"))) {
202
+ if (this.hasAttribute(addSizePrefix("hide"))) {
202
203
  this._styles[size].display = "none !important";
203
204
  }
204
205
 
205
- if (this.hasAttribute(addSizePrefix("visible"))) {
206
+ if (this.hasAttribute(addSizePrefix("show"))) {
206
207
  this._styles[size].display = "block !important";
207
208
  }
208
209
  });
@@ -94,8 +94,8 @@ class RettangoliInputElement extends HTMLElement {
94
94
  "wh",
95
95
  "w",
96
96
  "h",
97
- "hidden",
98
- "visible",
97
+ "hide",
98
+ "show",
99
99
  "op",
100
100
  "z",
101
101
  ])
@@ -186,11 +186,11 @@ class RettangoliInputElement extends HTMLElement {
186
186
  this._styles[size]["max-height"] = height;
187
187
  }
188
188
 
189
- if (this.hasAttribute(addSizePrefix("hidden"))) {
189
+ if (this.hasAttribute(addSizePrefix("hide"))) {
190
190
  this._styles[size].display = "none !important";
191
191
  }
192
192
 
193
- if (this.hasAttribute(addSizePrefix("visible"))) {
193
+ if (this.hasAttribute(addSizePrefix("show"))) {
194
194
  this._styles[size].display = "block !important";
195
195
  }
196
196
  });
@@ -115,8 +115,8 @@ class RettangoliSliderElement extends HTMLElement {
115
115
  "wh",
116
116
  "w",
117
117
  "h",
118
- "hidden",
119
- "visible",
118
+ "hide",
119
+ "show",
120
120
  "op",
121
121
  "z",
122
122
  ])
@@ -212,11 +212,11 @@ class RettangoliSliderElement extends HTMLElement {
212
212
  this._styles[size]["max-height"] = height;
213
213
  }
214
214
 
215
- if (this.hasAttribute(addSizePrefix("hidden"))) {
215
+ if (this.hasAttribute(addSizePrefix("hide"))) {
216
216
  this._styles[size].display = "none !important";
217
217
  }
218
218
 
219
- if (this.hasAttribute(addSizePrefix("visible"))) {
219
+ if (this.hasAttribute(addSizePrefix("show"))) {
220
220
  this._styles[size].display = "block !important";
221
221
  }
222
222
  });
@@ -89,10 +89,17 @@ class RettangoliViewElement extends HTMLElement {
89
89
  "wh",
90
90
  "w",
91
91
  "h",
92
- "hidden",
92
+ "hide",
93
+ "show",
93
94
  "sh",
94
95
  "sv",
95
- "z"
96
+ "z",
97
+ "d",
98
+ "ah",
99
+ "av",
100
+ "flex",
101
+ "fw",
102
+ "overflow"
96
103
  ]),
97
104
  ];
98
105
  }
@@ -136,14 +143,13 @@ class RettangoliViewElement extends HTMLElement {
136
143
  this._linkElement = null;
137
144
  }
138
145
  }
146
+
147
+ connectedCallback() {
148
+ // Force update styles when connected to ensure responsive attributes are processed
149
+ this.updateStyles();
150
+ }
139
151
 
140
- attributeChangedCallback(name, oldValue, newValue) {
141
- // Handle href and target changes
142
- if (name === "href" || name === "target") {
143
- this._updateDOM();
144
- return;
145
- }
146
-
152
+ updateStyles() {
147
153
  // Reset styles for fresh calculation
148
154
  this._styles = {
149
155
  default: {},
@@ -158,6 +164,7 @@ class RettangoliViewElement extends HTMLElement {
158
164
  return `${size === "default" ? "" : `${size}-`}${tag}`;
159
165
  };
160
166
 
167
+
161
168
  const wh = this.getAttribute(addSizePrefix("wh"));
162
169
  const width = dimensionWithUnit(
163
170
  wh === null ? this.getAttribute(addSizePrefix("w")) : wh,
@@ -192,12 +199,110 @@ class RettangoliViewElement extends HTMLElement {
192
199
  this._styles[size]["max-height"] = height;
193
200
  }
194
201
 
195
- if (this.hasAttribute(addSizePrefix("hidden"))) {
196
- this._styles[size].display = "none !important";
202
+ if (this.hasAttribute(addSizePrefix("hide"))) {
203
+ this._styles[size].display = "none";
204
+ }
205
+
206
+ if (this.hasAttribute(addSizePrefix("show"))) {
207
+ this._styles[size].display = "flex";
208
+ }
209
+
210
+ // Handle flex direction and alignment
211
+ const direction = this.getAttribute(addSizePrefix("d"));
212
+ const alignHorizontal = this.getAttribute(addSizePrefix("ah"));
213
+ const alignVertical = this.getAttribute(addSizePrefix("av"));
214
+
215
+
216
+ if (direction === "h") {
217
+ this._styles[size]["flex-direction"] = "row";
218
+ } else if (direction === "v") {
219
+ this._styles[size]["flex-direction"] = "column";
220
+ } else if (size === "default" && !direction) {
221
+ // Check if any responsive direction attributes exist
222
+ const hasResponsiveDirection = ["sm", "md", "lg", "xl"].some(
223
+ breakpoint => this.hasAttribute(`${breakpoint}-d`)
224
+ );
225
+ if (hasResponsiveDirection) {
226
+ // Explicitly set column for default to ensure responsive overrides work
227
+ this._styles[size]["flex-direction"] = "column";
228
+ }
229
+ }
230
+
231
+ // Handle alignment based on direction
232
+ const isHorizontal = direction === "h";
233
+ const isVerticalOrDefault = direction === "v" || !direction;
234
+
235
+ // For horizontal direction: ah controls justify-content, av controls align-items
236
+ if (isHorizontal) {
237
+ if (alignHorizontal === "c") {
238
+ this._styles[size]["justify-content"] = "center";
239
+ } else if (alignHorizontal === "e") {
240
+ this._styles[size]["justify-content"] = "flex-end";
241
+ } else if (alignHorizontal === "s") {
242
+ this._styles[size]["justify-content"] = "flex-start";
243
+ }
244
+
245
+ if (alignVertical === "c") {
246
+ this._styles[size]["align-items"] = "center";
247
+ this._styles[size]["align-content"] = "center";
248
+ } else if (alignVertical === "e") {
249
+ this._styles[size]["align-items"] = "flex-end";
250
+ this._styles[size]["align-content"] = "flex-end";
251
+ } else if (alignVertical === "s") {
252
+ this._styles[size]["align-items"] = "flex-start";
253
+ }
254
+ }
255
+
256
+ // For vertical/default direction: ah controls align-items, av controls justify-content
257
+ if (isVerticalOrDefault && (alignHorizontal !== null || alignVertical !== null)) {
258
+ if (alignHorizontal === "c") {
259
+ this._styles[size]["align-items"] = "center";
260
+ } else if (alignHorizontal === "e") {
261
+ this._styles[size]["align-items"] = "flex-end";
262
+ } else if (alignHorizontal === "s") {
263
+ this._styles[size]["align-items"] = "flex-start";
264
+ }
265
+
266
+ if (alignVertical === "c") {
267
+ this._styles[size]["justify-content"] = "center";
268
+ } else if (alignVertical === "e") {
269
+ this._styles[size]["justify-content"] = "flex-end";
270
+ } else if (alignVertical === "s") {
271
+ this._styles[size]["justify-content"] = "flex-start";
272
+ }
273
+ }
274
+
275
+ // Handle flex property
276
+ const flex = this.getAttribute(addSizePrefix("flex"));
277
+ if (flex !== null) {
278
+ this._styles[size]["flex"] = flex;
279
+ }
280
+
281
+ // Handle flex-wrap
282
+ const flexWrap = this.getAttribute(addSizePrefix("fw"));
283
+ if (flexWrap === "w") {
284
+ this._styles[size]["flex-wrap"] = "wrap";
285
+ }
286
+
287
+ // Handle scroll properties
288
+ const scrollHorizontal = this.hasAttribute(addSizePrefix("sh"));
289
+ const scrollVertical = this.hasAttribute(addSizePrefix("sv"));
290
+ const overflow = this.getAttribute(addSizePrefix("overflow"));
291
+
292
+ if (scrollHorizontal && scrollVertical) {
293
+ this._styles[size]["overflow"] = "scroll";
294
+ this._styles[size]["flex-wrap"] = "nowrap";
295
+ } else if (scrollHorizontal) {
296
+ this._styles[size]["overflow-x"] = "scroll";
297
+ this._styles[size]["flex-wrap"] = "nowrap";
298
+ } else if (scrollVertical) {
299
+ this._styles[size]["overflow-y"] = "scroll";
300
+ this._styles[size]["flex-wrap"] = "nowrap";
197
301
  }
198
302
 
199
- if (this.hasAttribute(addSizePrefix("visible"))) {
200
- this._styles[size].display = "flex !important";
303
+ if (overflow === "hidden") {
304
+ this._styles[size]["overflow"] = "hidden";
305
+ this._styles[size]["flex-wrap"] = "nowrap";
201
306
  }
202
307
  });
203
308
 
@@ -207,6 +312,20 @@ class RettangoliViewElement extends HTMLElement {
207
312
  this._styleElement.textContent = newStyleString;
208
313
  this._lastStyleString = newStyleString;
209
314
  }
315
+
316
+ }
317
+
318
+ attributeChangedCallback(name, oldValue, newValue) {
319
+ // Handle href and target changes
320
+ if (name === "href" || name === "target") {
321
+ this._updateDOM();
322
+ return;
323
+ }
324
+
325
+ // Update styles for all other attributes
326
+ if (oldValue !== newValue) {
327
+ this.updateStyles();
328
+ }
210
329
  }
211
330
  }
212
331