@appius-fr/apx 2.6.1 → 2.7.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/APX.mjs +2 -0
- package/README.md +207 -203
- package/dist/2ab50e700c8fbddb45e0.svg +10 -0
- package/dist/2e967d8dd752e0bed703.svg +10 -0
- package/dist/5ddaeefe5dfbc8e09652.svg +7 -0
- package/dist/6dc2907ba3bbb232601d.svg +10 -0
- package/dist/6e1e61dfca176a885b8d.svg +3 -0
- package/dist/6f3a0a27a260bb2c221b.svg +9 -0
- package/dist/8b07a8bf719a38262b7d.svg +10 -0
- package/dist/APX.dev.mjs +843 -245
- package/dist/APX.mjs +1 -1
- package/dist/APX.prod.mjs +1 -1
- package/dist/APX.standalone.js +744 -92
- package/dist/APX.standalone.js.map +1 -1
- package/dist/bdfa755a1cdb872368c7.svg +3 -0
- package/dist/c9da177f7663f9fcd023.svg +10 -0
- package/dist/ce9ef5fceb78e17e68c9.svg +8 -0
- package/dist/ed5af5163957b04bc6cc.svg +7 -0
- package/modules/listen/README.md +242 -235
- package/modules/listen/listen.mjs +1 -3
- package/modules/scrollableTable/README.md +52 -0
- package/modules/scrollableTable/css/scrollableTable.css +60 -0
- package/modules/scrollableTable/scrollableTable.mjs +198 -0
- package/modules/toast/README.md +186 -153
- package/modules/tools/form-packer/README.md +12 -14
- package/modules/tools/form-packer/augment-apx.mjs +4 -2
- package/modules/tools/form-packer/packToJson.mjs +58 -16
- package/modules/tristate/CHANGELOG.md +34 -0
- package/modules/tristate/README.md +157 -94
- package/modules/tristate/assets/tristate-checked.svg +3 -0
- package/modules/tristate/assets/tristate-cross.svg +10 -0
- package/modules/tristate/assets/tristate-crossed.svg +3 -0
- package/modules/tristate/assets/tristate-indeterminate-dash.svg +9 -0
- package/modules/tristate/assets/tristate-tick.svg +10 -0
- package/modules/tristate/assets/tristate-unchecked.svg +7 -0
- package/modules/tristate/css/tristate.css +91 -24
- package/modules/tristate/tristate.mjs +292 -171
- package/package.json +5 -1
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
|
|
2
|
+
<mask id="cross-cutout">
|
|
3
|
+
<rect width="32" height="32" fill="#fff" />
|
|
4
|
+
<path d="M8 8L24 24" stroke="#000" stroke-width="2" />
|
|
5
|
+
<path d="M24 8L8 24" stroke="#000" stroke-width="2" />
|
|
6
|
+
</mask>
|
|
7
|
+
<rect width="32" height="32" fill="#f00" mask="url(#cross-cutout)" />
|
|
8
|
+
</svg>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
|
|
2
|
+
<mask id="frame-cutout">
|
|
3
|
+
<rect width="32" height="32" fill="#fff" />
|
|
4
|
+
<rect x="3" y="3" width="26" height="26" rx="3" ry="3" fill="#000" />
|
|
5
|
+
</mask>
|
|
6
|
+
<rect width="32" height="32" fill="#cccccc" mask="url(#frame-cutout)" />
|
|
7
|
+
</svg>
|
package/modules/listen/README.md
CHANGED
|
@@ -1,235 +1,242 @@
|
|
|
1
|
-
# APX Listen Module
|
|
2
|
-
|
|
3
|
-
The `listen` module provides a powerful event handling system for APX objects. It enables event delegation, multiple callback chaining, debouncing via timeouts, and manual event triggering.
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## Installation
|
|
8
|
-
|
|
9
|
-
The `listen` module is automatically included when you import APX. It augments all APX objects with the `.listen()` and `.trigger()` methods.
|
|
10
|
-
|
|
11
|
-
```javascript
|
|
12
|
-
import APX from './APX.mjs';
|
|
13
|
-
// .listen() and .trigger() are now available on all APX objects
|
|
14
|
-
```
|
|
15
|
-
|
|
16
|
-
---
|
|
17
|
-
|
|
18
|
-
## Usage
|
|
19
|
-
|
|
20
|
-
### Basic Event Listening
|
|
21
|
-
|
|
22
|
-
```javascript
|
|
23
|
-
// Listen to a single event type
|
|
24
|
-
APX('.my-button').listen('click').do((event) => {
|
|
25
|
-
console.log('Button clicked!', event.target);
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
// Listen to multiple event types
|
|
29
|
-
APX('.my-input').listen(['input', 'change']).do((event) => {
|
|
30
|
-
console.log('Input changed:', event.target.value);
|
|
31
|
-
});
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
### Event Delegation
|
|
35
|
-
|
|
36
|
-
Use event delegation to handle events on dynamically added elements:
|
|
37
|
-
|
|
38
|
-
```javascript
|
|
39
|
-
// Listen for clicks on any button inside the container (even if added later)
|
|
40
|
-
APX('#container').listen('click', '.button').do((event) => {
|
|
41
|
-
console.log('Button clicked:', event.target);
|
|
42
|
-
});
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
### Multiple Callbacks (Chaining)
|
|
46
|
-
|
|
47
|
-
Chain multiple callbacks that execute sequentially:
|
|
48
|
-
|
|
49
|
-
```javascript
|
|
50
|
-
APX('.my-button').listen('click').do((event) => {
|
|
51
|
-
console.log('First callback');
|
|
52
|
-
return fetch('/api/data'); // Can return promises
|
|
53
|
-
}).do((event) => {
|
|
54
|
-
console.log('Second callback (runs after first completes)');
|
|
55
|
-
}).do((event) => {
|
|
56
|
-
console.log('Third callback');
|
|
57
|
-
});
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
### Debouncing with Timeout
|
|
61
|
-
|
|
62
|
-
Add a delay before executing callbacks:
|
|
63
|
-
|
|
64
|
-
```javascript
|
|
65
|
-
// Wait 300ms after the last event before executing callbacks
|
|
66
|
-
APX('.search-input').listen('input', { timeout: 300 }).do((event) => {
|
|
67
|
-
console.log('Searching for:', event.target.value);
|
|
68
|
-
// This will only fire 300ms after the user stops typing
|
|
69
|
-
});
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
### Manual Event Triggering
|
|
73
|
-
|
|
74
|
-
Manually trigger events and their registered callbacks:
|
|
75
|
-
|
|
76
|
-
```javascript
|
|
77
|
-
// Trigger a click event
|
|
78
|
-
APX('.my-button').trigger('click');
|
|
79
|
-
|
|
80
|
-
// Or trigger with an actual Event object
|
|
81
|
-
const customEvent = new CustomEvent('myevent', { detail: { data: 'value' } });
|
|
82
|
-
APX('.my-element').trigger(customEvent);
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
---
|
|
86
|
-
|
|
87
|
-
## API
|
|
88
|
-
|
|
89
|
-
### `.listen(eventTypes, selector?, options?)`
|
|
90
|
-
|
|
91
|
-
Adds event listeners to the APX-wrapped elements.
|
|
92
|
-
|
|
93
|
-
**Parameters:**
|
|
94
|
-
- `eventTypes` (string | string[]): The event type(s) to listen for (e.g., `'click'`, `['input', 'change']`)
|
|
95
|
-
- `selector` (string, optional): CSS selector for event delegation
|
|
96
|
-
- `options` (object, optional): Configuration options
|
|
97
|
-
- `timeout` (number): Delay in milliseconds before executing callbacks (default: `0`)
|
|
98
|
-
|
|
99
|
-
**Returns:** An object with a `.do()` method for chaining callbacks.
|
|
100
|
-
|
|
101
|
-
**Example:**
|
|
102
|
-
```javascript
|
|
103
|
-
APX('.element')
|
|
104
|
-
.listen('click', '.button', { timeout: 100 })
|
|
105
|
-
.do((event) => { /* callback 1 */ })
|
|
106
|
-
.do((event) => { /* callback 2 */ });
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
### `.do(callback)`
|
|
110
|
-
|
|
111
|
-
Adds a callback function to the event listener chain. Callbacks execute sequentially, and each callback can return a Promise to delay the next callback.
|
|
112
|
-
|
|
113
|
-
**Parameters:**
|
|
114
|
-
- `callback` (Function): The callback function that receives the event object
|
|
115
|
-
|
|
116
|
-
**Returns:** The same object (for chaining)
|
|
117
|
-
|
|
118
|
-
**Example:**
|
|
119
|
-
```javascript
|
|
120
|
-
APX('.button').listen('click').do((event) => {
|
|
121
|
-
// Callback is bound to the matched element
|
|
122
|
-
console.log(this); // The matched element
|
|
123
|
-
return fetch('/api').then(res => res.json());
|
|
124
|
-
}).do((event) => {
|
|
125
|
-
// This runs after the fetch completes
|
|
126
|
-
console.log('Fetch completed');
|
|
127
|
-
});
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
### `.trigger(event)`
|
|
131
|
-
|
|
132
|
-
Manually triggers an event on all wrapped elements and executes registered callbacks.
|
|
133
|
-
|
|
134
|
-
**Parameters:**
|
|
135
|
-
- `event` (string | Event): Event type string or an Event object
|
|
136
|
-
|
|
137
|
-
**Example:**
|
|
138
|
-
```javascript
|
|
139
|
-
// Trigger with string
|
|
140
|
-
APX('.button').trigger('click');
|
|
141
|
-
|
|
142
|
-
// Trigger with Event object
|
|
143
|
-
const event = new CustomEvent('custom', { detail: { foo: 'bar' } });
|
|
144
|
-
APX('.element').trigger(event);
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
---
|
|
148
|
-
|
|
149
|
-
## Examples
|
|
150
|
-
|
|
151
|
-
### Form Validation with Debouncing
|
|
152
|
-
|
|
153
|
-
```javascript
|
|
154
|
-
APX('#email-input').listen('input', { timeout: 500 }).do((event) => {
|
|
155
|
-
const email = event.target.value;
|
|
156
|
-
if (email.includes('@')) {
|
|
157
|
-
validateEmail(email);
|
|
158
|
-
}
|
|
159
|
-
});
|
|
160
|
-
```
|
|
161
|
-
|
|
162
|
-
### Dynamic Content with Event Delegation
|
|
163
|
-
|
|
164
|
-
```javascript
|
|
165
|
-
// Handle clicks on buttons added dynamically
|
|
166
|
-
APX('#dynamic-container').listen('click', '.action-button').do((event) => {
|
|
167
|
-
const button = event.target;
|
|
168
|
-
const action = button.dataset.action;
|
|
169
|
-
handleAction(action);
|
|
170
|
-
});
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
### Sequential Async Operations
|
|
174
|
-
|
|
175
|
-
```javascript
|
|
176
|
-
APX('#submit-button').listen('click').do(async (event) => {
|
|
177
|
-
// First: Validate form
|
|
178
|
-
const isValid = await validateForm();
|
|
179
|
-
if (!isValid) throw new Error('Validation failed');
|
|
180
|
-
}).do(async (event) => {
|
|
181
|
-
// Second: Submit form (only if validation passed)
|
|
182
|
-
const result = await submitForm();
|
|
183
|
-
return result;
|
|
184
|
-
}).do((event) => {
|
|
185
|
-
// Third: Show success message
|
|
186
|
-
showSuccessMessage();
|
|
187
|
-
});
|
|
188
|
-
```
|
|
189
|
-
|
|
190
|
-
### Multiple Event Types
|
|
191
|
-
|
|
192
|
-
```javascript
|
|
193
|
-
APX('.input-field').listen(['focus', 'blur', 'input']).do((event) => {
|
|
194
|
-
switch (event.type) {
|
|
195
|
-
case 'focus':
|
|
196
|
-
highlightField(event.target);
|
|
197
|
-
break;
|
|
198
|
-
case 'blur':
|
|
199
|
-
validateField(event.target);
|
|
200
|
-
break;
|
|
201
|
-
case 'input':
|
|
202
|
-
updatePreview(event.target.value);
|
|
203
|
-
break;
|
|
204
|
-
}
|
|
205
|
-
});
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
---
|
|
209
|
-
|
|
210
|
-
## Features
|
|
211
|
-
|
|
212
|
-
- ✅ **Event Delegation**: Handle events on dynamically added elements
|
|
213
|
-
- ✅ **Multiple Callbacks**: Chain multiple callbacks that execute sequentially
|
|
214
|
-
- ✅ **Promise Support**: Callbacks can return Promises for async operations
|
|
215
|
-
- ✅ **Debouncing**: Add timeouts to delay callback execution
|
|
216
|
-
- ✅ **Multiple Event Types**: Listen to multiple event types at once
|
|
217
|
-
- ✅ **Manual Triggering**: Programmatically trigger events and callbacks
|
|
218
|
-
- ✅ **Context Binding**: Callbacks are bound to the matched element (`this`)
|
|
219
|
-
|
|
220
|
-
---
|
|
221
|
-
|
|
222
|
-
## Notes
|
|
223
|
-
|
|
224
|
-
- Callbacks execute sequentially; if a callback returns a Promise, the next callback waits for it to resolve
|
|
225
|
-
- Event delegation uses `closest()` to find the matching element, so it works with nested elements
|
|
226
|
-
- Timeouts are cleared and reset on each event, making it perfect for debouncing
|
|
227
|
-
- The `trigger()` method respects event delegation selectors when executing callbacks
|
|
228
|
-
|
|
229
|
-
---
|
|
230
|
-
|
|
231
|
-
##
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
1
|
+
# APX Listen Module
|
|
2
|
+
|
|
3
|
+
The `listen` module provides a powerful event handling system for APX objects. It enables event delegation, multiple callback chaining, debouncing via timeouts, and manual event triggering.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
The `listen` module is automatically included when you import APX. It augments all APX objects with the `.listen()` and `.trigger()` methods.
|
|
10
|
+
|
|
11
|
+
```javascript
|
|
12
|
+
import APX from './APX.mjs';
|
|
13
|
+
// .listen() and .trigger() are now available on all APX objects
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
|
|
20
|
+
### Basic Event Listening
|
|
21
|
+
|
|
22
|
+
```javascript
|
|
23
|
+
// Listen to a single event type
|
|
24
|
+
APX('.my-button').listen('click').do((event) => {
|
|
25
|
+
console.log('Button clicked!', event.target);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// Listen to multiple event types
|
|
29
|
+
APX('.my-input').listen(['input', 'change']).do((event) => {
|
|
30
|
+
console.log('Input changed:', event.target.value);
|
|
31
|
+
});
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Event Delegation
|
|
35
|
+
|
|
36
|
+
Use event delegation to handle events on dynamically added elements:
|
|
37
|
+
|
|
38
|
+
```javascript
|
|
39
|
+
// Listen for clicks on any button inside the container (even if added later)
|
|
40
|
+
APX('#container').listen('click', '.button').do((event) => {
|
|
41
|
+
console.log('Button clicked:', event.target);
|
|
42
|
+
});
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Multiple Callbacks (Chaining)
|
|
46
|
+
|
|
47
|
+
Chain multiple callbacks that execute sequentially:
|
|
48
|
+
|
|
49
|
+
```javascript
|
|
50
|
+
APX('.my-button').listen('click').do((event) => {
|
|
51
|
+
console.log('First callback');
|
|
52
|
+
return fetch('/api/data'); // Can return promises
|
|
53
|
+
}).do((event) => {
|
|
54
|
+
console.log('Second callback (runs after first completes)');
|
|
55
|
+
}).do((event) => {
|
|
56
|
+
console.log('Third callback');
|
|
57
|
+
});
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Debouncing with Timeout
|
|
61
|
+
|
|
62
|
+
Add a delay before executing callbacks:
|
|
63
|
+
|
|
64
|
+
```javascript
|
|
65
|
+
// Wait 300ms after the last event before executing callbacks
|
|
66
|
+
APX('.search-input').listen('input', { timeout: 300 }).do((event) => {
|
|
67
|
+
console.log('Searching for:', event.target.value);
|
|
68
|
+
// This will only fire 300ms after the user stops typing
|
|
69
|
+
});
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Manual Event Triggering
|
|
73
|
+
|
|
74
|
+
Manually trigger events and their registered callbacks:
|
|
75
|
+
|
|
76
|
+
```javascript
|
|
77
|
+
// Trigger a click event
|
|
78
|
+
APX('.my-button').trigger('click');
|
|
79
|
+
|
|
80
|
+
// Or trigger with an actual Event object
|
|
81
|
+
const customEvent = new CustomEvent('myevent', { detail: { data: 'value' } });
|
|
82
|
+
APX('.my-element').trigger(customEvent);
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## API
|
|
88
|
+
|
|
89
|
+
### `.listen(eventTypes, selector?, options?)`
|
|
90
|
+
|
|
91
|
+
Adds event listeners to the APX-wrapped elements.
|
|
92
|
+
|
|
93
|
+
**Parameters:**
|
|
94
|
+
- `eventTypes` (string | string[]): The event type(s) to listen for (e.g., `'click'`, `['input', 'change']`)
|
|
95
|
+
- `selector` (string, optional): CSS selector for event delegation
|
|
96
|
+
- `options` (object, optional): Configuration options
|
|
97
|
+
- `timeout` (number): Delay in milliseconds before executing callbacks (default: `0`)
|
|
98
|
+
|
|
99
|
+
**Returns:** An object with a `.do()` method for chaining callbacks.
|
|
100
|
+
|
|
101
|
+
**Example:**
|
|
102
|
+
```javascript
|
|
103
|
+
APX('.element')
|
|
104
|
+
.listen('click', '.button', { timeout: 100 })
|
|
105
|
+
.do((event) => { /* callback 1 */ })
|
|
106
|
+
.do((event) => { /* callback 2 */ });
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### `.do(callback)`
|
|
110
|
+
|
|
111
|
+
Adds a callback function to the event listener chain. Callbacks execute sequentially, and each callback can return a Promise to delay the next callback.
|
|
112
|
+
|
|
113
|
+
**Parameters:**
|
|
114
|
+
- `callback` (Function): The callback function that receives the event object
|
|
115
|
+
|
|
116
|
+
**Returns:** The same object (for chaining)
|
|
117
|
+
|
|
118
|
+
**Example:**
|
|
119
|
+
```javascript
|
|
120
|
+
APX('.button').listen('click').do((event) => {
|
|
121
|
+
// Callback is bound to the matched element
|
|
122
|
+
console.log(this); // The matched element
|
|
123
|
+
return fetch('/api').then(res => res.json());
|
|
124
|
+
}).do((event) => {
|
|
125
|
+
// This runs after the fetch completes
|
|
126
|
+
console.log('Fetch completed');
|
|
127
|
+
});
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### `.trigger(event)`
|
|
131
|
+
|
|
132
|
+
Manually triggers an event on all wrapped elements and executes registered callbacks.
|
|
133
|
+
|
|
134
|
+
**Parameters:**
|
|
135
|
+
- `event` (string | Event): Event type string or an Event object
|
|
136
|
+
|
|
137
|
+
**Example:**
|
|
138
|
+
```javascript
|
|
139
|
+
// Trigger with string
|
|
140
|
+
APX('.button').trigger('click');
|
|
141
|
+
|
|
142
|
+
// Trigger with Event object
|
|
143
|
+
const event = new CustomEvent('custom', { detail: { foo: 'bar' } });
|
|
144
|
+
APX('.element').trigger(event);
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## Examples
|
|
150
|
+
|
|
151
|
+
### Form Validation with Debouncing
|
|
152
|
+
|
|
153
|
+
```javascript
|
|
154
|
+
APX('#email-input').listen('input', { timeout: 500 }).do((event) => {
|
|
155
|
+
const email = event.target.value;
|
|
156
|
+
if (email.includes('@')) {
|
|
157
|
+
validateEmail(email);
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Dynamic Content with Event Delegation
|
|
163
|
+
|
|
164
|
+
```javascript
|
|
165
|
+
// Handle clicks on buttons added dynamically
|
|
166
|
+
APX('#dynamic-container').listen('click', '.action-button').do((event) => {
|
|
167
|
+
const button = event.target;
|
|
168
|
+
const action = button.dataset.action;
|
|
169
|
+
handleAction(action);
|
|
170
|
+
});
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Sequential Async Operations
|
|
174
|
+
|
|
175
|
+
```javascript
|
|
176
|
+
APX('#submit-button').listen('click').do(async (event) => {
|
|
177
|
+
// First: Validate form
|
|
178
|
+
const isValid = await validateForm();
|
|
179
|
+
if (!isValid) throw new Error('Validation failed');
|
|
180
|
+
}).do(async (event) => {
|
|
181
|
+
// Second: Submit form (only if validation passed)
|
|
182
|
+
const result = await submitForm();
|
|
183
|
+
return result;
|
|
184
|
+
}).do((event) => {
|
|
185
|
+
// Third: Show success message
|
|
186
|
+
showSuccessMessage();
|
|
187
|
+
});
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### Multiple Event Types
|
|
191
|
+
|
|
192
|
+
```javascript
|
|
193
|
+
APX('.input-field').listen(['focus', 'blur', 'input']).do((event) => {
|
|
194
|
+
switch (event.type) {
|
|
195
|
+
case 'focus':
|
|
196
|
+
highlightField(event.target);
|
|
197
|
+
break;
|
|
198
|
+
case 'blur':
|
|
199
|
+
validateField(event.target);
|
|
200
|
+
break;
|
|
201
|
+
case 'input':
|
|
202
|
+
updatePreview(event.target.value);
|
|
203
|
+
break;
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
## Features
|
|
211
|
+
|
|
212
|
+
- ✅ **Event Delegation**: Handle events on dynamically added elements
|
|
213
|
+
- ✅ **Multiple Callbacks**: Chain multiple callbacks that execute sequentially
|
|
214
|
+
- ✅ **Promise Support**: Callbacks can return Promises for async operations
|
|
215
|
+
- ✅ **Debouncing**: Add timeouts to delay callback execution
|
|
216
|
+
- ✅ **Multiple Event Types**: Listen to multiple event types at once
|
|
217
|
+
- ✅ **Manual Triggering**: Programmatically trigger events and callbacks
|
|
218
|
+
- ✅ **Context Binding**: Callbacks are bound to the matched element (`this`)
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
## Notes
|
|
223
|
+
|
|
224
|
+
- Callbacks execute sequentially; if a callback returns a Promise, the next callback waits for it to resolve
|
|
225
|
+
- Event delegation uses `closest()` to find the matching element, so it works with nested elements
|
|
226
|
+
- Timeouts are cleared and reset on each event, making it perfect for debouncing
|
|
227
|
+
- The `trigger()` method respects event delegation selectors when executing callbacks
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
## Demos
|
|
232
|
+
|
|
233
|
+
- Demo landing page: `demo/index.html`
|
|
234
|
+
- Related module demo: `demo/modules/tristate/index.html` (tri-state visual states and interaction)
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
## License
|
|
239
|
+
|
|
240
|
+
Author : Thibault SAELEN
|
|
241
|
+
Copyright Appius SARL.
|
|
242
|
+
|
|
@@ -132,9 +132,7 @@ function executeCallbacks(event, uniqueId, timeoutDuration, selector = '', eleme
|
|
|
132
132
|
}
|
|
133
133
|
});
|
|
134
134
|
});
|
|
135
|
-
}, Promise.resolve()).catch(
|
|
136
|
-
console.error('Error in callback chain:', error);
|
|
137
|
-
});
|
|
135
|
+
}, Promise.resolve()).catch(() => {});
|
|
138
136
|
}
|
|
139
137
|
}
|
|
140
138
|
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# APX Scrollable Table Module
|
|
2
|
+
|
|
3
|
+
Makes the **tbody** of a table scrollable while keeping **thead** and **tfoot** fixed and column alignment consistent. Uses CSS Grid with subgrid so no JavaScript is needed for width syncing or scrollbar compensation.
|
|
4
|
+
|
|
5
|
+
## Requirements
|
|
6
|
+
|
|
7
|
+
- Modern browsers with **CSS subgrid** support (Chrome 117+, Safari 16+, Firefox 71+). See [caniuse](https://caniuse.com/css-subgrid).
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
The module is part of APX. Ensure the scrollableTable augment is applied (see `APX.mjs`).
|
|
12
|
+
|
|
13
|
+
CSS is loaded automatically when the module is imported.
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
### Init
|
|
18
|
+
|
|
19
|
+
```js
|
|
20
|
+
APX('table.my-table').scrollableTable({ maxHeight: 300 });
|
|
21
|
+
// or with a CSS length
|
|
22
|
+
APX('table.my-table').scrollableTable({ maxHeight: '50vh' });
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Calling `scrollableTable(options)` again with no options or an empty object does nothing (idempotent). Passing new options (e.g. a different `maxHeight`) updates the stored options and re-applies the layout.
|
|
26
|
+
|
|
27
|
+
### Refresh
|
|
28
|
+
|
|
29
|
+
After changing the table DOM (adding/removing rows, changing colspan/rowspan), re-apply the layout with the same options:
|
|
30
|
+
|
|
31
|
+
```js
|
|
32
|
+
APX('table.my-table').scrollableTable('refresh');
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Options
|
|
36
|
+
|
|
37
|
+
| Option | Type | Default | Description |
|
|
38
|
+
| ----------- | ---------------- | --------- | ------------------------------------ |
|
|
39
|
+
| `maxHeight` | `number` or `string` | `'200px'` | Max height of the tbody. Number is treated as pixels; string can be any CSS length (e.g. `'50vh'`, `'20rem'`). |
|
|
40
|
+
|
|
41
|
+
## Colspan and rowspan
|
|
42
|
+
|
|
43
|
+
Tables with **colspan** and **rowspan** are supported. The module sets explicit grid placement on each cell so thead, tbody, and tfoot stay aligned. Complex headers or merged cells in the body/foot work as expected.
|
|
44
|
+
|
|
45
|
+
## Edge cases
|
|
46
|
+
|
|
47
|
+
- **Empty tbody:** The layout is still applied; the tbody area is scrollable with no content.
|
|
48
|
+
- **No thead or no tfoot:** Tables with only thead+tbody or tbody+tfoot are supported; grid rows adjust automatically.
|
|
49
|
+
|
|
50
|
+
## Browser support
|
|
51
|
+
|
|
52
|
+
Requires **CSS subgrid**. No fallback is provided in this module; for legacy browsers, consider a different approach or a separate fallback implementation.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/* APX Scrollable Table: scrollable tbody with fixed thead/tfoot and aligned columns (CSS Grid + subgrid) */
|
|
2
|
+
|
|
3
|
+
.apx-scrollable-table {
|
|
4
|
+
display: grid;
|
|
5
|
+
grid-template-columns: repeat(var(--apx-scrollable-cols, 3), minmax(0, 1fr));
|
|
6
|
+
grid-template-rows: auto fit-content(var(--apx-scrollable-body-max-height, 200px)) auto;
|
|
7
|
+
width: 100%;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/* Table with no tfoot: thead + tbody only */
|
|
11
|
+
.apx-scrollable-table:not(.apx-scrollable-table--has-tfoot) {
|
|
12
|
+
grid-template-rows: auto fit-content(var(--apx-scrollable-body-max-height, 200px));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/* Table with no thead: tbody + tfoot only */
|
|
16
|
+
.apx-scrollable-table.apx-scrollable-table--no-thead {
|
|
17
|
+
grid-template-rows: fit-content(var(--apx-scrollable-body-max-height, 200px)) auto;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/* Table with neither thead nor tfoot: tbody only */
|
|
21
|
+
.apx-scrollable-table.apx-scrollable-table--no-thead:not(.apx-scrollable-table--has-tfoot) {
|
|
22
|
+
grid-template-rows: fit-content(var(--apx-scrollable-body-max-height, 200px));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.apx-scrollable-table > thead,
|
|
26
|
+
.apx-scrollable-table > tbody,
|
|
27
|
+
.apx-scrollable-table > tfoot {
|
|
28
|
+
display: grid;
|
|
29
|
+
grid-template-columns: subgrid;
|
|
30
|
+
grid-column: 1 / -1;
|
|
31
|
+
grid-template-rows: repeat(var(--apx-scrollable-thead-rows, 1), auto);
|
|
32
|
+
min-height: 0;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.apx-scrollable-table > thead {
|
|
36
|
+
grid-template-rows: repeat(var(--apx-scrollable-thead-rows, 1), auto);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.apx-scrollable-table > tbody {
|
|
40
|
+
grid-template-rows: repeat(var(--apx-scrollable-tbody-rows, 1), auto);
|
|
41
|
+
overflow: auto;
|
|
42
|
+
/* Reserve space for the vertical scrollbar so it doesn't shrink content width and trigger a horizontal scrollbar */
|
|
43
|
+
scrollbar-gutter: stable;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.apx-scrollable-table > tfoot {
|
|
47
|
+
grid-template-rows: repeat(var(--apx-scrollable-tfoot-rows, 1), auto);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.apx-scrollable-table > thead > tr,
|
|
51
|
+
.apx-scrollable-table > tbody > tr,
|
|
52
|
+
.apx-scrollable-table > tfoot > tr {
|
|
53
|
+
display: contents;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.apx-scrollable-table th,
|
|
57
|
+
.apx-scrollable-table td {
|
|
58
|
+
/* Grid placement set by JS (grid-row, grid-column) for colspan/rowspan */
|
|
59
|
+
min-width: 0;
|
|
60
|
+
}
|