@appius-fr/apx 2.6.2 → 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.
Files changed (35) hide show
  1. package/APX.mjs +2 -0
  2. package/README.md +207 -203
  3. package/dist/2ab50e700c8fbddb45e0.svg +10 -0
  4. package/dist/2e967d8dd752e0bed703.svg +10 -0
  5. package/dist/5ddaeefe5dfbc8e09652.svg +7 -0
  6. package/dist/6dc2907ba3bbb232601d.svg +10 -0
  7. package/dist/6e1e61dfca176a885b8d.svg +3 -0
  8. package/dist/6f3a0a27a260bb2c221b.svg +9 -0
  9. package/dist/8b07a8bf719a38262b7d.svg +10 -0
  10. package/dist/APX.dev.mjs +781 -227
  11. package/dist/APX.mjs +1 -1
  12. package/dist/APX.prod.mjs +1 -1
  13. package/dist/APX.standalone.js +677 -75
  14. package/dist/APX.standalone.js.map +1 -1
  15. package/dist/bdfa755a1cdb872368c7.svg +3 -0
  16. package/dist/c9da177f7663f9fcd023.svg +10 -0
  17. package/dist/ce9ef5fceb78e17e68c9.svg +8 -0
  18. package/dist/ed5af5163957b04bc6cc.svg +7 -0
  19. package/modules/listen/README.md +242 -235
  20. package/modules/listen/listen.mjs +1 -3
  21. package/modules/scrollableTable/README.md +52 -0
  22. package/modules/scrollableTable/css/scrollableTable.css +60 -0
  23. package/modules/scrollableTable/scrollableTable.mjs +198 -0
  24. package/modules/toast/README.md +186 -153
  25. package/modules/tristate/CHANGELOG.md +34 -0
  26. package/modules/tristate/README.md +157 -94
  27. package/modules/tristate/assets/tristate-checked.svg +3 -0
  28. package/modules/tristate/assets/tristate-cross.svg +10 -0
  29. package/modules/tristate/assets/tristate-crossed.svg +3 -0
  30. package/modules/tristate/assets/tristate-indeterminate-dash.svg +9 -0
  31. package/modules/tristate/assets/tristate-tick.svg +10 -0
  32. package/modules/tristate/assets/tristate-unchecked.svg +7 -0
  33. package/modules/tristate/css/tristate.css +91 -24
  34. package/modules/tristate/tristate.mjs +292 -171
  35. package/package.json +5 -1
