rettangoli-ui 0.1.0-rc3 → 0.1.0-rc4

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.0-rc3",
3
+ "version": "0.1.0-rc4",
4
4
  "description": "A UI component library for building web interfaces.",
5
5
  "main": "dist/rettangoli-esm.min.js",
6
6
  "type": "module",
@@ -21,20 +21,21 @@ events:
21
21
  properties: {}
22
22
 
23
23
  styles:
24
- '@keyframes dialog-in':
25
- from:
26
- opacity: 0
27
- transform: scale(0.95)
28
- to:
29
- opacity: 1
30
- transform: scale(1)
24
+ # '@keyframes dialog-in':
25
+ # from:
26
+ # opacity: 0
27
+ # transform: scale(0.95)
28
+ # to:
29
+ # opacity: 1
30
+ # transform: scale(1)
31
31
 
32
- '#dialog-container':
33
- animation: dialog-in 150ms cubic-bezier(0.16, 1, 0.3, 1)
34
- transform-origin: top
32
+ # '#dialog-container':
33
+ # animation: dialog-in 150ms cubic-bezier(0.16, 1, 0.3, 1)
34
+ # transform-origin: top
35
35
 
36
36
  template:
37
37
  - $if isOpen:
38
- - rtgl-view#dialog-overlay pos=fix cor=full ah=c av=c:
38
+ - rtgl-view pos=fix cor=full ah=c av=c:
39
+ - 'rtgl-view#dialog-overlay pos=fix cor=full ah=c av=c bgc=bg op=0.5':
39
40
  - rtgl-view#dialog-container pos=fix bw=xs p=lg bgc=bg w=600 br=sm:
40
41
  - slot name=content:
@@ -0,0 +1,5 @@
1
+
2
+ export const handleClickPopoverOverlay = (e, deps) => {
3
+ const { dispatchEvent } = deps;
4
+ dispatchEvent(new CustomEvent('click-overlay'));
5
+ }
@@ -0,0 +1,25 @@
1
+
2
+ export const INITIAL_STATE = Object.freeze({
3
+ });
4
+
5
+ export const toViewData = ({ state, props }) => {
6
+ return {
7
+ items: props.items || [],
8
+ isOpen: props.isOpen || false,
9
+ position: props.position || {
10
+ x: 0,
11
+ y: 0,
12
+ },
13
+ };
14
+ }
15
+
16
+ export const selectState = ({ state }) => {
17
+ return state;
18
+ }
19
+
20
+ export const setState = (state) => {
21
+ // do doSomething
22
+ }
23
+
24
+
25
+
@@ -0,0 +1,52 @@
1
+ elementName: rtgl-dropdown-menu
2
+
3
+ viewDataSchema:
4
+ type: object
5
+
6
+ propsSchema:
7
+ type: object
8
+ properties:
9
+ items:
10
+ type: array
11
+ items:
12
+ type: object
13
+ properties:
14
+ label:
15
+ type: string
16
+ type:
17
+ type: string
18
+ enum:
19
+ - label
20
+ - item
21
+ - separator
22
+ isOpen:
23
+ type: boolean
24
+ position:
25
+ type: object
26
+ properties:
27
+ x:
28
+ type: number
29
+ y:
30
+ type: number
31
+
32
+ refs:
33
+ popover:
34
+ eventListeners:
35
+ click-overlay:
36
+ handler: handleClickPopoverOverlay
37
+
38
+ events: {}
39
+
40
+ template:
41
+ - rtgl-popover#popover .isOpen=isOpen .position=position:
42
+ - rtgl-view wh=300 g=xs slot=content bgc=background br=md:
43
+ - $for item, i in items:
44
+ - $if item.type == 'label':
45
+ - rtgl-view w=f ph=lg pv=md:
46
+ - rtgl-text s=sm c=mu-fg: ${item.label}
47
+ $elif item.type == 'item':
48
+ - rtgl-view#option-${i} w=f h-bgc=ac ph=lg pv=md cur=p br=md bgc=mu:
49
+ - rtgl-text: ${item.label}
50
+ $elif item.type == 'separator':
51
+ - rtgl-view w=f h=1 ph=lg mv=md bgc=bo:
52
+
@@ -1,17 +1,5 @@
1
- export const handleClickOverlay = (e, deps) => {
2
- const { props, dispatchEvent, store, render } = deps;
3
- if (props.isOpen !== undefined) {
4
- dispatchEvent(new CustomEvent('closePopover'));
5
- return;
6
- }
7
- store.setClose();
8
- render();
9
- }
10
1
 
