@rettangoli/ui 1.0.21 → 1.0.22
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/rettangoli-iife-layout.min.js +2 -2
- package/dist/rettangoli-iife-ui.min.js +39 -39
- package/package.json +1 -1
- package/src/components/sidebar/sidebar.handlers.js +43 -1
- package/src/components/sidebar/sidebar.schema.yaml +9 -0
- package/src/components/sidebar/sidebar.store.js +66 -10
- package/src/components/sidebar/sidebar.view.yaml +17 -6
- package/src/components/tooltip/tooltip.schema.yaml +3 -0
- package/src/components/tooltip/tooltip.store.js +29 -1
- package/src/components/tooltip/tooltip.view.yaml +3 -3
- package/src/primitives/popover.js +2 -2
package/package.json
CHANGED
|
@@ -24,10 +24,12 @@ export const handleHeaderClick = (deps, payload) => {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
export const handleItemClick = (deps, payload) => {
|
|
27
|
-
const { store, dispatchEvent } = deps;
|
|
27
|
+
const { store, dispatchEvent, render } = deps;
|
|
28
28
|
const event = payload._event;
|
|
29
29
|
const id = event.currentTarget.dataset.itemId || event.currentTarget.id.slice('item'.length);
|
|
30
30
|
const item = store.selectItem(id);
|
|
31
|
+
store.hideTooltip({});
|
|
32
|
+
render();
|
|
31
33
|
dispatchEvent(new CustomEvent('item-click', {
|
|
32
34
|
detail: {
|
|
33
35
|
item,
|
|
@@ -36,3 +38,43 @@ export const handleItemClick = (deps, payload) => {
|
|
|
36
38
|
composed: true
|
|
37
39
|
}));
|
|
38
40
|
}
|
|
41
|
+
|
|
42
|
+
export const handleItemMouseEnter = (deps, payload) => {
|
|
43
|
+
const { props, store, render } = deps;
|
|
44
|
+
const showCompactTooltip = props.showCompactTooltip === true ||
|
|
45
|
+
props.showCompactTooltip === '' ||
|
|
46
|
+
props.showCompactTooltip === 'true';
|
|
47
|
+
|
|
48
|
+
if (!showCompactTooltip || (props.mode || 'full') === 'full') {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const event = payload._event;
|
|
53
|
+
const id = event.currentTarget.dataset.itemId || event.currentTarget.id.slice('item'.length);
|
|
54
|
+
const item = store.selectItem(id);
|
|
55
|
+
|
|
56
|
+
if (!item || item.type === 'groupLabel') {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const rect = event.currentTarget.getBoundingClientRect();
|
|
61
|
+
const content = item.tooltip || item.label || item.title;
|
|
62
|
+
|
|
63
|
+
if (!content) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
store.showTooltip({
|
|
68
|
+
x: rect.right,
|
|
69
|
+
y: rect.top + rect.height / 2,
|
|
70
|
+
place: 'r',
|
|
71
|
+
content,
|
|
72
|
+
});
|
|
73
|
+
render();
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export const handleItemMouseLeave = (deps) => {
|
|
77
|
+
const { store, render } = deps;
|
|
78
|
+
store.hideTooltip({});
|
|
79
|
+
render();
|
|
80
|
+
};
|
|
@@ -7,6 +7,9 @@ propsSchema:
|
|
|
7
7
|
hideHeader:
|
|
8
8
|
type: boolean
|
|
9
9
|
default: false
|
|
10
|
+
showCompactTooltip:
|
|
11
|
+
type: boolean
|
|
12
|
+
default: false
|
|
10
13
|
w:
|
|
11
14
|
type: string
|
|
12
15
|
bwr:
|
|
@@ -38,12 +41,18 @@ propsSchema:
|
|
|
38
41
|
items:
|
|
39
42
|
type: object
|
|
40
43
|
properties:
|
|
44
|
+
label:
|
|
45
|
+
type: string
|
|
41
46
|
title:
|
|
42
47
|
type: string
|
|
48
|
+
description: Deprecated item text field. Use label instead.
|
|
43
49
|
slug:
|
|
44
50
|
type: string
|
|
45
51
|
type:
|
|
46
52
|
type: string
|
|
53
|
+
description: Item row type. Supports item, groupLabel, divider, and spacer.
|
|
54
|
+
tooltip:
|
|
55
|
+
type: string
|
|
47
56
|
items:
|
|
48
57
|
type: array
|
|
49
58
|
testId:
|
|
@@ -1,6 +1,14 @@
|
|
|
1
|
-
export const createInitialState = () => Object.freeze({
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
export const createInitialState = () => Object.freeze({
|
|
2
|
+
tooltipState: {
|
|
3
|
+
open: false,
|
|
4
|
+
x: 0,
|
|
5
|
+
y: 0,
|
|
6
|
+
place: 'r',
|
|
7
|
+
content: '',
|
|
8
|
+
},
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const blacklistedAttrs = ['id', 'class', 'style', 'slot', 'header', 'items', 'selectedItemId', 'mode', 'hideHeader', 'showCompactTooltip', 'w', 'bwr'];
|
|
4
12
|
|
|
5
13
|
const stringifyAttrs = (props = {}) => {
|
|
6
14
|
return Object.entries(props).filter(([key]) => !blacklistedAttrs.includes(key)).map(([key, value]) => `${key}=${value}`).join(' ');
|
|
@@ -49,20 +57,35 @@ const resolveSidebarWidth = (value, mode) => {
|
|
|
49
57
|
return mode === 'full' ? 272 : 64;
|
|
50
58
|
};
|
|
51
59
|
|
|
60
|
+
const resolveItemLabel = (item = {}) => {
|
|
61
|
+
if (item.label !== undefined && item.label !== null) {
|
|
62
|
+
return item.label;
|
|
63
|
+
}
|
|
64
|
+
if (item.title !== undefined && item.title !== null) {
|
|
65
|
+
return item.title;
|
|
66
|
+
}
|
|
67
|
+
return '';
|
|
68
|
+
};
|
|
69
|
+
|
|
52
70
|
function flattenItems(items, selectedItemId = null) {
|
|
53
71
|
let result = [];
|
|
54
72
|
|
|
55
73
|
for (const item of items) {
|
|
56
74
|
const itemId = item.id || item.href || item.path;
|
|
57
|
-
const isSelected = selectedItemId === itemId;
|
|
75
|
+
const isSelected = itemId !== undefined && itemId !== null && selectedItemId === itemId;
|
|
76
|
+
const label = resolveItemLabel(item);
|
|
58
77
|
|
|
59
|
-
//
|
|
78
|
+
// Normalize all sidebar rows to a single shape so the view can branch on type.
|
|
60
79
|
result.push({
|
|
61
80
|
id: itemId,
|
|
62
|
-
|
|
81
|
+
label,
|
|
82
|
+
title: item.title ?? label,
|
|
63
83
|
href: item.href,
|
|
64
84
|
type: item.type || 'item',
|
|
65
85
|
icon: item.icon,
|
|
86
|
+
testId: item.testId,
|
|
87
|
+
tooltip: item.tooltip,
|
|
88
|
+
path: item.path,
|
|
66
89
|
hrefAttr: item.href ? `href=${item.href}` : '',
|
|
67
90
|
isSelected,
|
|
68
91
|
itemBgc: isSelected ? 'ac' : 'bg',
|
|
@@ -73,14 +96,19 @@ function flattenItems(items, selectedItemId = null) {
|
|
|
73
96
|
if (item.items && Array.isArray(item.items)) {
|
|
74
97
|
for (const subItem of item.items) {
|
|
75
98
|
const subItemId = subItem.id || subItem.href || subItem.path;
|
|
76
|
-
const isSubSelected = selectedItemId === subItemId;
|
|
99
|
+
const isSubSelected = subItemId !== undefined && subItemId !== null && selectedItemId === subItemId;
|
|
100
|
+
const label = resolveItemLabel(subItem);
|
|
77
101
|
|
|
78
102
|
result.push({
|
|
79
103
|
id: subItemId,
|
|
80
|
-
|
|
104
|
+
label,
|
|
105
|
+
title: subItem.title ?? label,
|
|
81
106
|
href: subItem.href,
|
|
82
107
|
type: subItem.type || 'item',
|
|
83
108
|
icon: subItem.icon,
|
|
109
|
+
testId: subItem.testId,
|
|
110
|
+
tooltip: subItem.tooltip,
|
|
111
|
+
path: subItem.path,
|
|
84
112
|
hrefAttr: subItem.href ? `href=${subItem.href}` : '',
|
|
85
113
|
isSelected: isSubSelected,
|
|
86
114
|
itemBgc: isSubSelected ? 'ac' : 'bg',
|
|
@@ -93,7 +121,7 @@ function flattenItems(items, selectedItemId = null) {
|
|
|
93
121
|
return result;
|
|
94
122
|
}
|
|
95
123
|
|
|
96
|
-
export const selectViewData = ({ props }) => {
|
|
124
|
+
export const selectViewData = ({ state, props }) => {
|
|
97
125
|
const resolvedHeader = parseMaybeEncodedJson(props.header) || props.header;
|
|
98
126
|
const resolvedItems = parseMaybeEncodedJson(props.items) || props.items;
|
|
99
127
|
const selectedItemId = props.selectedItemId;
|
|
@@ -114,6 +142,7 @@ export const selectViewData = ({ props }) => {
|
|
|
114
142
|
const items = resolvedItems ? flattenItems(resolvedItems, selectedItemId) : [];
|
|
115
143
|
|
|
116
144
|
const showHeader = !parseBooleanProp(props.hideHeader);
|
|
145
|
+
const showCompactTooltip = parseBooleanProp(props.showCompactTooltip);
|
|
117
146
|
const rightBorderWidth = props.bwr || 'xs';
|
|
118
147
|
// Computed values based on mode
|
|
119
148
|
const sidebarWidth = resolveSidebarWidth(props.w, mode);
|
|
@@ -126,6 +155,7 @@ export const selectViewData = ({ props }) => {
|
|
|
126
155
|
const firstLetterSize = mode === 'shrunk-lg' ? 'md' : 'sm';
|
|
127
156
|
const showLabels = mode === 'full';
|
|
128
157
|
const showGroupLabels = mode === 'full';
|
|
158
|
+
const enableCompactTooltip = showCompactTooltip && !showLabels;
|
|
129
159
|
|
|
130
160
|
// For items with icons in full mode, we need left alignment within the container
|
|
131
161
|
// but the container itself should use flex-start alignment
|
|
@@ -141,7 +171,7 @@ export const selectViewData = ({ props }) => {
|
|
|
141
171
|
const headerWidth = itemWidth;
|
|
142
172
|
|
|
143
173
|
const ah = mode === 'shrunk-lg' || mode === 'shrunk-md' ? 'c' : '';
|
|
144
|
-
const listAttrString = mode === 'full' ? 'sv' : 'sv hsb';
|
|
174
|
+
const listAttrString = mode === 'full' ? 'd=v sv' : 'd=v sv hsb';
|
|
145
175
|
|
|
146
176
|
return {
|
|
147
177
|
containerAttrString,
|
|
@@ -166,7 +196,15 @@ export const selectViewData = ({ props }) => {
|
|
|
166
196
|
ah,
|
|
167
197
|
listAttrString,
|
|
168
198
|
showHeader,
|
|
199
|
+
enableCompactTooltip,
|
|
169
200
|
rightBorderWidth,
|
|
201
|
+
tooltipState: state.tooltipState || {
|
|
202
|
+
open: false,
|
|
203
|
+
x: 0,
|
|
204
|
+
y: 0,
|
|
205
|
+
place: 'r',
|
|
206
|
+
content: '',
|
|
207
|
+
},
|
|
170
208
|
};
|
|
171
209
|
}
|
|
172
210
|
|
|
@@ -189,3 +227,21 @@ export const selectItem = ({ props }, id) => {
|
|
|
189
227
|
export const setState = ({ state }) => {
|
|
190
228
|
// State management if needed
|
|
191
229
|
};
|
|
230
|
+
|
|
231
|
+
export const showTooltip = ({ state }, payload = {}) => {
|
|
232
|
+
const { x, y, place = 'r', content = '' } = payload;
|
|
233
|
+
state.tooltipState = {
|
|
234
|
+
open: true,
|
|
235
|
+
x,
|
|
236
|
+
y,
|
|
237
|
+
place,
|
|
238
|
+
content,
|
|
239
|
+
};
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
export const hideTooltip = ({ state }) => {
|
|
243
|
+
state.tooltipState = {
|
|
244
|
+
...state.tooltipState,
|
|
245
|
+
open: false,
|
|
246
|
+
};
|
|
247
|
+
};
|
|
@@ -15,6 +15,10 @@ refs:
|
|
|
15
15
|
eventListeners:
|
|
16
16
|
click:
|
|
17
17
|
handler: handleItemClick
|
|
18
|
+
mouseenter:
|
|
19
|
+
handler: handleItemMouseEnter
|
|
20
|
+
mouseleave:
|
|
21
|
+
handler: handleItemMouseLeave
|
|
18
22
|
anchors:
|
|
19
23
|
- - $if header.image && header.image.src:
|
|
20
24
|
- $if header.image.href:
|
|
@@ -77,24 +81,31 @@ template:
|
|
|
77
81
|
- rtgl-text s=lg: ${header.label}
|
|
78
82
|
- rtgl-view w=f h=1fg ph=${headerPadding} pb=lg g=xs ah=${ah} ${listAttrString}:
|
|
79
83
|
- $for item, i in items:
|
|
80
|
-
- $if item.type == "
|
|
84
|
+
- $if item.type == "divider":
|
|
85
|
+
- rtgl-view w=f pv=md:
|
|
86
|
+
- rtgl-view h=1 w=f bgc=mu: null
|
|
87
|
+
$elif item.type == "spacer":
|
|
88
|
+
- rtgl-view h=1fg w=f: null
|
|
89
|
+
$elif item.type == "groupLabel":
|
|
81
90
|
- $if showGroupLabels:
|
|
82
91
|
- rtgl-view mt=md h=32 av=c ph=md:
|
|
83
|
-
- rtgl-text s=xs c=mu-fg: ${item.
|
|
92
|
+
- rtgl-text s=xs c=mu-fg: ${item.label}
|
|
84
93
|
$else:
|
|
85
94
|
- rtgl-view mt=md h=1 bgc=mu: null
|
|
86
95
|
$else:
|
|
87
|
-
- rtgl-view#item${i} data-item-id=${item.id} ${item.hrefAttr} h=${itemHeight} av=c ${itemAlignAttr} ph=${itemPadding} w=${itemWidth} h-bgc=${item.itemHoverBgc} br=lg bgc=${item.itemBgc} cur=pointer data-testid=${item.testId}:
|
|
96
|
+
- rtgl-view#item${i} data-item-id=${item.id} ${item.hrefAttr} h=${itemHeight} av=c ${itemAlignAttr} ph=${itemPadding} w=${itemWidth} h-bgc=${item.itemHoverBgc} br=lg bgc=${item.itemBgc} cur=pointer data-testid=${item.testId} aria-label="${item.label}":
|
|
88
97
|
- $if item.icon:
|
|
89
98
|
- $if showLabels:
|
|
90
99
|
- rtgl-view d=h ah=${itemContentAlign} g=sm:
|
|
91
100
|
- rtgl-svg wh=16 svg=${item.icon} c=fg: null
|
|
92
|
-
- rtgl-text s=sm: ${item.
|
|
101
|
+
- rtgl-text s=sm: ${item.label}
|
|
93
102
|
$else:
|
|
94
103
|
- rtgl-svg wh=${iconSize} svg=${item.icon} c=fg: null
|
|
95
104
|
$else:
|
|
96
105
|
- $if showLabels:
|
|
97
|
-
- rtgl-text s=sm: ${item.
|
|
106
|
+
- rtgl-text s=sm: ${item.label}
|
|
98
107
|
$else:
|
|
99
108
|
- rtgl-view wh=${iconSize} br=f bgc=mu av=c ah=c:
|
|
100
|
-
- rtgl-text s=${firstLetterSize} c=fg: ${item.
|
|
109
|
+
- rtgl-text s=${firstLetterSize} c=fg: ${item.label.charAt(0).toUpperCase()}
|
|
110
|
+
- $if enableCompactTooltip:
|
|
111
|
+
- rtgl-tooltip#tooltip s="md" ?open=${tooltipState.open} x=${tooltipState.x} y=${tooltipState.y} place=${tooltipState.place} content="${tooltipState.content}": null
|
|
@@ -1,12 +1,40 @@
|
|
|
1
1
|
export const createInitialState = () => Object.freeze({
|
|
2
2
|
});
|
|
3
3
|
|
|
4
|
+
const sizePresets = {
|
|
5
|
+
sm: {
|
|
6
|
+
textSize: 'sm',
|
|
7
|
+
paddingX: 'md',
|
|
8
|
+
paddingY: 'sm',
|
|
9
|
+
maxWidth: 'min(320px, calc(100vw - 16px))',
|
|
10
|
+
},
|
|
11
|
+
md: {
|
|
12
|
+
textSize: 'sm',
|
|
13
|
+
paddingX: 'lg',
|
|
14
|
+
paddingY: 'md',
|
|
15
|
+
maxWidth: 'min(360px, calc(100vw - 16px))',
|
|
16
|
+
},
|
|
17
|
+
lg: {
|
|
18
|
+
textSize: 'md',
|
|
19
|
+
paddingX: 'lg',
|
|
20
|
+
paddingY: 'md',
|
|
21
|
+
maxWidth: 'min(420px, calc(100vw - 16px))',
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
|
|
4
25
|
export const selectViewData = ({ props }) => {
|
|
26
|
+
const size = sizePresets[props.s] ? props.s : 'sm';
|
|
27
|
+
const preset = sizePresets[size];
|
|
28
|
+
|
|
5
29
|
return {
|
|
6
30
|
open: !!props.open,
|
|
7
31
|
x: props.x || 0,
|
|
8
32
|
y: props.y || 0,
|
|
9
33
|
place: props.place || 't',
|
|
10
|
-
content: props.content || ''
|
|
34
|
+
content: props.content || '',
|
|
35
|
+
textSize: preset.textSize,
|
|
36
|
+
paddingX: preset.paddingX,
|
|
37
|
+
paddingY: preset.paddingY,
|
|
38
|
+
popoverStyle: `--rtgl-popover-content-padding: 0; --rtgl-popover-content-min-width: 0; --rtgl-popover-content-max-width: ${preset.maxWidth}`,
|
|
11
39
|
};
|
|
12
40
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
template:
|
|
2
|
-
- rtgl-popover#popover ?open=${open} x=${x} y=${y} place=${place} no-overlay:
|
|
3
|
-
- rtgl-view slot=content bc=bo br=md
|
|
4
|
-
- rtgl-text ta=
|
|
2
|
+
- 'rtgl-popover#popover ?open=${open} x=${x} y=${y} place=${place} no-overlay style="${popoverStyle}"':
|
|
3
|
+
- rtgl-view slot=content bc=bo br=md ph=${paddingX} pv=${paddingY} ah=s av=c:
|
|
4
|
+
- rtgl-text ta=s s=${textSize} c=fg: ${content}
|
|
5
5
|
refs: {}
|
|
@@ -57,8 +57,8 @@ class RettangoliPopoverElement extends HTMLElement {
|
|
|
57
57
|
border: 1px solid var(--border);
|
|
58
58
|
border-radius: var(--rtgl-popover-content-border-radius, var(--border-radius-md));
|
|
59
59
|
padding: var(--rtgl-popover-content-padding, var(--spacing-md));
|
|
60
|
-
min-width: 200px;
|
|
61
|
-
max-width: 400px;
|
|
60
|
+
min-width: var(--rtgl-popover-content-min-width, 200px);
|
|
61
|
+
max-width: var(--rtgl-popover-content-max-width, 400px);
|
|
62
62
|
}
|
|
63
63
|
`);
|
|
64
64
|
}
|