@rettangoli/ui 0.1.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/README.md +49 -0
- package/dist/rettangoli-iife-layout.min.js +728 -0
- package/dist/rettangoli-iife-ui.min.js +830 -0
- package/package.json +53 -0
- package/src/common/BaseElement.js +182 -0
- package/src/common.js +190 -0
- package/src/components/dialog/dialog.handlers.js +5 -0
- package/src/components/dialog/dialog.store.js +25 -0
- package/src/components/dialog/dialog.view.yaml +44 -0
- package/src/components/dropdownMenu/dropdownMenu.handlers.js +18 -0
- package/src/components/dropdownMenu/dropdownMenu.store.js +25 -0
- package/src/components/dropdownMenu/dropdownMenu.view.yaml +54 -0
- package/src/components/form/form.handlers.js +63 -0
- package/src/components/form/form.store.js +45 -0
- package/src/components/form/form.view.yaml +174 -0
- package/src/components/navbar/navbar.examples.yaml +86 -0
- package/src/components/navbar/navbar.handlers.js +10 -0
- package/src/components/navbar/navbar.store.js +46 -0
- package/src/components/navbar/navbar.view.yaml +74 -0
- package/src/components/pageOutline/pageOutline.handlers.js +69 -0
- package/src/components/pageOutline/pageOutline.store.js +40 -0
- package/src/components/pageOutline/pageOutline.view.yaml +34 -0
- package/src/components/popover/popover.handlers.js +5 -0
- package/src/components/popover/popover.store.js +12 -0
- package/src/components/popover/popover.view.yaml +57 -0
- package/src/components/select/select.handlers.js +61 -0
- package/src/components/select/select.store.js +65 -0
- package/src/components/select/select.view.yaml +50 -0
- package/src/components/sidebar/sidebar.handlers.js +36 -0
- package/src/components/sidebar/sidebar.store.js +142 -0
- package/src/components/sidebar/sidebar.view.yaml +190 -0
- package/src/components/table/table.handlers.js +55 -0
- package/src/components/table/table.store.js +72 -0
- package/src/components/table/table.view.yaml +103 -0
- package/src/entry-iife-layout.js +15 -0
- package/src/entry-iife-ui.js +22 -0
- package/src/index.js +17 -0
- package/src/lib/uhtml.js +9 -0
- package/src/primitives/button.js +306 -0
- package/src/primitives/colorPicker.js +213 -0
- package/src/primitives/image.js +234 -0
- package/src/primitives/input.js +218 -0
- package/src/primitives/slider.js +269 -0
- package/src/primitives/svg.js +95 -0
- package/src/primitives/text.js +141 -0
- package/src/primitives/textarea.js +103 -0
- package/src/primitives/view.js +217 -0
- package/src/setup.js +16 -0
- package/src/styles/anchorStyles.js +13 -0
- package/src/styles/buttonMarginStyles.js +84 -0
- package/src/styles/cursorStyles.js +12 -0
- package/src/styles/flexChildStyles.js +43 -0
- package/src/styles/flexDirectionStyles.js +74 -0
- package/src/styles/marginStyles.js +13 -0
- package/src/styles/paddingSvgStyles.js +120 -0
- package/src/styles/scrollStyles.js +22 -0
- package/src/styles/textColorStyles.js +14 -0
- package/src/styles/textStyles.js +62 -0
- package/src/styles/viewStyles.js +114 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
export const INITIAL_STATE = Object.freeze({
|
|
2
|
+
isOpen: false,
|
|
3
|
+
position: {
|
|
4
|
+
x: 0,
|
|
5
|
+
y: 0,
|
|
6
|
+
},
|
|
7
|
+
selectedValue: null,
|
|
8
|
+
selectedLabel: null,
|
|
9
|
+
});
|
|
10
|
+
|
|
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
|
+
|
|
34
|
+
return {
|
|
35
|
+
isOpen: state.isOpen,
|
|
36
|
+
position: state.position,
|
|
37
|
+
options: optionsWithSelection,
|
|
38
|
+
selectedValue: currentValue,
|
|
39
|
+
selectedLabel: displayLabel,
|
|
40
|
+
placeholder: props.placeholder || 'Select an option'
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export const selectState = ({ state }) => {
|
|
45
|
+
return state;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export const openOptionsPopover = (state, payload) => {
|
|
49
|
+
const { position } = payload;
|
|
50
|
+
state.position = position;
|
|
51
|
+
state.isOpen = true;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export const closeOptionsPopover = (state) => {
|
|
55
|
+
state.isOpen = false;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export const updateSelectOption = (state, option) => {
|
|
59
|
+
state.selectedValue = option.value;
|
|
60
|
+
state.selectedLabel = option.label;
|
|
61
|
+
state.isOpen = false;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
elementName: rtgl-select
|
|
2
|
+
|
|
3
|
+
viewDataSchema:
|
|
4
|
+
type: object
|
|
5
|
+
|
|
6
|
+
propsSchema:
|
|
7
|
+
type: object
|
|
8
|
+
properties:
|
|
9
|
+
options:
|
|
10
|
+
type: array
|
|
11
|
+
items:
|
|
12
|
+
type: object
|
|
13
|
+
properties:
|
|
14
|
+
id:
|
|
15
|
+
type: string
|
|
16
|
+
label:
|
|
17
|
+
type: string
|
|
18
|
+
value:
|
|
19
|
+
type: any
|
|
20
|
+
selectedValue:
|
|
21
|
+
type: any
|
|
22
|
+
placeholder:
|
|
23
|
+
type: string
|
|
24
|
+
onChange:
|
|
25
|
+
type: function
|
|
26
|
+
|
|
27
|
+
refs:
|
|
28
|
+
select-button:
|
|
29
|
+
eventListeners:
|
|
30
|
+
click:
|
|
31
|
+
handler: handleButtonClick
|
|
32
|
+
popover:
|
|
33
|
+
eventListeners:
|
|
34
|
+
click-overlay:
|
|
35
|
+
handler: handleClickOptionsPopoverOverlay
|
|
36
|
+
option-*:
|
|
37
|
+
eventListeners:
|
|
38
|
+
click:
|
|
39
|
+
handler: handleOptionClick
|
|
40
|
+
|
|
41
|
+
events: {}
|
|
42
|
+
|
|
43
|
+
template:
|
|
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:
|
|
48
|
+
- $for option, i in options:
|
|
49
|
+
- rtgl-view#option-${i} w=f h-bgc=ac ph=lg pv=md cur=p br=md bgc=${option.bgc}:
|
|
50
|
+
- rtgl-text: ${option.label}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export const handleHeaderClick = (e, deps) => {
|
|
2
|
+
const { store, dispatchEvent } = deps;
|
|
3
|
+
|
|
4
|
+
let path;
|
|
5
|
+
|
|
6
|
+
const header = store.selectHeader();
|
|
7
|
+
|
|
8
|
+
if (e.currentTarget.id === 'header-label') {
|
|
9
|
+
path = header.labelPath;
|
|
10
|
+
} else if (e.currentTarget.id === 'header-image') {
|
|
11
|
+
path = header.image.path;
|
|
12
|
+
} else if (e.currentTarget.id === 'header') {
|
|
13
|
+
path = header.path;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
dispatchEvent(new CustomEvent('headerClick', {
|
|
17
|
+
detail: {
|
|
18
|
+
path
|
|
19
|
+
},
|
|
20
|
+
bubbles: true,
|
|
21
|
+
composed: true
|
|
22
|
+
}));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const handleItemClick = (e, deps) => {
|
|
26
|
+
const { store, dispatchEvent } = deps;
|
|
27
|
+
const id = e.currentTarget.id.replace('item-', '');
|
|
28
|
+
const item = store.selectItem(id);
|
|
29
|
+
dispatchEvent(new CustomEvent('itemClick', {
|
|
30
|
+
detail: {
|
|
31
|
+
item,
|
|
32
|
+
},
|
|
33
|
+
bubbles: true,
|
|
34
|
+
composed: true
|
|
35
|
+
}));
|
|
36
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
export const INITIAL_STATE = Object.freeze({});
|
|
2
|
+
|
|
3
|
+
const blacklistedAttrs = ['id', 'class', 'style', 'slot'];
|
|
4
|
+
|
|
5
|
+
const stringifyAttrs = (attrs) => {
|
|
6
|
+
return Object.entries(attrs).filter(([key]) => !blacklistedAttrs.includes(key)).map(([key, value]) => `${key}=${value}`).join(' ');
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function flattenItems(items, selectedItemId = null) {
|
|
10
|
+
let result = [];
|
|
11
|
+
|
|
12
|
+
for (const item of items) {
|
|
13
|
+
const itemId = item.id || item.href || item.path;
|
|
14
|
+
const isSelected = selectedItemId === itemId;
|
|
15
|
+
|
|
16
|
+
// Add the parent item if it's not just a group label
|
|
17
|
+
result.push({
|
|
18
|
+
id: itemId,
|
|
19
|
+
title: item.title,
|
|
20
|
+
href: item.href,
|
|
21
|
+
type: item.type || 'item',
|
|
22
|
+
icon: item.icon,
|
|
23
|
+
hrefAttr: item.href ? `href=${item.href}` : '',
|
|
24
|
+
isSelected,
|
|
25
|
+
itemBgc: isSelected ? 'ac' : 'bg',
|
|
26
|
+
itemHoverBgc: isSelected ? 'ac' : 'mu',
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// Add child items if they exist
|
|
30
|
+
if (item.items && Array.isArray(item.items)) {
|
|
31
|
+
for (const subItem of item.items) {
|
|
32
|
+
const subItemId = subItem.id || subItem.href || subItem.path;
|
|
33
|
+
const isSubSelected = selectedItemId === subItemId;
|
|
34
|
+
|
|
35
|
+
result.push({
|
|
36
|
+
id: subItemId,
|
|
37
|
+
title: subItem.title,
|
|
38
|
+
href: subItem.href,
|
|
39
|
+
type: subItem.type || 'item',
|
|
40
|
+
icon: subItem.icon,
|
|
41
|
+
hrefAttr: subItem.href ? `href=${subItem.href}` : '',
|
|
42
|
+
isSelected: isSubSelected,
|
|
43
|
+
itemBgc: isSubSelected ? 'ac' : 'bg',
|
|
44
|
+
itemHoverBgc: isSubSelected ? 'ac' : 'mu',
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export const toViewData = ({ state, props, attrs }) => {
|
|
54
|
+
const attrsHeader = attrs.header ? JSON.parse(decodeURIComponent(attrs.header)) : props.header;
|
|
55
|
+
const attrsItems = attrs.items ? JSON.parse(decodeURIComponent(attrs.items)) : props.items;
|
|
56
|
+
const selectedItemId = attrs.selectedItemId || props.selectedItemId;
|
|
57
|
+
|
|
58
|
+
const containerAttrString = stringifyAttrs(attrs);
|
|
59
|
+
const mode = attrs.mode || 'full';
|
|
60
|
+
const header = attrsHeader || {
|
|
61
|
+
label: '',
|
|
62
|
+
path: '',
|
|
63
|
+
image: {
|
|
64
|
+
src: '',
|
|
65
|
+
alt: '',
|
|
66
|
+
width: 0,
|
|
67
|
+
height: 0,
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const items = attrsItems ? flattenItems(attrsItems, selectedItemId) : [];
|
|
72
|
+
|
|
73
|
+
// Computed values based on mode
|
|
74
|
+
const sidebarWidth = mode === 'full' ? 272 : 64;
|
|
75
|
+
const headerAlign = mode === 'full' ? 'fs' : 'c';
|
|
76
|
+
const itemAlign = mode === 'full' ? 'fs' : 'c';
|
|
77
|
+
const headerPadding = mode === 'full' ? 'lg' : 'sm';
|
|
78
|
+
const itemPadding = mode === 'full' ? 'md' : 'sm';
|
|
79
|
+
const itemHeight = mode === 'shrunk-lg' ? 48 : 40;
|
|
80
|
+
const iconSize = mode === 'shrunk-lg' ? 28 : 20;
|
|
81
|
+
const firstLetterSize = mode === 'shrunk-lg' ? 'md' : 'sm';
|
|
82
|
+
const showLabels = mode === 'full';
|
|
83
|
+
const showGroupLabels = mode === 'full';
|
|
84
|
+
|
|
85
|
+
// For items with icons in full mode, we need left alignment within the container
|
|
86
|
+
// but the container itself should use flex-start alignment
|
|
87
|
+
const itemContentAlign = mode === 'full' ? 'fs' : 'c';
|
|
88
|
+
|
|
89
|
+
// Item container alignment - only set for shrunk modes, leave default for full mode
|
|
90
|
+
const itemAlignAttr = mode === 'full' ? '' : `ah=${itemAlign}`;
|
|
91
|
+
|
|
92
|
+
// Item width - for shrunk modes, make it square to constrain the highlight
|
|
93
|
+
const itemWidth = mode === 'full' ? 'f' : itemHeight;
|
|
94
|
+
|
|
95
|
+
// Header width - should match item width for alignment
|
|
96
|
+
const headerWidth = itemWidth;
|
|
97
|
+
|
|
98
|
+
const ah = mode === 'shrunk-lg' || mode === 'shrunk-md' ? 'c' : '';
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
containerAttrString,
|
|
102
|
+
mode,
|
|
103
|
+
header,
|
|
104
|
+
items,
|
|
105
|
+
sidebarWidth,
|
|
106
|
+
headerAlign,
|
|
107
|
+
itemAlign,
|
|
108
|
+
headerPadding,
|
|
109
|
+
itemPadding,
|
|
110
|
+
itemHeight,
|
|
111
|
+
iconSize,
|
|
112
|
+
firstLetterSize,
|
|
113
|
+
showLabels,
|
|
114
|
+
showGroupLabels,
|
|
115
|
+
itemContentAlign,
|
|
116
|
+
itemAlignAttr,
|
|
117
|
+
itemWidth,
|
|
118
|
+
headerWidth,
|
|
119
|
+
selectedItemId,
|
|
120
|
+
ah,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export const selectHeader = ({ state, props, attrs }) => {
|
|
125
|
+
const attrsHeader = attrs.header ? JSON.parse(decodeURIComponent(attrs.header)) : props.header;
|
|
126
|
+
return attrsHeader;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export const selectActiveItem = ({ state, props }) => {
|
|
130
|
+
const items = props.items ? flattenItems(props.items) : [];
|
|
131
|
+
return items.find(item => item.active);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export const selectItem = ({ state, props, attrs }, id) => {
|
|
135
|
+
const attrsItems = attrs.items ? JSON.parse(decodeURIComponent(attrs.items)) : props.items;
|
|
136
|
+
const items = attrsItems ? flattenItems(attrsItems) : [];
|
|
137
|
+
return items.find(item => item.id === id);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export const setState = (state) => {
|
|
141
|
+
// State management if needed
|
|
142
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
elementName: rtgl-sidebar
|
|
2
|
+
|
|
3
|
+
viewDataSchema:
|
|
4
|
+
type: object
|
|
5
|
+
properties:
|
|
6
|
+
containerAttrString:
|
|
7
|
+
type: string
|
|
8
|
+
mode:
|
|
9
|
+
type: string
|
|
10
|
+
enum: ['full', 'shrunk', 'shrunk-lg']
|
|
11
|
+
default: 'full'
|
|
12
|
+
sidebarWidth:
|
|
13
|
+
type: number
|
|
14
|
+
headerAlign:
|
|
15
|
+
type: string
|
|
16
|
+
itemAlign:
|
|
17
|
+
type: string
|
|
18
|
+
headerPadding:
|
|
19
|
+
type: string
|
|
20
|
+
itemPadding:
|
|
21
|
+
type: string
|
|
22
|
+
itemHeight:
|
|
23
|
+
type: number
|
|
24
|
+
iconSize:
|
|
25
|
+
type: number
|
|
26
|
+
firstLetterSize:
|
|
27
|
+
type: string
|
|
28
|
+
showLabels:
|
|
29
|
+
type: boolean
|
|
30
|
+
showGroupLabels:
|
|
31
|
+
type: boolean
|
|
32
|
+
itemContentAlign:
|
|
33
|
+
type: string
|
|
34
|
+
itemAlignAttr:
|
|
35
|
+
type: string
|
|
36
|
+
itemWidth:
|
|
37
|
+
type: string
|
|
38
|
+
headerWidth:
|
|
39
|
+
type: string
|
|
40
|
+
selectedItemId:
|
|
41
|
+
type: string
|
|
42
|
+
header:
|
|
43
|
+
type: object
|
|
44
|
+
properties:
|
|
45
|
+
label:
|
|
46
|
+
type: string
|
|
47
|
+
href:
|
|
48
|
+
type: string
|
|
49
|
+
image:
|
|
50
|
+
type: object
|
|
51
|
+
properties:
|
|
52
|
+
src:
|
|
53
|
+
type: string
|
|
54
|
+
width:
|
|
55
|
+
type: number
|
|
56
|
+
height:
|
|
57
|
+
type: number
|
|
58
|
+
alt:
|
|
59
|
+
type: string
|
|
60
|
+
items:
|
|
61
|
+
type: array
|
|
62
|
+
items:
|
|
63
|
+
type: object
|
|
64
|
+
properties:
|
|
65
|
+
title:
|
|
66
|
+
type: string
|
|
67
|
+
slug:
|
|
68
|
+
type: string
|
|
69
|
+
type:
|
|
70
|
+
type: string
|
|
71
|
+
active:
|
|
72
|
+
type: boolean
|
|
73
|
+
icon:
|
|
74
|
+
type: string
|
|
75
|
+
|
|
76
|
+
propsSchema:
|
|
77
|
+
type: object
|
|
78
|
+
properties:
|
|
79
|
+
selectedItemId:
|
|
80
|
+
type: string
|
|
81
|
+
header:
|
|
82
|
+
type: object
|
|
83
|
+
properties:
|
|
84
|
+
label:
|
|
85
|
+
type: string
|
|
86
|
+
href:
|
|
87
|
+
type: string
|
|
88
|
+
image:
|
|
89
|
+
type: object
|
|
90
|
+
properties:
|
|
91
|
+
src:
|
|
92
|
+
type: string
|
|
93
|
+
width:
|
|
94
|
+
type: number
|
|
95
|
+
height:
|
|
96
|
+
type: number
|
|
97
|
+
alt:
|
|
98
|
+
type: string
|
|
99
|
+
items:
|
|
100
|
+
type: array
|
|
101
|
+
items:
|
|
102
|
+
type: object
|
|
103
|
+
properties:
|
|
104
|
+
title:
|
|
105
|
+
type: string
|
|
106
|
+
slug:
|
|
107
|
+
type: string
|
|
108
|
+
type:
|
|
109
|
+
type: string
|
|
110
|
+
items:
|
|
111
|
+
type: array
|
|
112
|
+
|
|
113
|
+
refs:
|
|
114
|
+
header-image:
|
|
115
|
+
eventListeners:
|
|
116
|
+
click:
|
|
117
|
+
handler: handleHeaderClick
|
|
118
|
+
header-label:
|
|
119
|
+
eventListeners:
|
|
120
|
+
click:
|
|
121
|
+
handler: handleHeaderClick
|
|
122
|
+
header:
|
|
123
|
+
eventListeners:
|
|
124
|
+
click:
|
|
125
|
+
handler: handleHeaderClick
|
|
126
|
+
item-*:
|
|
127
|
+
eventListeners:
|
|
128
|
+
click:
|
|
129
|
+
handler: handleItemClick
|
|
130
|
+
|
|
131
|
+
events:
|
|
132
|
+
headerClick:
|
|
133
|
+
type: object
|
|
134
|
+
properties:
|
|
135
|
+
path:
|
|
136
|
+
type: string
|
|
137
|
+
|
|
138
|
+
anchors:
|
|
139
|
+
- &headerContent
|
|
140
|
+
- $if header.image && header.image.src:
|
|
141
|
+
- $if header.image.href:
|
|
142
|
+
- a href=${header.image.href}:
|
|
143
|
+
- rtgl-image w=${header.image.width} h=${header.image.height} src=${header.image.src} alt="${header.image.alt}":
|
|
144
|
+
$elif header.image.path:
|
|
145
|
+
- rtgl-view#header-image cur=p:
|
|
146
|
+
- rtgl-image w=${header.image.width} h=${header.image.height} src=${header.image.src} alt="${header.image.alt}":
|
|
147
|
+
$else:
|
|
148
|
+
- rtgl-image w=${header.image.width} h=${header.image.height} src=${header.image.src} alt="${header.image.alt}":
|
|
149
|
+
- $if header.label && showLabels:
|
|
150
|
+
- $if header.labelHref:
|
|
151
|
+
- rtgl-text href=${header.labelHref} s=lg: "${header.label}"
|
|
152
|
+
$elif header.labelPath:
|
|
153
|
+
- rtgl-view#header-label cur=p:
|
|
154
|
+
- rtgl-text s=lg: "${header.label}"
|
|
155
|
+
$else:
|
|
156
|
+
- rtgl-text s=lg: "${header.label}"
|
|
157
|
+
|
|
158
|
+
template:
|
|
159
|
+
- rtgl-view h=f w=${sidebarWidth} bwr=xs ah=${ah} ${containerAttrString}:
|
|
160
|
+
- rtgl-view ph=${headerPadding} pv=lg:
|
|
161
|
+
- $if header.href:
|
|
162
|
+
- rtgl-view href=${header.href} d=h av=c ah=${headerAlign} g=lg w=${headerWidth}:
|
|
163
|
+
- *headerContent
|
|
164
|
+
$else:
|
|
165
|
+
- rtgl-view#header d=h av=c ah=${headerAlign} g=lg w=${headerWidth} cur=p:
|
|
166
|
+
- *headerContent
|
|
167
|
+
|
|
168
|
+
- rtgl-view w=f ph=${headerPadding} pb=lg g=xs ah=${ah}:
|
|
169
|
+
- $for item, i in items:
|
|
170
|
+
- $if item.type == "groupLabel":
|
|
171
|
+
- $if showGroupLabels:
|
|
172
|
+
- rtgl-view mt=md h=32 av=c ph=md:
|
|
173
|
+
- rtgl-text s=xs c=mu-fg: "${item.title}"
|
|
174
|
+
$else:
|
|
175
|
+
- rtgl-view mt=md h=1 bgc=mu-bg:
|
|
176
|
+
$else:
|
|
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}":
|
|
178
|
+
- $if item.icon:
|
|
179
|
+
- $if showLabels:
|
|
180
|
+
- rtgl-view d=h ah=${itemContentAlign} g=sm:
|
|
181
|
+
- rtgl-svg wh=16 svg=${item.icon} c=fg:
|
|
182
|
+
- rtgl-text s=sm: "${item.title}"
|
|
183
|
+
$else:
|
|
184
|
+
- rtgl-svg wh=${iconSize} svg=${item.icon} c=fg:
|
|
185
|
+
$else:
|
|
186
|
+
- $if showLabels:
|
|
187
|
+
- rtgl-text s=sm: "${item.title}"
|
|
188
|
+
$else:
|
|
189
|
+
- rtgl-view wh=${iconSize} br=f bgc=mu av=c ah=c:
|
|
190
|
+
- rtgl-text s=${firstLetterSize} c=fg: "${item.title.charAt(0).toUpperCase()}"
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export const handleOnMount = (deps) => {
|
|
2
|
+
// No special initialization needed for basic table
|
|
3
|
+
};
|
|
4
|
+
|
|
5
|
+
export const handleRowClick = (e, deps) => {
|
|
6
|
+
const { dispatchEvent, props } = deps;
|
|
7
|
+
const rowIndex = parseInt(e.currentTarget.id.replace("row-", ""));
|
|
8
|
+
const rowData = props.data?.rows?.[rowIndex];
|
|
9
|
+
|
|
10
|
+
if (rowData) {
|
|
11
|
+
dispatchEvent(
|
|
12
|
+
new CustomEvent("row-click", {
|
|
13
|
+
detail: {
|
|
14
|
+
rowIndex,
|
|
15
|
+
rowData,
|
|
16
|
+
},
|
|
17
|
+
}),
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const handleHeaderClick = (e, deps) => {
|
|
23
|
+
const { store, render, dispatchEvent } = deps;
|
|
24
|
+
const columnKey = e.currentTarget.id.replace("header-", "");
|
|
25
|
+
const currentSort = store.selectSortInfo();
|
|
26
|
+
|
|
27
|
+
// Determine new sort direction
|
|
28
|
+
let newDirection = 'asc';
|
|
29
|
+
if (currentSort.column === columnKey) {
|
|
30
|
+
if (currentSort.direction === 'asc') {
|
|
31
|
+
newDirection = 'desc';
|
|
32
|
+
} else if (currentSort.direction === 'desc') {
|
|
33
|
+
newDirection = null; // Clear sort
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (newDirection) {
|
|
38
|
+
store.setSortColumn({ column: columnKey, direction: newDirection });
|
|
39
|
+
} else {
|
|
40
|
+
store.clearSort();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
render();
|
|
44
|
+
|
|
45
|
+
// Dispatch custom event for external handling
|
|
46
|
+
dispatchEvent(
|
|
47
|
+
new CustomEvent("header-click", {
|
|
48
|
+
detail: {
|
|
49
|
+
column: columnKey,
|
|
50
|
+
direction: newDirection,
|
|
51
|
+
sortInfo: newDirection ? { column: columnKey, direction: newDirection } : null,
|
|
52
|
+
},
|
|
53
|
+
}),
|
|
54
|
+
);
|
|
55
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
export const INITIAL_STATE = Object.freeze({
|
|
2
|
+
sortColumn: null,
|
|
3
|
+
sortDirection: null,
|
|
4
|
+
});
|
|
5
|
+
|
|
6
|
+
const blacklistedAttrs = ['id', 'class', 'style', 'slot'];
|
|
7
|
+
|
|
8
|
+
const stringifyAttrs = (attrs) => {
|
|
9
|
+
return Object.entries(attrs).filter(([key]) => !blacklistedAttrs.includes(key)).map(([key, value]) => `${key}=${value}`).join(' ');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const getNestedValue = (obj, path) => {
|
|
13
|
+
const keys = path.split('.');
|
|
14
|
+
let result = obj;
|
|
15
|
+
|
|
16
|
+
for (const key of keys) {
|
|
17
|
+
if (result === null || result === undefined) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
result = result[key];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return result;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const toViewData = ({ state, props, attrs }) => {
|
|
27
|
+
const containerAttrString = stringifyAttrs(attrs);
|
|
28
|
+
const data = props.data || { columns: [], rows: [] };
|
|
29
|
+
|
|
30
|
+
// Transform rows to create cells array for easier template access
|
|
31
|
+
const transformedRows = data.rows.map((row, rowIndex) => {
|
|
32
|
+
const cells = data.columns.map(column => {
|
|
33
|
+
const value = getNestedValue(row, column.key);
|
|
34
|
+
return {
|
|
35
|
+
key: column.key,
|
|
36
|
+
value: value !== null && value !== undefined ? String(value) : ''
|
|
37
|
+
};
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
index: rowIndex,
|
|
42
|
+
cells: cells
|
|
43
|
+
};
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
containerAttrString,
|
|
48
|
+
columns: data.columns || [],
|
|
49
|
+
rows: transformedRows || [],
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export const selectState = ({ state }) => {
|
|
54
|
+
return state;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export const selectSortInfo = ({ state }) => {
|
|
58
|
+
return {
|
|
59
|
+
column: state.sortColumn,
|
|
60
|
+
direction: state.sortDirection
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export const setSortColumn = (state, { column, direction }) => {
|
|
65
|
+
state.sortColumn = column;
|
|
66
|
+
state.sortDirection = direction;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export const clearSort = (state) => {
|
|
70
|
+
state.sortColumn = null;
|
|
71
|
+
state.sortDirection = null;
|
|
72
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
elementName: rtgl-table
|
|
2
|
+
|
|
3
|
+
viewDataSchema:
|
|
4
|
+
type: object
|
|
5
|
+
properties:
|
|
6
|
+
columns:
|
|
7
|
+
type: array
|
|
8
|
+
rows:
|
|
9
|
+
type: array
|
|
10
|
+
containerAttrString:
|
|
11
|
+
type: string
|
|
12
|
+
|
|
13
|
+
propsSchema:
|
|
14
|
+
type: object
|
|
15
|
+
properties:
|
|
16
|
+
data:
|
|
17
|
+
type: object
|
|
18
|
+
properties:
|
|
19
|
+
columns:
|
|
20
|
+
type: array
|
|
21
|
+
items:
|
|
22
|
+
type: object
|
|
23
|
+
properties:
|
|
24
|
+
key:
|
|
25
|
+
type: string
|
|
26
|
+
label:
|
|
27
|
+
type: string
|
|
28
|
+
rows:
|
|
29
|
+
type: array
|
|
30
|
+
items:
|
|
31
|
+
type: object
|
|
32
|
+
responsive:
|
|
33
|
+
type: boolean
|
|
34
|
+
default: true
|
|
35
|
+
|
|
36
|
+
refs:
|
|
37
|
+
row-*:
|
|
38
|
+
eventListeners:
|
|
39
|
+
click:
|
|
40
|
+
handler: handleRowClick
|
|
41
|
+
header-*:
|
|
42
|
+
eventListeners:
|
|
43
|
+
click:
|
|
44
|
+
handler: handleHeaderClick
|
|
45
|
+
|
|
46
|
+
events: {}
|
|
47
|
+
|
|
48
|
+
styles:
|
|
49
|
+
table:
|
|
50
|
+
width: 100%
|
|
51
|
+
border-collapse: collapse
|
|
52
|
+
border-spacing: 0
|
|
53
|
+
|
|
54
|
+
thead:
|
|
55
|
+
border-bottom: 2px solid var(--border)
|
|
56
|
+
|
|
57
|
+
th:
|
|
58
|
+
padding: 12px
|
|
59
|
+
text-align: left
|
|
60
|
+
font-weight: 500
|
|
61
|
+
color: var(--foreground)
|
|
62
|
+
background-color: var(--muted)
|
|
63
|
+
cursor: pointer
|
|
64
|
+
position: sticky
|
|
65
|
+
top: 0
|
|
66
|
+
z-index: 1
|
|
67
|
+
|
|
68
|
+
tbody tr:
|
|
69
|
+
border-bottom: 1px solid var(--border)
|
|
70
|
+
|
|
71
|
+
tbody tr:last-child:
|
|
72
|
+
border-bottom: none
|
|
73
|
+
|
|
74
|
+
td:
|
|
75
|
+
padding: 12px
|
|
76
|
+
color: var(--foreground)
|
|
77
|
+
|
|
78
|
+
.table-container:
|
|
79
|
+
width: 100%
|
|
80
|
+
height: 100%
|
|
81
|
+
overflow: auto
|
|
82
|
+
|
|
83
|
+
.empty-state:
|
|
84
|
+
text-align: center
|
|
85
|
+
padding: 24px
|
|
86
|
+
color: var(--muted-foreground)
|
|
87
|
+
|
|
88
|
+
template:
|
|
89
|
+
- rtgl-view.table-container ${containerAttrString}:
|
|
90
|
+
- $if rows.length > 0:
|
|
91
|
+
- table:
|
|
92
|
+
- thead:
|
|
93
|
+
- tr:
|
|
94
|
+
- $for column, i in columns:
|
|
95
|
+
- th#header-${column.key}: ${column.label}
|
|
96
|
+
- tbody:
|
|
97
|
+
- $for row, rowIndex in rows:
|
|
98
|
+
- tr#row-${row.index}:
|
|
99
|
+
- $for cell, cellIndex in row.cells:
|
|
100
|
+
- td: ${cell.value}
|
|
101
|
+
$else:
|
|
102
|
+
- rtgl-view.empty-state w=f p=xl:
|
|
103
|
+
- rtgl-text c=mu-fg: No data available
|