@rettangoli/ui 1.5.0 → 1.6.0

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": "1.5.0",
3
+ "version": "1.6.0",
4
4
  "description": "A UI component library for building web interfaces.",
5
5
  "main": "dist/rettangoli-esm.min.js",
6
6
  "type": "module",
@@ -1,4 +1,16 @@
1
1
 
2
+ const getItemType = (item = {}) => {
3
+ if (item.type === 'section' || item.type === 'label') {
4
+ return 'section';
5
+ }
6
+
7
+ if (item.type === 'separator') {
8
+ return 'separator';
9
+ }
10
+
11
+ return 'item';
12
+ };
13
+
2
14
  export const handleClosePopover = (deps, payload) => {
3
15
  const { dispatchEvent } = deps;
4
16
  dispatchEvent(new CustomEvent('close'));
@@ -9,7 +21,7 @@ export const handleClickMenuItem = (deps, payload) => {
9
21
  const event = payload._event;
10
22
  const index = Number(event.currentTarget.dataset.index ?? event.currentTarget.id.slice('option'.length));
11
23
  const item = props.items[index];
12
- const itemType = item?.type || 'item';
24
+ const itemType = getItemType(item);
13
25
 
14
26
  if (!item || itemType !== 'item' || item.disabled) {
15
27
  event.preventDefault();
@@ -12,9 +12,10 @@ propsSchema:
12
12
  type:
13
13
  type: string
14
14
  enum:
15
- - label
15
+ - section
16
16
  - item
17
17
  - separator
18
+ - label
18
19
  id:
19
20
  type: string
20
21
  icon:
@@ -3,12 +3,23 @@ export const createInitialState = () => Object.freeze({
3
3
  });
4
4
 
5
5
  const escapeAttrValue = (value) => `${value}`.replace(/"/g, '"');
6
+ const getItemType = (item = {}) => {
7
+ if (item.type === 'section' || item.type === 'label') {
8
+ return 'section';
9
+ }
10
+
11
+ if (item.type === 'separator') {
12
+ return 'separator';
13
+ }
14
+
15
+ return 'item';
16
+ };
6
17
 
7
18
  const normalizeItems = (items) => {
8
19
  return items.map((item, index) => {
9
- const type = item.type || 'item';
20
+ const type = getItemType(item);
10
21
  const isSeparator = type === 'separator';
11
- const isLabel = type === 'label';
22
+ const isSection = type === 'section';
12
23
  const isItem = type === 'item';
13
24
  const isDisabled = !!item.disabled;
14
25
  const isInteractive = isItem && !isDisabled;
@@ -34,7 +45,7 @@ const normalizeItems = (items) => {
34
45
  index,
35
46
  type,
36
47
  isSeparator,
37
- isLabel,
48
+ isSection,
38
49
  isItem,
39
50
  isDisabled,
40
51
  isInteractive,
@@ -20,7 +20,7 @@ styles:
20
20
  template:
21
21
  - rtgl-popover#popover ?open=${open} x=${x} y=${y} place=${place} content-w=${w} content-h=${h} content-sv=true content-g=xs content-pv=sm:
22
22
  - $for item, i in items:
23
- - $if item.isLabel:
23
+ - $if item.isSection:
24
24
  - rtgl-view w=f p=md:
25
25
  - rtgl-text s=xs c=mu-fg: ${item.label}
26
26
  $elif item.isItem && item.isDisabled:
@@ -1,10 +1,22 @@
1
1
  import { deepEqual } from '../../common.js';
2
2
 
3
+ const getOptionType = (option = {}) => {
4
+ if (option.type === 'section') {
5
+ return 'section';
6
+ }
7
+
8
+ if (option.type === 'separator') {
9
+ return 'separator';
10
+ }
11
+
12
+ return 'item';
13
+ };
14
+
3
15
  export const handleBeforeMount = (deps) => {
4
16
  const { store, props, render } = deps;
5
17
 
6
18
  if (props.selectedValue !== null && props.selectedValue !== undefined && props.options) {
7
- const selectedOption = props.options.find(opt => deepEqual(opt.value, props.selectedValue));
19
+ const selectedOption = props.options.find((opt) => getOptionType(opt) === 'item' && deepEqual(opt.value, props.selectedValue));
8
20
  if (selectedOption) {
9
21
  store.updateSelectedValue({
10
22
  value: selectedOption?.value
@@ -48,7 +60,7 @@ export const handleButtonClick = (deps, payload) => {
48
60
  const currentValue = storeSelectedValue !== null ? storeSelectedValue : props.selectedValue;
49
61
  let selectedIndex = null;
50
62
  if (currentValue !== null && currentValue !== undefined && props.options) {
51
- selectedIndex = props.options.findIndex(opt => deepEqual(opt.value, currentValue));
63
+ selectedIndex = props.options.findIndex((opt) => getOptionType(opt) === 'item' && deepEqual(opt.value, currentValue));
52
64
  if (selectedIndex === -1) selectedIndex = null;
53
65
  }
54
66
 
@@ -87,6 +99,9 @@ export const handleOptionClick = (deps, payload) => {
87
99
  const index = Number(id);
88
100
 
89
101
  const option = props.options[id];
102
+ if (getOptionType(option) !== 'item') {
103
+ return;
104
+ }
90
105
 
91
106
  // Update internal state
92
107
  store.updateSelectedValue({ value: option?.value });
@@ -9,6 +9,12 @@ propsSchema:
9
9
  items:
10
10
  type: object
11
11
  properties:
12
+ type:
13
+ type: string
14
+ enum:
15
+ - section
16
+ - item
17
+ - separator
12
18
  label:
13
19
  type: string
14
20
  value:
@@ -25,6 +25,18 @@ const stringifyProps = (props = {}) => {
25
25
  };
26
26
 
27
27
  const hasOwnProp = (object, key) => Object.prototype.hasOwnProperty.call(object || {}, key);
28
+ const getOptionType = (option = {}) => {
29
+ if (option.type === 'section') {
30
+ return 'section';
31
+ }
32
+
33
+ if (option.type === 'separator') {
34
+ return 'separator';
35
+ }
36
+
37
+ return 'item';
38
+ };
39
+ const isSelectableOption = (option = {}) => getOptionType(option) === 'item';
28
40
 
29
41
  const getOptionIcon = (option = {}) => {
30
42
  return typeof option.icon === 'string' && option.icon.length > 0 ? option.icon : '';
@@ -43,6 +55,33 @@ const getOptionSuffixText = (option = {}) => {
43
55
  };
44
56
 
45
57
  const normalizeOption = (option = {}, index, currentValue, hoveredOptionId, hasIconColumn) => {
58
+ const type = getOptionType(option);
59
+ const isSection = type === 'section';
60
+ const isSeparator = type === 'separator';
61
+ const isItem = type === 'item';
62
+
63
+ if (isSection) {
64
+ return {
65
+ ...option,
66
+ index,
67
+ type,
68
+ isSection,
69
+ isSeparator,
70
+ isItem,
71
+ };
72
+ }
73
+
74
+ if (isSeparator) {
75
+ return {
76
+ ...option,
77
+ index,
78
+ type,
79
+ isSection,
80
+ isSeparator,
81
+ isItem,
82
+ };
83
+ }
84
+
46
85
  const isSelected = deepEqual(option.value, currentValue);
47
86
  const isHovered = hoveredOptionId === index;
48
87
  const icon = getOptionIcon(option);
@@ -50,6 +89,11 @@ const normalizeOption = (option = {}, index, currentValue, hoveredOptionId, hasI
50
89
 
51
90
  return {
52
91
  ...option,
92
+ index,
93
+ type,
94
+ isSection,
95
+ isSeparator,
96
+ isItem,
53
97
  isSelected,
54
98
  bgc: isHovered ? 'ac' : (isSelected ? 'mu' : ''),
55
99
  hasIconSlot: hasIconColumn,
@@ -88,17 +132,17 @@ export const selectViewData = ({ state, props }) => {
88
132
  let isPlaceholderLabel = true;
89
133
 
90
134
  const options = props.options || [];
91
- const selectedOption = options.find((opt) => deepEqual(opt.value, currentValue));
135
+ const selectedOption = options.find((opt) => isSelectableOption(opt) && deepEqual(opt.value, currentValue));
92
136
  if (selectedOption) {
93
137
  displayLabel = selectedOption.label;
94
138
  isPlaceholderLabel = false;
95
139
  }
96
140
 
97
- const hasIconColumn = options.some((option) => hasOwnProp(option, 'icon'));
141
+ const hasIconColumn = options.some((option) => isSelectableOption(option) && hasOwnProp(option, 'icon'));
98
142
  const optionsWithSelection = options.map((option, index) => {
99
143
  return normalizeOption(option, index, currentValue, state.hoveredOptionId, hasIconColumn);
100
144
  });
101
- const selectedOptionView = optionsWithSelection.find((option) => option.isSelected);
145
+ const selectedOptionView = optionsWithSelection.find((option) => option.isItem && option.isSelected);
102
146
 
103
147
  return {
104
148
  containerAttrString,
@@ -48,17 +48,23 @@ template:
48
48
  - rtgl-svg svg=chevronDown wh=16 c=mu-fg: null
49
49
  - rtgl-popover#popover ?open=${isOpen} x=${position.x} y=${position.y} place=rs content-wh=300 content-g=xs content-sv=true content-pv=sm:
50
50
  - $for option, i in options:
51
- - rtgl-view#option${i} w=f ph=lg pv=md cur=pointer bgc=${option.bgc} data-testid=${option.testId}:
52
- - rtgl-view d=h av=c w=f g=md:
53
- - rtgl-view d=h av=c g=md w=1fg:
54
- - $if option.hasIconSlot:
55
- - $if option.hasIcon:
56
- - rtgl-svg wh=16 svg=${option.icon} c=${option.iconColor}: null
57
- $else:
58
- - div class=icon-placeholder aria-hidden="true": null
59
- - rtgl-text s=sm c=${option.c} w=1fg ellipsis: ${option.label}
60
- - $if option.hasSuffixText:
61
- - rtgl-text s=xs c=${option.suffixTextColor} ta=e: ${option.suffixText}
51
+ - $if option.isSection:
52
+ - rtgl-view w=f p=md:
53
+ - rtgl-text s=xs c=mu-fg: ${option.label}
54
+ $elif option.isItem:
55
+ - rtgl-view#option${option.index} w=f ph=lg pv=md cur=pointer bgc=${option.bgc} data-testid=${option.testId}:
56
+ - rtgl-view d=h av=c w=f g=md:
57
+ - rtgl-view d=h av=c g=md w=1fg:
58
+ - $if option.hasIconSlot:
59
+ - $if option.hasIcon:
60
+ - rtgl-svg wh=16 svg=${option.icon} c=${option.iconColor}: null
61
+ $else:
62
+ - div class=icon-placeholder aria-hidden="true": null
63
+ - rtgl-text s=sm c=${option.c} w=1fg ellipsis: ${option.label}
64
+ - $if option.hasSuffixText:
65
+ - rtgl-text s=xs c=${option.suffixTextColor} ta=e: ${option.suffixText}
66
+ $elif option.isSeparator:
67
+ - rtgl-view w=f h=1 bgc=mu mv=sm: null
62
68
  - $if showAddOption:
63
69
  - rtgl-view w=f bw=xs bc=mu bwt=sm: null
64
70
  - rtgl-view#optionAdd w=f ph=lg pv=md cur=pointer bgc=${addOptionBgc} data-testid="select-add-option":