@@ -0,0 +1,198 @@
1
+ import './css/scrollableTable.css';
2
+
3
+ const DATA_KEY = '_apxScrollableTable';
4
+ const CLASS_TABLE = 'apx-scrollable-table';
5
+
6
+ const DEFAULT_MAX_HEIGHT = '200px';
7
+
8
+ /**
9
+ * Get number of columns from the first row that has cells (sum of colspan).
10
+ * @param {HTMLTableSectionElement} section - thead, tbody, or tfoot
11
+ * @returns {number}
12
+ */
13
+ function getColumnCountFromSection(section) {
14
+ const firstRow = section?.querySelector(':scope > tr');
15
+ if (!firstRow) return 0;
16
+ let cols = 0;
17
+ firstRow.querySelectorAll(':scope > th, :scope > td').forEach((cell) => {
18
+ cols += parseInt(cell.getAttribute('colspan'), 10) || 1;
19
+ });
20
+ return cols;
21
+ }
22
+
23
+ /**
24
+ * Get total column count for the table (from thead or tbody first row).
25
+ * @param {HTMLTableElement} table
26
+ * @returns {number}
27
+ */
28
+ function getTableColumnCount(table) {
29
+ const thead = table.querySelector('thead');
30
+ const tbody = table.querySelector('tbody');
31
+ const countFromThead = thead ? getColumnCountFromSection(thead) : 0;
32
+ const countFromTbody = tbody ? getColumnCountFromSection(tbody) : 0;
33
+ return countFromThead || countFromTbody || 1;
34
+ }
35
+
36
+ /**
37
+ * Count direct tr children in a section.
38
+ * @param {HTMLTableSectionElement} section
39
+ * @returns {number}
40
+ */
41
+ function getRowCount(section) {
42
+ if (!section) return 0;
43
+ return section.querySelectorAll(':scope > tr').length;
44
+ }
45
+
46
+ /**
47
+ * Build a 2D grid of occupied slots for a section (for rowspan/colspan placement).
48
+ * Place each cell in DOM order; return a list of { cell, row, col, colspan, rowspan }.
49
+ * @param {HTMLTableSectionElement} section
50
+ * @param {number} numRows
51
+ * @param {number} numCols
52
+ * @returns {{ cell: HTMLTableCellElement, row: number, col: number, colspan: number, rowspan: number }[]}
53
+ */
54
+ function computeCellPlacements(section, numRows, numCols) {
55
+ if (!section || numRows === 0 || numCols === 0) return [];
56
+ const occupied = Array.from({ length: numRows }, () => Array(numCols).fill(false));
57
+ const placements = [];
58
+ const rows = section.querySelectorAll(':scope > tr');
59
+
60
+ for (let r = 0; r < rows.length; r++) {
61
+ const tr = rows[r];
62
+ const cells = tr.querySelectorAll(':scope > th, :scope > td');
63
+ for (const cell of cells) {
64
+ const colspan = Math.min(parseInt(cell.getAttribute('colspan'), 10) || 1, numCols);
65
+ const rowspan = Math.min(parseInt(cell.getAttribute('rowspan'), 10) || 1, numRows - r);
66
+ let col = 0;
67
+ while (col < numCols) {
68
+ let free = true;
69
+ for (let rr = r; rr < r + rowspan && free; rr++) {
70
+ for (let cc = col; cc < col + colspan && free; cc++) {
71
+ if (occupied[rr]?.[cc]) free = false;
72
+ }
73
+ }
74
+ if (free) break;
75
+ col++;
76
+ }
77
+ if (col + colspan > numCols) continue;
78
+ for (let rr = r; rr < r + rowspan; rr++) {
79
+ for (let cc = col; cc < col + colspan; cc++) {
80
+ if (occupied[rr]) occupied[rr][cc] = true;
81
+ }
82
+ }
83
+ placements.push({ cell, row: r, col, colspan, rowspan });
84
+ }
85
+ }
86
+ return placements;
87
+ }
88
+
89
+ /**
90
+ * Apply grid placement styles to a list of placements (1-based line numbers for CSS Grid).
91
+ */
92
+ function applyPlacements(placements) {
93
+ placements.forEach(({ cell, row, col, colspan, rowspan }) => {
94
+ cell.style.gridRow = `${row + 1} / span ${rowspan}`;
95
+ cell.style.gridColumn = `${col + 1} / span ${colspan}`;
96
+ });
97
+ }
98
+
99
+ /**
100
+ * Clear grid placement styles from all th/td in a table (for refresh).
101
+ * @param {HTMLTableElement} table
102
+ */
103
+ function clearPlacements(table) {
104
+ table.querySelectorAll('th, td').forEach((cell) => {
105
+ cell.style.gridRow = '';
106
+ cell.style.gridColumn = '';
107
+ });
108
+ }
109
+
110
+ /**
111
+ * Normalize maxHeight option to a CSS length string.
112
+ * @param {number|string} value
113
+ * @returns {string}
114
+ */
115
+ function normalizeMaxHeight(value) {
116
+ if (value == null) return DEFAULT_MAX_HEIGHT;
117
+ if (typeof value === 'number') return `${value}px`;
118
+ return String(value);
119
+ }
120
+
121
+ /**
122
+ * Apply scrollable table layout to a single table.
123
+ * @param {HTMLTableElement} table
124
+ * @param {{ maxHeight?: number|string }} options
125
+ */
126
+ function applyScrollableTable(table, options) {
127
+ const thead = table.querySelector('thead');
128
+ const tbody = table.querySelector('tbody');
129
+ const tfoot = table.querySelector('tfoot');
130
+
131
+ const numCols = getTableColumnCount(table);
132
+ if (numCols === 0) return;
133
+
134
+ const theadRows = getRowCount(thead);
135
+ const tbodyRows = getRowCount(tbody);
136
+ const tfootRows = getRowCount(tfoot);
137
+
138
+ table.style.setProperty('--apx-scrollable-cols', String(numCols));
139
+ table.style.setProperty('--apx-scrollable-thead-rows', String(Math.max(1, theadRows)));
140
+ table.style.setProperty('--apx-scrollable-tbody-rows', String(Math.max(1, tbodyRows)));
141
+ table.style.setProperty('--apx-scrollable-tfoot-rows', String(Math.max(1, tfootRows)));
142
+ table.style.setProperty('--apx-scrollable-body-max-height', normalizeMaxHeight(options.maxHeight));
143
+
144
+ table.classList.add(CLASS_TABLE);
145
+ table.classList.toggle('apx-scrollable-table--has-tfoot', !!(tfoot && tfootRows > 0));
146
+ table.classList.toggle('apx-scrollable-table--no-thead', !(thead && theadRows > 0));
147
+
148
+ clearPlacements(table);
149
+
150
+ const sections = [
151
+ { section: thead, rows: Math.max(1, theadRows) },
152
+ { section: tbody, rows: Math.max(1, tbodyRows) },
153
+ { section: tfoot, rows: Math.max(1, tfootRows) }
154
+ ];
155
+ sections.forEach(({ section, rows }) => {
156
+ if (!section) return;
157
+ const placements = computeCellPlacements(section, rows, numCols);
158
+ applyPlacements(placements);
159
+ });
160
+ }
161
+
162
+ /**
163
+ * Augments the APX object with scrollableTable(options | 'refresh').
164
+ * Makes the tbody of a table scrollable while keeping thead/tfoot fixed and columns aligned (CSS Grid + subgrid).
165
+ *
166
+ * @param {Object} apx - The APX object to augment.
167
+ * @example
168
+ * APX('table.data-grid').scrollableTable({ maxHeight: 300 });
169
+ * APX('table.data-grid').scrollableTable('refresh');
170
+ */
171
+ export default function augmentWithScrollableTable(apx) {
172
+ apx.scrollableTable = function (optionsOrAction) {
173
+ const isRefresh = optionsOrAction === 'refresh';
174
+ const options = isRefresh ? null : (optionsOrAction && typeof optionsOrAction === 'object' ? optionsOrAction : {});
175
+
176
+ apx.elements.forEach((element) => {
177
+ if (element.tagName !== 'TABLE') return;
178
+ const table = /** @type {HTMLTableElement} */ (element);
179
+
180
+ const ref = table[DATA_KEY];
181
+ if (ref) {
182
+ if (isRefresh) {
183
+ applyScrollableTable(table, ref.options);
184
+ } else if (options && Object.keys(options).length > 0) {
185
+ ref.options = { ...ref.options, ...options };
186
+ applyScrollableTable(table, ref.options);
187
+ }
188
+ return;
189
+ }
190
+ if (isRefresh) return;
191
+
192
+ applyScrollableTable(table, options);
193
+ table[DATA_KEY] = { options: { ...options } };
194
+ });
195
+
196
+ return apx;
197
+ };
198
+ }
@@ -1,153 +1,186 @@
1
- # APX Toast
2
-
3
- A tiny, framework‑agnostic toast library for APX. Minimal CSS, ESM‑first, no globals. DOM is only touched when you actually show a toast (SSR‑safe to import).
4
-
5
- ## Install / Import
6
-
7
- Just import `APX` — the toast CSS is automatically loaded by `modules/toast/toast.mjs`.
8
-
9
- ```html
10
- <script type="module">
11
- import APX from './APX.mjs';
12
- // CSS is auto‑imported; no <link> tag required.
13
- // ...
14
- APX.toast.success('Ready!');
15
- </script>
16
- ```
17
-
18
- ## Quick start
19
-
20
- ```js
21
- // Default manager (lazy)
22
- APX.toast.success('Saved!');
23
- APX.toast.warning('Heads up', { durationMs: 4000 });
24
- APX.toast.danger('Something failed', { durationMs: 0 }); // sticky
25
-
26
- // Custom toast
27
- const ref = APX.toast.show({ message: 'Processing…', type: 'info', durationMs: 0 });
28
- // Callable shorthand for show:
29
- APX.toast({ message: 'Hello', type: 'success' });
30
- ref.update({ message: 'Done', type: 'success', durationMs: 1800 });
31
- ref.whenClosed().then(() => console.log('closed'));
32
-
33
- // Configure defaults at runtime
34
- APX.toast.configure({ position: 'top-right', maxToasts: 4, dedupe: true });
35
- ```
36
-
37
- ### Named managers (profiles)
38
-
39
- ```js
40
- // Register a named manager
41
- APX.toast.create('admin', { position: 'bottom-left', ariaLive: 'polite' });
42
-
43
- // Use it later
44
- APX.toast.use('admin').info('Admin ready');
45
- APX.toast.use('admin').closeAll();
46
- ```
47
-
48
- ## API overview
49
-
50
- - Top‑level (default manager):
51
- - `APX.toast.show(opts)`
52
- - `APX.toast.info|success|warning|danger(message, opts?)`
53
- - `APX.toast.configure(config)`
54
- - `APX.toast.closeAll(reason?)`
55
- - `APX.toast.create(name, config)` registers named manager
56
- - `APX.toast.use(name)` returns named manager
57
-
58
- - Named manager instance (same surface):
59
- - `.show(opts)`, `.info|success|warning|danger(message, opts?)`
60
- - `.configure(config)`, `.closeAll(reason?)`
61
-
62
- ## Options and types
63
-
64
- ```js
65
- // ToastConfig (global/manager defaults)
66
- {
67
- position: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left', // default 'bottom-right'
68
- maxToasts: number, // default 5
69
- defaultDurationMs: number, // default 5000
70
- zIndex: number, // default 11000
71
- ariaLive: 'polite'|'assertive'|'off', // default 'polite'
72
- gap: number, // default 8
73
- dedupe: boolean, // default false
74
- containerClass: string, // extra class on container
75
- offset: number, // px offset from screen edges
76
- progress: false | true | { enable, position?, pauseButton? } // default false; pauseButton default false (v2.6.1)
77
- }
78
-
79
- // ToastOptions (per toast)
80
- {
81
- message: string | Node,
82
- type: 'info'|'success'|'warning'|'danger', // default 'info'
83
- durationMs: number, // default from config; 0 = sticky
84
- dismissible: boolean, // default true
85
- id: string, // stable id for dedupe updates
86
- className: string, // extra classes on the toast element
87
- progress: true | { enable: boolean, position?: 'top'|'bottom', pauseButton?: boolean }, // v2.6.1; pauseButton default false
88
- onClick: (ref, ev) => void,
89
- onClose: (ref, reason) => void // reason: 'timeout'|'close'|'api'|'overflow'
90
- }
91
- ```
92
-
93
- ## Theming (minimal CSS)
94
-
95
- Override CSS variables to theme without touching markup:
96
-
97
- ```css
98
- :root {
99
- --apx-toast-gap: 10px;
100
- --apx-toast-min-width: 280px;
101
- --apx-toast-radius: 8px;
102
- --apx-toast-success-bg: #16a34a;
103
- --apx-toast-success-fg: #fff;
104
- }
105
- ```
106
-
107
- Class structure (BEM‑like):
108
- - Container: `APX-toast-container APX-toast-container--{corner}`
109
- - Toast: `APX-toast APX-toast--{type}`
110
- - Children: `APX-toast__content`, optional `APX-toast__close`
111
- - Progress (v2.6.1): `APX-toast__progress`, `APX-toast__progress-track`, `APX-toast__progress-bar`, optional `APX-toast__progress-pause`
112
- - Animations: `APX-toast--enter/--enter-active`, `APX-toast--exit/--exit-active`
113
-
114
- ## Progress bar (v2.6.1)
115
-
116
- Optional visual countdown for toasts with a duration. Bar shows time remaining (100% → 0%); sync with timer and hover pause.
117
-
118
- ```js
119
- // Bar on top (default), no pause button by default
120
- APX.toast.show({ message: 'Saving…', type: 'info', progress: true });
121
-
122
- // Bar with pause/resume button
123
- APX.toast.show({ message: 'Pausable', type: 'info', progress: { enable: true, position: 'top', pauseButton: true } });
124
-
125
- // Bar at bottom
126
- APX.toast.show({ message: 'Done', type: 'success', progress: { enable: true, position: 'bottom' } });
127
-
128
- // Default progress for all toasts from a manager
129
- APX.toast.configure({ progress: true });
130
- ```
131
-
132
- - `progress: true` → bar at top, no pause button.
133
- - `progress: { enable, position: 'top'|'bottom', pauseButton?: boolean }` — `pauseButton` defaults to `false`; set `pauseButton: true` to show the round pause/resume button on the toast corner.
134
- - No bar if `durationMs === 0` (sticky). Extra spacing is applied when the button is present so stacked toasts do not overlap.
135
-
136
- ## Behavior
137
-
138
- - Lazy container creation (first `show`).
139
- - `maxToasts` enforced; oldest removed with reason `'overflow'`.
140
- - Hover pauses timer; resumes on mouse leave (unless user clicked pause).
141
- - `durationMs = 0` makes the toast sticky (no progress bar).
142
- - If `dedupe: true` and `id` matches an open toast, it updates instead of creating a new one.
143
- - With `progress`, a bar and optional pause/resume button show; button toggles pause (same as hover).
144
-
145
- ## Accessibility & SSR
146
-
147
- - Container uses `aria-live` (configurable).
148
- - Each toast has `role="status"`.
149
- - ESM only; no DOM access at import time. Safe to import in SSR; DOM is touched only when showing toasts in the browser.
150
-
151
- ## License
152
-
153
- Copyright Appius.
1
+ # APX Toast
2
+
3
+ A tiny, framework‑agnostic toast library for APX. Minimal CSS, ESM‑first, no globals. DOM is only touched when you actually show a toast (SSR‑safe to import).
4
+
5
+ ## Install / Import
6
+
7
+ Just import `APX` — the toast CSS is automatically loaded by `modules/toast/toast.mjs`.
8
+
9
+ ```html
10
+ <script type="module">
11
+ import APX from './APX.mjs';
12
+ // CSS is auto‑imported; no <link> tag required.
13
+ // ...
14
+ APX.toast.success('Ready!');
15
+ </script>
16
+ ```
17
+
18
+ ## Quick start
19
+
20
+ ```js
21
+ // Default manager (lazy)
22
+ APX.toast.success('Saved!');
23
+ APX.toast.warning('Heads up', { durationMs: 4000 });
24
+ APX.toast.danger('Something failed', { durationMs: 0 }); // sticky
25
+
26
+ // Custom toast
27
+ const ref = APX.toast.show({ message: 'Processing…', type: 'info', durationMs: 0 });
28
+ // Callable shorthand for show:
29
+ APX.toast({ message: 'Hello', type: 'success' });
30
+ ref.update({ message: 'Done', type: 'success', durationMs: 1800 });
31
+ ref.whenClosed().then(() => console.log('closed'));
32
+
33
+ // Configure defaults at runtime
34
+ APX.toast.configure({ position: 'top-right', maxToasts: 4, dedupe: true });
35
+ ```
36
+
37
+ ### Named managers (profiles)
38
+
39
+ ```js
40
+ // Register a named manager
41
+ APX.toast.create('admin', { position: 'bottom-left', ariaLive: 'polite' });
42
+
43
+ // Use it later
44
+ APX.toast.use('admin').info('Admin ready');
45
+ APX.toast.use('admin').closeAll();
46
+ ```
47
+
48
+ ## API overview
49
+
50
+ - Top‑level (default manager):
51
+ - `APX.toast(opts)` — callable shorthand for `show(opts)`
52
+ - `APX.toast.show(opts)`
53
+ - `APX.toast.info|success|warning|danger(message, opts?)`
54
+ - `APX.toast.configure(config)`
55
+ - `APX.toast.closeAll(reason?)` `reason`: `'api'` | `'overflow'`
56
+ - `APX.toast.getOpenToasts()` returns `ToastRef[]`
57
+ - `APX.toast.create(name, config)` — register named manager; or `create(config)` to get a new manager without registering
58
+ - `APX.toast.use(name)` — returns named manager or `null`
59
+
60
+ - Named manager instance (same surface):
61
+ - `.show(opts)`, `.info|success|warning|danger(message, opts?)`
62
+ - `.configure(config)`, `.closeAll(reason?)`, `.getOpenToasts()`
63
+
64
+ - **ToastRef** (returned by `show` / helpers):
65
+ - `ref.id`, `ref.el` (HTMLElement)
66
+ - `ref.close(reason?)` — `reason`: `'api'` | `'close'`
67
+ - `ref.update(partial)` merge options (message, type, durationMs, progress, className, etc.)
68
+ - `ref.whenClosed()` `Promise<void>` when toast is removed
69
+ - `ref.on('close'|'click', handler)` — returns unsubscribe function
70
+ - `ref.off('close'|'click', handler)`
71
+
72
+ ## Options and types
73
+
74
+ ```js
75
+ // ToastConfig (global/manager defaults)
76
+ {
77
+ position: Position, // default 'bottom-right' (string or object, see Position below)
78
+ flow: 'up'|'down'|'auto', // stacking direction; 'auto' from position (default 'auto')
79
+ maxToasts: number, // default 5
80
+ defaultDurationMs: number, // default 5000
81
+ zIndex: number, // default 11000
82
+ ariaLive: 'polite'|'assertive'|'off', // default 'polite'
83
+ gap: number, // default 8
84
+ dedupe: boolean, // default false
85
+ containerClass: string, // extra class on container
86
+ offset: number, // px offset from screen edges (string positions only)
87
+ id: string, // default id for toasts when not provided per-call
88
+ progress: false | true | { enable, position?, pauseButton? } // default false; pauseButton default false
89
+ }
90
+
91
+ // Position — string or object
92
+ // String: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'
93
+ // Object:
94
+ // - type 'sticky': fixed viewport, use x / y (e.g. '10px', '-20px' = from right/bottom)
95
+ // - type 'relative': relative to element; x, y offset; requires element
96
+ // - type 'anchored': outside element; placement 'top'|'right'|'bottom'|'left' (or 'above'|'below'|'before'|'after'); gap; requires element
97
+ // - useNativeCSS: true — for relative/anchored, render container inside the element with CSS (no fixed overlay)
98
+
99
+ // ToastOptions (per toast)
100
+ {
101
+ message: string | Node,
102
+ type: 'info'|'success'|'warning'|'danger', // default 'info'
103
+ durationMs: number, // default from config; 0 = sticky
104
+ dismissible: boolean, // default true
105
+ id: string, // stable id for dedupe updates
106
+ className: string, // extra classes on the toast element
107
+ position: Position, // override container position for this toast
108
+ flow: 'up'|'down'|'auto', // override stacking direction for this toast
109
+ progress: true | { enable: boolean, position?: 'top'|'bottom', pauseButton?: boolean }, // pauseButton default false
110
+ onClick: (ref, ev) => void,
111
+ onClose: (ref, reason) => void // reason: 'timeout'|'close'|'api'|'overflow'
112
+ }
113
+ ```
114
+
115
+ ## Position (advanced)
116
+
117
+ Besides the four corners (`'bottom-right'`, `'bottom-left'`, `'top-right'`, `'top-left'`), position can be an object:
118
+
119
+ - **sticky** (or omit type and set `x`/`y`): fixed in viewport. Use `x`, `y` as CSS values; prefix with `-` for right/bottom (e.g. `y: '-20px'`).
120
+ - **relative**: fixed overlay positioned relative to an element. Requires `element` (HTMLElement); `x`, `y` are offsets (px/em). Container follows scroll/resize.
121
+ - **anchored**: container placed outside the element. Requires `element` and `placement` (`'top'|'right'|'bottom'|'left'` or synonyms `'above'|'below'|'before'|'after'`). Optional `gap` (e.g. `'1em'`).
122
+ - **useNativeCSS**: when `true` with relative/anchored, the toast container is rendered inside the target element with `position: absolute` (no fixed overlay). The target gets `position: relative` while toasts exist.
123
+
124
+ `flow` (`'up'|'down'|'auto'`) controls stacking: `'auto'` is derived from position (e.g. top → up, bottom → down).
125
+
126
+ ## Theming (minimal CSS)
127
+
128
+ Override CSS variables to theme without touching markup:
129
+
130
+ ```css
131
+ :root {
132
+ --apx-toast-gap: 10px;
133
+ --apx-toast-min-width: 280px;
134
+ --apx-toast-radius: 8px;
135
+ --apx-toast-success-bg: #16a34a;
136
+ --apx-toast-success-fg: #fff;
137
+ }
138
+ ```
139
+
140
+ Class structure (BEM‑like):
141
+ - Container: `APX-toast-container APX-toast-container--{corner}`
142
+ - Toast: `APX-toast APX-toast--{type}`
143
+ - Children: `APX-toast__content`, optional `APX-toast__close`
144
+ - Progress (v2.6.1): `APX-toast__progress`, `APX-toast__progress-track`, `APX-toast__progress-bar`, optional `APX-toast__progress-pause`
145
+ - Animations: `APX-toast--enter/--enter-active`, `APX-toast--exit/--exit-active`
146
+
147
+ ## Progress bar (v2.6.1)
148
+
149
+ Optional visual countdown for toasts with a duration. Bar shows time remaining (100% 0%); sync with timer and hover pause.
150
+
151
+ ```js
152
+ // Bar on top (default), no pause button by default
153
+ APX.toast.show({ message: 'Saving…', type: 'info', progress: true });
154
+
155
+ // Bar with pause/resume button
156
+ APX.toast.show({ message: 'Pausable', type: 'info', progress: { enable: true, position: 'top', pauseButton: true } });
157
+
158
+ // Bar at bottom
159
+ APX.toast.show({ message: 'Done', type: 'success', progress: { enable: true, position: 'bottom' } });
160
+
161
+ // Default progress for all toasts from a manager
162
+ APX.toast.configure({ progress: true });
163
+ ```
164
+
165
+ - `progress: true` → bar at top, no pause button.
166
+ - `progress: { enable, position: 'top'|'bottom', pauseButton?: boolean }` — `pauseButton` defaults to `false`; set `pauseButton: true` to show the round pause/resume button on the toast corner.
167
+ - No bar if `durationMs === 0` (sticky). Extra spacing is applied when the button is present so stacked toasts do not overlap.
168
+
169
+ ## Behavior
170
+
171
+ - Lazy container creation (first `show`).
172
+ - `maxToasts` enforced; oldest removed with reason `'overflow'`.
173
+ - Hover pauses timer; resumes on mouse leave (unless user clicked pause).
174
+ - `durationMs = 0` makes the toast sticky (no progress bar).
175
+ - If `dedupe: true` and `id` matches an open toast, it updates instead of creating a new one.
176
+ - With `progress`, a bar and optional pause/resume button show; button toggles pause (same as hover).
177
+
178
+ ## Accessibility & SSR
179
+
180
+ - Container uses `aria-live` (configurable).
181
+ - Each toast has `role="status"`.
182
+ - ESM only; no DOM access at import time. Safe to import in SSR; DOM is touched only when showing toasts in the browser.
183
+
184
+ ## License
185
+
186
+ Copyright Appius.
@@ -0,0 +1,34 @@
1
+ # Tristate Module Changelog
2
+
3
+ This changelog tracks updates for `modules/tristate` between APX releases.
4
+
5
+ ## TBA (next release)
6
+
7
+ ### Added
8
+ - **Chainable API**: `tristate(options)` now returns an object with `setChildren(childrenApx)`. Use it to link a parent tristate to its child checkboxes.
9
+ - **Parent + sub-checkboxes**: `setChildren(childrenApx)` wires one parent (first in the selection) to all child checkboxes. Parent reflects children (all checked → parent checked, all crossed → parent crossed, all unchecked → parent empty, **mixed → parent shows indeterminate dash**). Clicking the parent applies its new state to all children. Children are initialized as tristates with the same options as the parent.
10
+ - **Automatic indeterminate (dash)**: When `setChildren()` is used, the parent automatically gets the indeterminate (dash) appearance **only when children are mixed**. When all children are unchecked the parent shows an empty frame. Indeterminate is no longer a manual option; it is derived from child states.
11
+ - New `colors` option: an object grouping color targets.
12
+ - `colors.tick`: checked/crossed symbol color (default: `transparent`).
13
+ - `colors.checked`: checked state background (default: `#0654ba`).
14
+ - `colors.crossed`: crossed state background (default: `#f00`).
15
+ - Flat options `tickColor`, `checkedColor`, `crossedColor` remain supported for backward compatibility; `colors` takes precedence when both are set.
16
+ - CSS class `apx-tristate--mixed`: when the parent has indeterminate-dash and `--mixed`, the dash is shown; when not `--mixed` (e.g. all children unchecked), the parent shows an empty unchecked frame. Used internally by `setChildren()`.
17
+
18
+ ### Changed
19
+ - `size.width` and `size.height` accept numbers (treated as pixels) or strings (used as CSS length, e.g. `'1.25rem'`, `'20px'`). Invalid types throw `TypeError`.
20
+ - Tristate symbols (check and cross) are rendered with `mask-image` and data URIs over themeable backgrounds. Unchecked and indeterminate-dash assets are inlined as data URIs in CSS so the demo works from `file://` without CORS.
21
+ - Unchecked icon uses a rounded inset (`4px`). Indeterminate (dash) uses checked background + tick-colored dash; indeterminate (filled check) uses checked background only with `border-radius: inherit`.
22
+ - **State classes**: Only one of `unchecked` / `checked` / `crossed` is ever set on the tristate DOM (fixes parent with `setChildren` incorrectly showing indeterminate when state was `crossed`). `setDefaultState` and `toggleTriStateCheckboxState` now clear all three before adding the active one.
23
+ - Asset `tristate-indeterminate.svg` renamed to `tristate-indeterminate-dash.svg`.
24
+
25
+ ### Removed
26
+ - **Breaking**: Options `uncheckedAppearance` and `indeterminateDisplay` have been removed. Indeterminate (dash) is no longer configurable manually; it is applied automatically on parents that use `setChildren()` when their children are in a mixed state.
27
+
28
+ ### Docs & Demo
29
+ - Demo `demo/modules/tristate/index.html`: Parameters (size + colors), Live preview, States preview, Interactive cycle, **Parent + sub-checkboxes** real-world section using `APX('#policyParent').tristate(opts).setChildren(APX('#policyEmail, #policySms, #policyPush'))`. Removed manual uncheckedAppearance/indeterminateDisplay controls and the separate “Unchecked appearance” preview section.
30
+ - README: documented `setChildren(childrenApx)`, automatic indeterminate when mixed, and removed `uncheckedAppearance` / `indeterminateDisplay` from the options list.
31
+
32
+ ## 2.6.2 (current)
33
+
34
+ Baseline for this changelog. Tristate visuals and options prior to the theming improvements above.