11
- export const open = (payload, deps) => {
12
- const { position } = payload;
13
- const { store, render } = deps;
14
- store.setOpen(position);
15
- render();
2
+ export const handleClickOverlay = (e, deps) => {
3
+ const { dispatchEvent } = deps;
4
+ dispatchEvent(new CustomEvent('click-overlay'));
16
5
  }
17
-
@@ -1,37 +1,12 @@
1
- export const INITIAL_STATE = Object.freeze({
2
- isOpen: false,
3
- position: {
4
- x: 0,
5
- y: 0
6
- },
7
- });
1
+ export const INITIAL_STATE = Object.freeze({});
8
2
 
9
3
  export const toViewData = ({ state, props }) => {
10
- if (props.isOpen !== undefined) {
11
- return {
12
- isOpen: props.isOpen,
13
- position: {
14
- x: 0,
15
- y: 0
16
- }
17
- }
18
- }
19
4
  return {
20
- isOpen: state.isOpen,
21
- position: state.position,
5
+ isOpen: props.isOpen,
6
+ position: props.position,
22
7
  };
23
8
  }
24
9
 
25
10
  export const selectState = ({ state }) => {
26
11
  return state;
27
12
  }
28
-
29
- export const setOpen = (state, position) => {
30
- state.isOpen = true;
31
- state.position = position;
32
- }
33
-
34
- export const setClose = (state) => {
35
- state.isOpen = false;
36
- state.position = { x: 0, y: 0 };
37
- }
@@ -23,6 +23,13 @@ propsSchema:
23
23
  default: bottom
24
24
  isOpen:
25
25
  type: boolean
26
+ position:
27
+ type: object
28
+ properties:
29
+ x:
30
+ type: number
31
+ y:
32
+ type: number
26
33
 
27
34
  refs:
28
35
  popoverOverlay:
@@ -46,5 +53,5 @@ styles:
46
53
  template:
47
54
  - $if isOpen:
48
55
  - rtgl-view#popoverOverlay pos=fix cor=full:
49
- - 'rtgl-view#popoverContainer pos=fix style="left: ${position.x}px; top: ${position.y}px;" id=floatingElement bw=xs p=md bgc=bg':
56
+ - 'rtgl-view#popoverContainer pos=fix style="left: ${position.x}px; top: ${position.y}px;" id=floatingElement bw=xs p=md bgc=mu':
50
57
  - slot name=content:
@@ -1,15 +1,46 @@
1
1
 
2
2
  export const handleOnMount = (deps) => {
3
+ const { store, props, render } = deps;
4
+
5
+ if (props.selectedValue !== null && props.selectedValue !== undefined && props.options) {
6
+ const selectedOption = props.options.find(opt => opt.value === props.selectedValue);
7
+ if (selectedOption) {
8
+ store.update((state) => {
9
+ state.selectedValue = selectedOption.value;
10
+ state.selectedLabel = selectedOption.label;
11
+ });
12
+ render();
13
+ }
14
+ }
3
15
  }
4
16
 
5
17
  export const handleButtonClick = (e, deps) => {
6
18
  const { store, render, getRefIds } = deps;
7
- const refIds = getRefIds();
8
- refIds.popover.elm.transformedHandlers.open({
19
+ store.openOptionsPopover({
9
20
  position: {
21
+ y: e.clientY,
10
22
  x: e.clientX,
11
- y: e.clientY
12
23
  }
13
- });
24
+ })
25
+ render();
26
+ }
27
+
28
+ export const handleClickOptionsPopoverOverlay = (e, deps) => {
29
+ const { store, render } = deps;
30
+ store.closeOptionsPopover();
31
+ render();
32
+ }
33
+
34
+ export const handleOptionClick = (e, deps) => {
35
+ const { render, dispatchEvent, props } = deps;
36
+ const id = e.currentTarget.id.replace('option-', '');
37
+
38
+ const option = props.options[id];
14
39
 
40
+ dispatchEvent(new CustomEvent('option-selected', {
41
+ detail: { value: option.value, label: option.label },
42
+ bubbles: true
43
+ }));
44
+
45
+ render();
15
46
  }
@@ -1,11 +1,43 @@
1
1
  export const INITIAL_STATE = Object.freeze({
2
2
  isOpen: false,
3
+ position: {
4
+ x: 0,
5
+ y: 0,
6
+ },
7
+ selectedValue: null,
8
+ selectedLabel: null,
3
9
  });
4
10
 
5
11
  export const toViewData = ({ state, props }) => {
12
+ // Calculate display label
13
+ let displayLabel = props.placeholder || 'Select an option';
14
+
15
+ // Use state's selected value if available, otherwise use props.selectedValue
16
+ const currentValue = state.selectedValue !== null ? state.selectedValue : props.selectedValue;
17
+
18
+ if (currentValue !== null && currentValue !== undefined && props.options) {
19
+ const selectedOption = props.options.find(opt => opt.value === currentValue);
20
+ if (selectedOption) {
21
+ displayLabel = selectedOption.label;
22
+ }
23
+ } else if (state.selectedLabel) {
24
+ displayLabel = state.selectedLabel;
25
+ }
26
+
27
+ // Map options to include isSelected flag and computed background color
28
+ const optionsWithSelection = (props.options || []).map(option => ({
29
+ ...option,
30
+ isSelected: option.value === currentValue,
31
+ bgc: option.value === currentValue ? 'mu' : ''
32
+ }));
33
+
6
34
  return {
7
35
  isOpen: state.isOpen,
8
- options: props.options || []
36
+ position: state.position,
37
+ options: optionsWithSelection,
38
+ selectedValue: currentValue,
39
+ selectedLabel: displayLabel,
40
+ placeholder: props.placeholder || 'Select an option'
9
41
  };
10
42
  }
11
43
 
@@ -13,12 +45,20 @@ export const selectState = ({ state }) => {
13
45
  return state;
14
46
  }
15
47
 
16
- export const setOpen = (state) => {
17
- state.open = true;
48
+ export const openOptionsPopover = (state, payload) => {
49
+ const { position } = payload;
50
+ state.position = position;
51
+ state.isOpen = true;
18
52
  }
19
53
 
20
- export const setState = (state) => {
21
- // do doSomething
54
+ export const closeOptionsPopover = (state) => {
55
+ state.isOpen = false;
56
+ }
57
+
58
+ export const selectOption = (state, option) => {
59
+ state.selectedValue = option.value;
60
+ state.selectedLabel = option.label;
61
+ state.isOpen = false;
22
62
  }
23
63
 
24
64
 
@@ -7,32 +7,44 @@ propsSchema:
7
7
  type: object
8
8
  properties:
9
9
  options:
10
- items:
11
10
  type: array
12
11
  items:
13
12
  type: object
14
13
  properties:
15
14
  id:
16
15
  type: string
17
- lalbel:
16
+ label:
18
17
  type: string
19
18
  value:
20
19
  type: any
20
+ selectedValue:
21
+ type: any
22
+ placeholder:
23
+ type: string
24
+ onChange:
25
+ type: function
21
26
 
22
27
  refs:
23
28
  select-button:
24
29
  eventListeners:
25
30
  click:
26
31
  handler: handleButtonClick
27
- popover: {}
28
-
32
+ popover:
33
+ eventListeners:
34
+ click-overlay:
35
+ handler: handleClickOptionsPopoverOverlay
36
+ option-*:
37
+ eventListeners:
38
+ click:
39
+ handler: handleOptionClick
29
40
 
30
41
  events: {}
31
42
 
32
43
  template:
33
- - rtgl-button#select-button v=ol: Placeholder
34
- - rtgl-popover#popover:
35
- - rtgl-view wh=300 g=xs slot=content:
44
+ - rtgl-button#select-button v=ol:
45
+ - ${selectedLabel}
46
+ - rtgl-popover#popover .isOpen=isOpen .position=position:
47
+ - rtgl-view wh=300 g=xs slot=content bgc=background br=md:
36
48
  - $for option, i in options:
37
- - rtgl-view w=f h-bgc=mu ph=lg pv=md cur=p br=md:
49
+ - rtgl-view#option-${i} w=f h-bgc=ac ph=lg pv=md cur=p br=md bgc=${option.bgc}:
38
50
  - rtgl-text: ${option.label}
@@ -6,30 +6,42 @@ const stringifyAttrs = (attrs) => {
6
6
  return Object.entries(attrs).filter(([key]) => !blacklistedAttrs.includes(key)).map(([key, value]) => `${key}=${value}`).join(' ');
7
7
  }
8
8
 
9
- function flattenItems(items) {
9
+ function flattenItems(items, selectedItemId = null) {
10
10
  let result = [];
11
11
 
12
12
  for (const item of items) {
13
+ const itemId = item.id || item.href || item.path;
14
+ const isSelected = selectedItemId === itemId;
15
+
13
16
  // Add the parent item if it's not just a group label
14
17
  result.push({
15
- id: item.id || item.href || item.path,
18
+ id: itemId,
16
19
  title: item.title,
17
20
  href: item.href,
18
21
  type: item.type || 'item',
19
22
  icon: item.icon,
20
23
  hrefAttr: item.href ? `href=${item.href}` : '',
24
+ isSelected,
25
+ itemBgc: isSelected ? 'ac' : 'bg',
26
+ itemHoverBgc: isSelected ? 'ac' : 'mu',
21
27
  });
22
28
 
23
29
  // Add child items if they exist
24
30
  if (item.items && Array.isArray(item.items)) {
25
31
  for (const subItem of item.items) {
32
+ const subItemId = subItem.id || subItem.href || subItem.path;
33
+ const isSubSelected = selectedItemId === subItemId;
34
+
26
35
  result.push({
27
- id: subItem.id || subItem.href || subItem.path,
36
+ id: subItemId,
28
37
  title: subItem.title,
29
38
  href: subItem.href,
30
39
  type: subItem.type || 'item',
31
40
  icon: subItem.icon,
32
41
  hrefAttr: subItem.href ? `href=${subItem.href}` : '',
42
+ isSelected: isSubSelected,
43
+ itemBgc: isSubSelected ? 'ac' : 'bg',
44
+ itemHoverBgc: isSubSelected ? 'ac' : 'mu',
33
45
  });
34
46
  }
35
47
  }
@@ -41,6 +53,7 @@ function flattenItems(items) {
41
53
  export const toViewData = ({ state, props, attrs }) => {
42
54
  const attrsHeader = attrs.header ? JSON.parse(decodeURIComponent(attrs.header)) : props.header;
43
55
  const attrsItems = attrs.items ? JSON.parse(decodeURIComponent(attrs.items)) : props.items;
56
+ const selectedItemId = attrs.selectedItemId || props.selectedItemId;
44
57
 
45
58
  const containerAttrString = stringifyAttrs(attrs);
46
59
  const mode = attrs.mode || 'full';
@@ -55,7 +68,7 @@ export const toViewData = ({ state, props, attrs }) => {
55
68
  },
56
69
  };
57
70
 
58
- const items = attrsItems ? flattenItems(attrsItems) : [];
71
+ const items = attrsItems ? flattenItems(attrsItems, selectedItemId) : [];
59
72
 
60
73
  // Computed values based on mode
61
74
  const sidebarWidth = mode === 'full' ? 272 : 64;
@@ -100,7 +113,8 @@ export const toViewData = ({ state, props, attrs }) => {
100
113
  itemContentAlign,
101
114
  itemAlignAttr,
102
115
  itemWidth,
103
- headerWidth
116
+ headerWidth,
117
+ selectedItemId
104
118
  };
105
119
  }
106
120
 
@@ -37,6 +37,8 @@ viewDataSchema:
37
37
  type: string
38
38
  headerWidth:
39
39
  type: string
40
+ selectedItemId:
41
+ type: string
40
42
  header:
41
43
  type: object
42
44
  properties:
@@ -74,6 +76,8 @@ viewDataSchema:
74
76
  propsSchema:
75
77
  type: object
76
78
  properties:
79
+ selectedItemId:
80
+ type: string
77
81
  header:
78
82
  type: object
79
83
  properties:
@@ -170,7 +174,7 @@ template:
170
174
  $else:
171
175
  - rtgl-view mt=md h=1 bgc=mu-bg:
172
176
  $else:
173
- - rtgl-view#item-${item.id} ${item.hrefAttr} h=${itemHeight} av=c ${itemAlignAttr} ph=${itemPadding} w=${itemWidth} h-bgc=mu br=lg bgc=bg cur=p title="${item.title}":
177
+ - rtgl-view#item-${item.id} ${item.hrefAttr} h=${itemHeight} av=c ${itemAlignAttr} ph=${itemPadding} w=${itemWidth} h-bgc=${item.itemHoverBgc} br=lg bgc=${item.itemBgc} cur=p title="${item.title}":
174
178
  - $if item.icon:
175
179
  - $if showLabels:
176
180
  - rtgl-view d=h ah=${itemContentAlign} g=sm:
@@ -23,7 +23,7 @@ class RettangoliButtonElement extends HTMLElement {
23
23
  flex-direction: row;
24
24
  align-items: center;
25
25
  justify-content: center;
26
- gap: var(--spacing-sm);
26
+ gap: var(--spacing-lg);
27
27
  border-width: 0px;
28
28
  border-style: solid;
29
29
  border-color: var(--border);
@@ -124,6 +124,28 @@ class RettangoliButtonElement extends HTMLElement {
124
124
  text-decoration: underline;
125
125
  }
126
126
 
127
+ /* Square button styles */
128
+ :host([sq]) button {
129
+ width: 32px;
130
+ height: 32px;
131
+ padding: 0;
132
+ gap: 0;
133
+ }
134
+
135
+ :host([sq][s="sm"]) button {
136
+ width: 24px;
137
+ height: 24px;
138
+ padding: 0;
139
+ gap: 0;
140
+ }
141
+
142
+ :host([sq][s="lg"]) button {
143
+ width: 40px;
144
+ height: 40px;
145
+ padding: 0;
146
+ gap: 0;
147
+ }
148
+
127
149
  ${anchorStyles}
128
150
 
129
151
  a {
@@ -152,7 +174,7 @@ class RettangoliButtonElement extends HTMLElement {
152
174
  }
153
175
 
154
176
  static get observedAttributes() {
155
- return ["key", "href", "target", "w", "t", "icon", "disabled", "v", "s"];
177
+ return ["key", "href", "target", "w", "t", "icon", "disabled", "v", "s", "sq", "ip"];
156
178
  }
157
179
 
158
180
  connectedCallback() {
@@ -170,8 +192,10 @@ class RettangoliButtonElement extends HTMLElement {
170
192
  // Update icon
171
193
  this._updateIcon();
172
194
 
173
- // Update width styling
174
- this._updateWidth();
195
+ // Update width styling (skip for square buttons)
196
+ if (!this.hasAttribute('sq')) {
197
+ this._updateWidth();
198
+ }
175
199
 
176
200
  // Update disabled state
177
201
  const isDisabled = this.hasAttribute('disabled');
@@ -226,15 +250,34 @@ class RettangoliButtonElement extends HTMLElement {
226
250
  lg: 22
227
251
  };
228
252
  const color = colorMap[this.getAttribute("v")] || 'pr-fg';
229
- const size = sizeMap[this.getAttribute("t")] || 18;
253
+
254
+ // For square buttons, use button size (s attribute), otherwise use icon size (t attribute)
255
+ let size = 18; // default
256
+ if (this.hasAttribute('sq')) {
257
+ const buttonSizeMap = {
258
+ sm: 14,
259
+ lg: 22
260
+ };
261
+ const buttonSize = this.getAttribute("s");
262
+ size = buttonSizeMap[buttonSize] || 18;
263
+ } else {
264
+ size = sizeMap[this.getAttribute("t")] || 18;
265
+ }
230
266
 
231
267
  this._iconElement = document.createElement('rtgl-svg');
232
268
  this._iconElement.setAttribute('svg', icon);
233
269
  this._iconElement.setAttribute('c', color);
234
270
  this._iconElement.setAttribute('wh', size.toString());
235
271
 
236
- // Insert icon before slot
237
- this._buttonElement.insertBefore(this._iconElement, this._slotElement);
272
+ // Insert icon based on position (default is right, 's' means start/left)
273
+ const iconPosition = this.getAttribute("ip");
274
+ if (iconPosition === 's') {
275
+ // Insert icon before slot (left position)
276
+ this._buttonElement.insertBefore(this._iconElement, this._slotElement);
277
+ } else {
278
+ // Insert icon after slot (right position - default)
279
+ this._buttonElement.appendChild(this._iconElement);
280
+ }
238
281
  }
239
282
  }
240
283
 
@@ -83,6 +83,7 @@ class RettangoliViewElement extends HTMLElement {
83
83
  return [
84
84
  "href",
85
85
  "target",
86
+ "op",
86
87
  ...permutateBreakpoints([
87
88
  ...styleMapKeys,
88
89
  "wh",
@@ -141,6 +142,7 @@ class RettangoliViewElement extends HTMLElement {
141
142
  this._updateDOM();
142
143
  return;
143
144
  }
145
+
144
146
  // Reset styles for fresh calculation
145
147
  this._styles = {
146
148
  default: {},