@andreyshpigunov/x 0.3.89 → 0.4.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 (64) hide show
  1. package/README.md +1 -1
  2. package/assets/css/app.css +1 -0
  3. package/assets/img/github-mark-white.png +0 -0
  4. package/assets/img/github-mark.png +0 -0
  5. package/assets/js/app.js +1 -0
  6. package/cheatsheet.html +427 -0
  7. package/dist/x.css +1 -1
  8. package/dist/x.js +1 -3
  9. package/index.html +20 -24
  10. package/package.json +52 -47
  11. package/postcss.config.cjs +0 -2
  12. package/src/components/x/animate.js +39 -45
  13. package/src/components/x/appear.js +19 -26
  14. package/src/components/x/autocomplete.js +22 -10
  15. package/src/components/x/buttons.css +40 -16
  16. package/src/components/x/colors.css +47 -41
  17. package/src/components/x/debug.css +2 -2
  18. package/src/components/x/device.js +39 -33
  19. package/src/components/x/dropdown.css +2 -3
  20. package/src/components/x/dropdown.js +16 -9
  21. package/src/components/x/flex.css +146 -109
  22. package/src/components/x/flow.css +12 -6
  23. package/src/components/x/form.css +3 -3
  24. package/src/components/x/form.js +12 -9
  25. package/src/components/x/grid.css +78 -42
  26. package/src/components/x/helpers.css +601 -438
  27. package/src/components/x/hover.js +20 -9
  28. package/src/components/x/icons.css +12 -12
  29. package/src/components/x/lazyload.js +17 -8
  30. package/src/components/x/lib.js +15 -1
  31. package/src/components/x/links.css +2 -6
  32. package/src/components/x/loadmore.js +17 -5
  33. package/src/components/x/modal.css +4 -22
  34. package/src/components/x/modal.js +14 -5
  35. package/src/components/x/reset.css +1 -15
  36. package/src/components/x/scroll.css +4 -9
  37. package/src/components/x/scroll.js +14 -1
  38. package/src/components/x/sheets.css +0 -3
  39. package/src/components/x/sheets.js +157 -37
  40. package/src/components/x/slider.css +10 -1
  41. package/src/components/x/slider.js +15 -0
  42. package/src/components/x/space.css +22 -2
  43. package/src/components/x/sticky.css +10 -15
  44. package/src/components/x/sticky.js +21 -4
  45. package/src/components/x/typo.css +14 -40
  46. package/src/css/app.css +7 -8
  47. package/src/css/x.css +191 -213
  48. package/src/js/app.js +8 -8
  49. package/src/js/x.js +37 -41
  50. package/assets/github-mark-white.png +0 -0
  51. package/assets/github-mark.png +0 -0
  52. package/assets/logo-inverse.png +0 -0
  53. package/babel.config.cjs +0 -4
  54. package/dist/app.css +0 -1
  55. package/dist/app.js +0 -1
  56. package/dist/index.html +0 -2182
  57. package/dist/logo.png +0 -0
  58. package/jest.config.mjs +0 -7
  59. package/jsdoc.json +0 -11
  60. package/vite.config.js +0 -31
  61. /package/assets/{alpha.png → img/alpha.png} +0 -0
  62. /package/assets/{apple-touch-icon.png → img/apple-touch-icon.png} +0 -0
  63. /package/assets/{logo.png → img/logo.png} +0 -0
  64. /package/assets/{logo.svg → img/logo.svg} +0 -0
@@ -44,10 +44,11 @@
44
44
  *
45
45
  * Breakpoints:
46
46
  *
47
- * - s (small): < 600px (mobile)
48
- * - m (medium): >= 600px and < 1000px (tablet)
49
- * - l (large): >= 1000px and < 1400px (desktop)
50
- * - xl (xlarge): >= 1400px (large desktop)
47
+ * - xs (xsmall): >= 0px
48
+ * - s (small): >= 640px
49
+ * - m (medium): >= 768px
50
+ * - l (large): >= 1024px
51
+ * - xl (xlarge): >= 1280px
51
52
  *
52
53
  * Public API:
53
54
  *
@@ -61,8 +62,9 @@
61
62
  * @property {boolean} touch - true if touch support is available
62
63
  * @property {number} width - Current window width in pixels
63
64
  * @property {number} height - Current window height in pixels
64
- * @property {string} breakpoint - Current responsive breakpoint: 's', 'm', 'l', or 'xl'
65
+ * @property {string} breakpoint - Current responsive breakpoint: 'xs', 's', 'm', 'l', or 'xl'
65
66
  * @property {Object} size - Boolean flags for each breakpoint:
67
+ * - size.xs - true if xsmall breakpoint
66
68
  * - size.s - true if small breakpoint
67
69
  * - size.m - true if medium breakpoint
68
70
  * - size.l - true if large breakpoint
@@ -70,9 +72,8 @@
70
72
  *
71
73
  * @method init() - Initializes or re-initializes detection, CSS classes, and resize listeners.
72
74
  * Safe to call multiple times. Automatically cleans up previous state.
73
- * @method destroy() - Removes listeners and CSS classes. Use when unmounting (e.g. Next.js).
75
+ * @method destroy() - Removes listeners and CSS classes. Use when unmounting (page change / removing DOM).
74
76
  *
75
- * Next.js: call init() in useEffect() on client; optionally call destroy() in cleanup on route change.
76
77
  * SSR-safe: constructor/init/destroy no-op or safe when window is undefined.
77
78
  *
78
79
  * @event window.breakpointchange - Fired when the responsive breakpoint changes.
@@ -99,11 +100,13 @@
99
100
  * }
100
101
  *
101
102
  * @example
102
- * // Next.js_app.tsx or layout
103
- * useEffect(() => {
104
- * device.init();
105
- * return () => device.destroy();
106
- * }, []);
103
+ * // Vanilla JS plain HTML
104
+ * // index.html:
105
+ * // <script type="module">
106
+ * // import { device } from './src/components/x/device.js';
107
+ * // window.addEventListener('DOMContentLoaded', () => device.init());
108
+ * // window.addEventListener('breakpointchange', (e) => console.log(e.detail));
109
+ * // </script>
107
110
  *
108
111
  * @author Andrey Shpigunov
109
112
  * @version 0.7
@@ -119,10 +122,11 @@ const CLASSES_TO_REMOVE = [
119
122
  ];
120
123
 
121
124
  const SIZE_FLAGS = {
122
- s: { s: true, m: false, l: false, xl: false },
123
- m: { s: false, m: true, l: false, xl: false },
124
- l: { s: false, m: false, l: true, xl: false },
125
- xl: { s: false, m: false, l: false, xl: true }
125
+ xs: { xs: true, s: false, m: false, l: false, xl: false },
126
+ s: { xs: false, s: true, m: false, l: false, xl: false },
127
+ m: { xs: false, s: false, m: true, l: false, xl: false },
128
+ l: { xs: false, s: false, m: false, l: true, xl: false },
129
+ xl: { xs: false, s: false, m: false, l: false, xl: true }
126
130
  };
127
131
 
128
132
  export class Device {
@@ -135,8 +139,8 @@ export class Device {
135
139
  this.touch = false;
136
140
  this.width = 0;
137
141
  this.height = 0;
138
- this.breakpoint = 's';
139
- this.size = SIZE_FLAGS.s;
142
+ this.breakpoint = 'xs';
143
+ this.size = SIZE_FLAGS.xs;
140
144
  this._html = null;
141
145
  this._onResize = this._onResize.bind(this);
142
146
  this._debouncedResize = lib.debounce(this._onResize, 200);
@@ -147,7 +151,7 @@ export class Device {
147
151
  this.width = window.innerWidth;
148
152
  this.height = window.innerHeight;
149
153
  this.breakpoint = this._getBreakpointName(this.width);
150
- this.size = SIZE_FLAGS[this.breakpoint] || SIZE_FLAGS.s;
154
+ this.size = SIZE_FLAGS[this.breakpoint] || SIZE_FLAGS.xs;
151
155
  this._html = document.documentElement;
152
156
  }
153
157
 
@@ -168,7 +172,7 @@ export class Device {
168
172
  }
169
173
 
170
174
  /**
171
- * Removes listeners and CSS classes. Use when unmounting (e.g. Next.js).
175
+ * Removes listeners and CSS classes. Use when unmounting (page change / removing DOM).
172
176
  */
173
177
  destroy() {
174
178
  if (typeof window === 'undefined') return;
@@ -188,30 +192,32 @@ export class Device {
188
192
  /**
189
193
  * Returns a breakpoint name based on current width.
190
194
  * Breakpoint map:
191
- * - s: < 600px
192
- * - m: ≥600px and <1000px
193
- * - l: ≥1000px and <1400px
194
- * - xl: ≥1400px
195
+ * - xs: ≥0px and <640px
196
+ * - s: ≥640px and <768px
197
+ * - m: ≥768px and <1024px
198
+ * - l: ≥1024px and <1280px
199
+ * - xl: ≥1280px
195
200
  *
196
201
  * @param {number} width - Current viewport width
197
- * @return {'s'|'m'|'l'|'xl'}
202
+ * @return {'xs'|'s'|'m'|'l'|'xl'}
198
203
  * @private
199
204
  */
200
205
  _getBreakpointName(width) {
201
- if (width >= 1400) return 'xl';
202
- if (width >= 1000) return 'l';
203
- if (width >= 600) return 'm';
204
- return 's';
206
+ if (width >= 1280) return 'xl';
207
+ if (width >= 1024) return 'l';
208
+ if (width >= 768) return 'm';
209
+ if (width >= 640) return 's';
210
+ return 'xs';
205
211
  }
206
212
 
207
213
  /**
208
214
  * Converts breakpoint string into boolean flags for convenience.
209
- * @param {'s'|'m'|'l'|'xl'} bp - Breakpoint name
210
- * @return {{s:boolean,m:boolean,l:boolean,xl:boolean}}
215
+ * @param {'xs'|'s'|'m'|'l'|'xl'} bp - Breakpoint name
216
+ * @return {{xs:boolean,s:boolean,m:boolean,l:boolean,xl:boolean}}
211
217
  * @private
212
218
  */
213
219
  _getSizeFlags(bp) {
214
- return SIZE_FLAGS[bp] || SIZE_FLAGS.s;
220
+ return SIZE_FLAGS[bp] || SIZE_FLAGS.xs;
215
221
  }
216
222
 
217
223
  /**
@@ -229,7 +235,7 @@ export class Device {
229
235
  this.width = w;
230
236
  this.height = h;
231
237
  this.breakpoint = next;
232
- this.size = SIZE_FLAGS[next] || SIZE_FLAGS.s;
238
+ this.size = SIZE_FLAGS[next] || SIZE_FLAGS.xs;
233
239
 
234
240
  if (prev !== next) {
235
241
  window.dispatchEvent(new CustomEvent('breakpointchange', {
@@ -21,7 +21,6 @@ All right reserved.
21
21
  --dropdown-divider-color: var(--color-grey);
22
22
  }
23
23
 
24
-
25
24
  [x-dropdown] {
26
25
  display: none;
27
26
  list-style: none;
@@ -47,8 +46,8 @@ All right reserved.
47
46
  pointer-events: none;
48
47
  overscroll-behavior: contain;
49
48
 
50
- @media (--medium) {
51
- --dropdown-max-width: 32rem;
49
+ @media (min-width: 768px) {
50
+ max-width: calc(var(--dropdown-max-width) * 1.2);
52
51
  }
53
52
 
54
53
  & > li {
@@ -10,9 +10,7 @@
10
10
  *
11
11
  * - `dropdown.init()` – Initializes all dropdown menus in the DOM.
12
12
  * - `dropdown.closeAllDropdowns()` – Closes all open dropdown menus.
13
- * - `dropdown.destroy()` – Full teardown (listeners + global). Use on SPA unmount / Next.js.
14
- *
15
- * Next.js: call init() in useEffect() on client; call destroy() in cleanup (e.g. on unmount or route change).
13
+ * - `dropdown.destroy()` – Full teardown (listeners + global). Use on SPA unmount / page change.
16
14
  * SSR-safe: init/destroy no-op when document is undefined.
17
15
  *
18
16
  * Usage:
@@ -97,11 +95,20 @@
97
95
  * - Screen reader friendly
98
96
  *
99
97
  * @example
100
- * // Next.jslayout or _app
101
- * useEffect(() => {
102
- * dropdown.init();
103
- * return () => dropdown.destroy();
104
- * }, []);
98
+ * // Vanilla JS plain HTML
99
+ * // index.html:
100
+ * // <div class="dropdown">
101
+ * // <button x-dropdown-open>Menu</button>
102
+ * // <ul x-dropdown>
103
+ * // <li><a href="#" role="menuitem">Item 1</a></li>
104
+ * // <li><a href="#" role="menuitem">Item 2</a></li>
105
+ * // </ul>
106
+ * // </div>
107
+ * //
108
+ * // <script type="module">
109
+ * // import { dropdown } from './src/components/x/dropdown.js';
110
+ * // window.addEventListener('DOMContentLoaded', () => dropdown.init());
111
+ * // </script>
105
112
  *
106
113
  * @author Andrey Shpigunov
107
114
  * @version 0.2
@@ -553,7 +560,7 @@ class Dropdown {
553
560
  }
554
561
 
555
562
  /**
556
- * Full teardown: cleanup and remove global listener. Use on SPA unmount or Next.js.
563
+ * Full teardown: cleanup and remove global listener. Use on SPA unmount / page change.
557
564
  * SSR-safe: no-op when document is undefined.
558
565
  */
559
566
  destroy() {
@@ -6,54 +6,36 @@ Created by Andrey Shpigunov at 20.03.2025
6
6
  All right reserved.
7
7
  ----------------------------------------*/
8
8
 
9
-
10
9
  /*
11
- .flex
12
-
13
- .flex.fr (m,l,xl) - flex row
14
- .flex.fc (m,l,xl) - flex column
15
- .flex.fw (m,l,xl) - flex wrap
16
- .flex.fnw (m,l,xl) - flex nowrap
17
-
18
- .flex.ais (m,l,xl) - stretch
19
- .flex.aifs (m,l,xl) - flex start
20
- .flex.aic (m,l,xl) - center
21
- .flex.aife (m,l,xl) - flex end
22
-
23
- .flex.jcfs (m,l,xl) - flex start
24
- .flex.jcc (m,l,xl) - center
25
- .flex.jcfe (m,l,xl) - flex end
26
- .flex.jcsb (m,l,xl) - space between
27
- .flex.jcsa (m,l,xl) - space around
28
- .flex.jcse (m,l,xl) - space evenly
29
-
30
- .flex > .c[1-12]/[1-12] (m,l,xl) - column width "column/columns"
31
- */
32
-
33
-
34
- .flex {
35
- display: flex;
36
-
37
- &.fr { flex-direction: row }
38
- &.fc { flex-direction: column }
39
- &.fw { flex-wrap: wrap }
40
- &.fnw { flex-wrap: nowrap }
10
+ .flex (s,m,l,xl)
41
11
 
42
- &.ais { align-items: stretch }
43
- &.aifs { align-items: flex-start }
44
- &.aic { align-items: center }
45
- &.aife { align-items: flex-end }
12
+ .flex.fr (s,m,l,xl) - flex row
13
+ .flex.fc (s,m,l,xl) - flex column
14
+ .flex.fw (s,m,l,xl) - flex wrap
15
+ .flex.fnw (s,m,l,xl) - flex nowrap
46
16
 
47
- &.jcfs { justify-content: flex-start }
48
- &.jcc { justify-content: center }
49
- &.jcfe { justify-content: flex-end }
50
- &.jcsb { justify-content: space-between }
51
- &.jcsa { justify-content: space-around }
52
- &.jcse { justify-content: space-evenly }
17
+ .flex.ais (s,m,l,xl) - stretch
18
+ .flex.aifs (s,m,l,xl) - flex start
19
+ .flex.aic (s,m,l,xl) - center
20
+ .flex.aife (s,m,l,xl) - flex end
53
21
 
54
- & > .noshrink { flex-shrink: 0 }
55
- & > .nogrow { flex-grow: 0 }
22
+ .flex.jcfs (s,m,l,xl) - flex start
23
+ .flex.jcc (s,m,l,xl) - center
24
+ .flex.jcfe (s,m,l,xl) - flex end
25
+ .flex.jcsb (s,m,l,xl) - space between
26
+ .flex.jcsa (s,m,l,xl) - space around
27
+ .flex.jcse (s,m,l,xl) - space evenly
56
28
 
29
+ .flex > .c[1-12]/[1-12] (s,m,l,xl) - column width "column/columns"
30
+ */
31
+
32
+ .flex { display: flex }
33
+ @media (min-width: 640px) { .s\:flex { display: flex } }
34
+ @media (min-width: 768px) { .m\:flex { display: flex } }
35
+ @media (min-width: 1024px) { .l\:flex { display: flex } }
36
+ @media (min-width: 1280px) { .xl\:flex { display: flex } }
37
+
38
+ [class*=flex] {
57
39
  /* Columns */
58
40
  @for $cols from 1 to 12 {
59
41
  @for $col from 1 to $cols {
@@ -64,28 +46,18 @@ All right reserved.
64
46
  }
65
47
  }
66
48
 
67
- @media (--medium) {
68
-
69
- &.m\:fr { flex-direction: row }
70
- &.m\:fc { flex-direction: column }
71
- &.m\:fw { flex-wrap: wrap }
72
- &.m\:fnw { flex-wrap: nowrap }
73
-
74
- &.m\:ais { align-items: stretch }
75
- &.m\:aifs { align-items: flex-start }
76
- &.m\:aic { align-items: center }
77
- &.m\:aife { align-items: flex-end }
78
-
79
- &.m\:jcfs { justify-content: flex-start }
80
- &.m\:jcc { justify-content: center }
81
- &.m\:jcfe { justify-content: flex-end }
82
- &.m\:jcsb { justify-content: space-between }
83
- &.m\:jcsa { justify-content: space-around }
84
- &.m\:jcse { justify-content: space-evenly }
85
-
86
- & > .m\:noshrink { flex-shrink: 0 }
87
- & > .m\:nogrow { flex-grow: 0 }
88
-
49
+ @media (min-width: 640px) {
50
+ @for $cols from 1 to 12 {
51
+ @for $col from 1 to $cols {
52
+ & > .s\:c$(col)\/$(cols) {
53
+ flex-basis: calc($(col) / $(cols) * 100%);
54
+ max-width: calc($(col) / $(cols) * 100%);
55
+ }
56
+ }
57
+ }
58
+ }
59
+
60
+ @media (min-width: 768px) {
89
61
  @for $cols from 1 to 12 {
90
62
  @for $col from 1 to $cols {
91
63
  & > .m\:c$(col)\/$(cols) {
@@ -96,28 +68,7 @@ All right reserved.
96
68
  }
97
69
  }
98
70
 
99
- @media (--large) {
100
-
101
- &.l\:fr { flex-direction: row }
102
- &.l\:fc { flex-direction: column }
103
- &.l\:fw { flex-wrap: wrap }
104
- &.l\:fnw { flex-wrap: nowrap }
105
-
106
- &.l\:ais { align-items: stretch }
107
- &.l\:aifs { align-items: flex-start }
108
- &.l\:aic { align-items: center }
109
- &.l\:aife { align-items: flex-end }
110
-
111
- &.l\:jcfs { justify-content: flex-start }
112
- &.l\:jcc { justify-content: center }
113
- &.l\:jcfe { justify-content: flex-end }
114
- &.l\:jcsb { justify-content: space-between }
115
- &.l\:jcsa { justify-content: space-around }
116
- &.l\:jcse { justify-content: space-evenly }
117
-
118
- & > .l\:noshrink { flex-shrink: 0 }
119
- & > .l\:nogrow { flex-grow: 0 }
120
-
71
+ @media (min-width: 1024px) {
121
72
  @for $cols from 1 to 12 {
122
73
  @for $col from 1 to $cols {
123
74
  & > .l\:c$(col)\/$(cols) {
@@ -128,28 +79,7 @@ All right reserved.
128
79
  }
129
80
  }
130
81
 
131
- @media (--xlarge) {
132
-
133
- &.xl\:fr { flex-direction: row }
134
- &.xl\:fc { flex-direction: column }
135
- &.xl\:fw { flex-wrap: wrap }
136
- &.xl\:fnw { flex-wrap: nowrap }
137
-
138
- &.xl\:ais { align-items: stretch }
139
- &.xl\:aifs { align-items: flex-start }
140
- &.xl\:aic { align-items: center }
141
- &.xl\:aife { align-items: flex-end }
142
-
143
- &.xl\:jcfs { justify-content: flex-start }
144
- &.xl\:jcc { justify-content: center }
145
- &.xl\:jcfe { justify-content: flex-end }
146
- &.xl\:jcsb { justify-content: space-between }
147
- &.xl\:jcsa { justify-content: space-around }
148
- &.xl\:jcse { justify-content: space-evenly }
149
-
150
- & > .xl\:noshrink { flex-shrink: 0 }
151
- & > .xl\:nogrow { flex-grow: 0 }
152
-
82
+ @media (min-width: 1280px) {
153
83
  @for $cols from 1 to 12 {
154
84
  @for $col from 1 to $cols {
155
85
  & > .xl\:c$(col)\/$(cols) {
@@ -159,5 +89,112 @@ All right reserved.
159
89
  }
160
90
  }
161
91
  }
92
+ }
93
+
94
+ .fr { flex-direction: row }
95
+ .fc { flex-direction: column }
96
+ .fw { flex-wrap: wrap }
97
+ .fnw { flex-wrap: nowrap }
98
+
99
+ .ais { align-items: stretch }
100
+ .aifs { align-items: flex-start }
101
+ .aic { align-items: center }
102
+ .aife { align-items: flex-end }
103
+
104
+ .jcfs { justify-content: flex-start }
105
+ .jcc { justify-content: center }
106
+ .jcfe { justify-content: flex-end }
107
+ .jcsb { justify-content: space-between }
108
+ .jcsa { justify-content: space-around }
109
+ .jcse { justify-content: space-evenly }
110
+
111
+ .no-shrink { flex-shrink: 0 }
112
+ .no-grow { flex-grow: 0 }
113
+
114
+ @media (min-width: 640px) {
115
+ .s\:fr { flex-direction: row }
116
+ .s\:fc { flex-direction: column }
117
+ .s\:fw { flex-wrap: wrap }
118
+ .s\:fnw { flex-wrap: nowrap }
119
+
120
+ .s\:ais { align-items: stretch }
121
+ .s\:aifs { align-items: flex-start }
122
+ .s\:aic { align-items: center }
123
+ .s\:aife { align-items: flex-end }
124
+
125
+ .s\:jcfs { justify-content: flex-start }
126
+ .s\:jcc { justify-content: center }
127
+ .s\:jcfe { justify-content: flex-end }
128
+ .s\:jcsb { justify-content: space-between }
129
+ .s\:jcsa { justify-content: space-around }
130
+ .s\:jcse { justify-content: space-evenly }
131
+
132
+ .s\:no-shrink { flex-shrink: 0 }
133
+ .s\:no-grow { flex-grow: 0 }
134
+ }
135
+
136
+ @media (min-width: 768px) {
137
+ .m\:fr { flex-direction: row }
138
+ .m\:fc { flex-direction: column }
139
+ .m\:fw { flex-wrap: wrap }
140
+ .m\:fnw { flex-wrap: nowrap }
141
+
142
+ .m\:ais { align-items: stretch }
143
+ .m\:aifs { align-items: flex-start }
144
+ .m\:aic { align-items: center }
145
+ .m\:aife { align-items: flex-end }
146
+
147
+ .m\:jcfs { justify-content: flex-start }
148
+ .m\:jcc { justify-content: center }
149
+ .m\:jcfe { justify-content: flex-end }
150
+ .m\:jcsb { justify-content: space-between }
151
+ .m\:jcsa { justify-content: space-around }
152
+ .m\:jcse { justify-content: space-evenly }
153
+
154
+ .m\:no-shrink { flex-shrink: 0 }
155
+ .m\:no-grow { flex-grow: 0 }
156
+ }
157
+
158
+ @media (min-width: 1024px) {
159
+ .l\:fr { flex-direction: row }
160
+ .l\:fc { flex-direction: column }
161
+ .l\:fw { flex-wrap: wrap }
162
+ .l\:fnw { flex-wrap: nowrap }
163
+
164
+ .l\:ais { align-items: stretch }
165
+ .l\:aifs { align-items: flex-start }
166
+ .l\:aic { align-items: center }
167
+ .l\:aife { align-items: flex-end }
168
+
169
+ .l\:jcfs { justify-content: flex-start }
170
+ .l\:jcc { justify-content: center }
171
+ .l\:jcfe { justify-content: flex-end }
172
+ .l\:jcsb { justify-content: space-between }
173
+ .l\:jcsa { justify-content: space-around }
174
+ .l\:jcse { justify-content: space-evenly }
175
+
176
+ .l\:no-shrink { flex-shrink: 0 }
177
+ .l\:no-grow { flex-grow: 0 }
178
+ }
179
+
180
+ @media (min-width: 1280px) {
181
+ .xl\:fr { flex-direction: row }
182
+ .xl\:fc { flex-direction: column }
183
+ .xl\:fw { flex-wrap: wrap }
184
+ .xl\:fnw { flex-wrap: nowrap }
185
+
186
+ .xl\:ais { align-items: stretch }
187
+ .xl\:aifs { align-items: flex-start }
188
+ .xl\:aic { align-items: center }
189
+ .xl\:aife { align-items: flex-end }
190
+
191
+ .xl\:jcfs { justify-content: flex-start }
192
+ .xl\:jcc { justify-content: center }
193
+ .xl\:jcfe { justify-content: flex-end }
194
+ .xl\:jcsb { justify-content: space-between }
195
+ .xl\:jcsa { justify-content: space-around }
196
+ .xl\:jcse { justify-content: space-evenly }
162
197
 
198
+ .xl\:no-shrink { flex-shrink: 0 }
199
+ .xl\:no-grow { flex-grow: 0 }
163
200
  }
@@ -6,13 +6,11 @@ Created by Andrey Shpigunov at 20.03.2025
6
6
  All right reserved.
7
7
  ----------------------------------------*/
8
8
 
9
-
10
9
  /*
11
10
  .flow
12
- .flow.s[0-10] (m,l,xl) - vertical space
11
+ .flow.s[0-10] (s,m,l,xl) - vertical space
13
12
  */
14
13
 
15
-
16
14
  .flow {
17
15
 
18
16
  & > * { margin: 0 }
@@ -23,7 +21,15 @@ All right reserved.
23
21
  }
24
22
  }
25
23
 
26
- @media(--medium){
24
+ @media (min-width: 640px) {
25
+ @for $i from 0 to 10 {
26
+ &.s\:s$(i) > * {
27
+ margin-bottom: var(--space-$(i));
28
+ }
29
+ }
30
+ }
31
+
32
+ @media (min-width: 768px) {
27
33
  @for $i from 0 to 10 {
28
34
  &.m\:s$(i) > * {
29
35
  margin-bottom: var(--space-$(i));
@@ -31,7 +37,7 @@ All right reserved.
31
37
  }
32
38
  }
33
39
 
34
- @media(--large){
40
+ @media (min-width: 1024px) {
35
41
  @for $i from 0 to 10 {
36
42
  &.l\:s$(i) > * {
37
43
  margin-bottom: var(--space-$(i));
@@ -39,7 +45,7 @@ All right reserved.
39
45
  }
40
46
  }
41
47
 
42
- @media(--xlarge){
48
+ @media (min-width: 1280px) {
43
49
  @for $i from 0 to 10 {
44
50
  &.xl\:s$(i) > * {
45
51
  margin-bottom: var(--space-$(i));
@@ -80,7 +80,7 @@ select {
80
80
  color: var(--form-font-color-disabled);
81
81
  border-color: var(--form-border-color-disabled);
82
82
  background-color: var(--form-background-color-disabled);
83
- cursor: not-allowed !important;
83
+ cursor: not-allowed;
84
84
  box-shadow: none;
85
85
  }
86
86
 
@@ -99,9 +99,9 @@ select {
99
99
 
100
100
  select {
101
101
  padding-right: calc(var(--form-side-padding) * 2.6);
102
- background-image: url('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#888" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevrons-up-down-icon lucide-chevrons-up-down"%3E%3Cpath d="m7 15 5 5 5-5"/%3E%3Cpath d="m7 9 5-5 5 5"/%3E%3C/svg%3E');
103
- background-repeat: no-repeat;
102
+ background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' fill='none' stroke='%23888' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' class='lucide lucide-chevrons-up-down-icon lucide-chevrons-up-down'%3E%3Cpath d='m7 15 5 5 5-5M7 9l5-5 5 5'/%3E%3C/svg%3E");
104
103
  background-position: right calc(var(--form-side-padding) - 4px) top 52%, 0 0;
104
+ background-repeat: no-repeat;
105
105
  background-size: 1.6rem;
106
106
  }
107
107
 
@@ -85,7 +85,7 @@
85
85
  * @example
86
86
  * form.offUpdate('input[name="email"]');
87
87
  *
88
- * @method destroy() - Removes all update listeners and clears internal state. Use on SPA unmount / Next.js.
88
+ * @method destroy() - Removes all update listeners and clears internal state. Use on SPA unmount / page change.
89
89
  *
90
90
  * @method update(selector) - Manually dispatches input/change events.
91
91
  * @param {string} selector - CSS selector for elements.
@@ -114,14 +114,17 @@
114
114
  * - Input elements and contenteditable: 'input' event
115
115
  * - Select elements: 'change' event
116
116
  *
117
- * Next.js: call destroy() in useEffect cleanup (e.g. on unmount). All DOM methods no-op when document is undefined.
118
- *
119
117
  * @example
120
- * // Next.jscleanup on unmount
121
- * useEffect(() => {
122
- * form.onUpdate('input[name="email"]', (el) => setEmail(el.value));
123
- * return () => form.destroy();
124
- * }, []);
118
+ * // Vanilla JS plain HTML
119
+ * // index.html:
120
+ * // <input name="email" type="email" />
121
+ * // <script type="module">
122
+ * // import { form } from './src/components/x/form.js';
123
+ * // window.addEventListener('DOMContentLoaded', () => {
124
+ * // form.onUpdate('input[name="email"]', (el) => console.log('Email:', el.value));
125
+ * // });
126
+ * // window.addEventListener('pagehide', () => form.destroy());
127
+ * // </script>
125
128
  *
126
129
  * @author Andrey Shpigunov
127
130
  * @version 0.3
@@ -310,7 +313,7 @@ class Form {
310
313
  }
311
314
 
312
315
  /**
313
- * Removes all update listeners and clears internal state. Use on SPA unmount or in Next.js cleanup.
316
+ * Removes all update listeners and clears internal state. Use on SPA unmount / page change.
314
317
  * SSR-safe: no-op when document is undefined.
315
318
  */
316
319
  destroy() {