@andreyshpigunov/x 0.3.72
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/.editorconfig +12 -0
- package/.github/workflows/publish.yml +28 -0
- package/README.md +37 -0
- package/assets/alpha.png +0 -0
- package/assets/apple-touch-icon.png +0 -0
- package/assets/github-mark-white.png +0 -0
- package/assets/github-mark.png +0 -0
- package/assets/logo-inverse.png +0 -0
- package/assets/logo.png +0 -0
- package/assets/logo.svg +9 -0
- package/babel.config.cjs +4 -0
- package/dist/app.css +167 -0
- package/dist/app.js +1 -0
- package/dist/x.css +167 -0
- package/dist/x.js +1 -0
- package/favicon.ico +0 -0
- package/favicon.svg +9 -0
- package/index.html +2214 -0
- package/index.js +1 -0
- package/jest.config.mjs +7 -0
- package/jsdoc.json +11 -0
- package/package.json +50 -0
- package/src/components/x/animate.js +296 -0
- package/src/components/x/appear.js +158 -0
- package/src/components/x/autocomplete.js +150 -0
- package/src/components/x/buttons.css +265 -0
- package/src/components/x/colors.css +64 -0
- package/src/components/x/debug.css +55 -0
- package/src/components/x/device.js +265 -0
- package/src/components/x/dropdown.css +164 -0
- package/src/components/x/dropdown.js +463 -0
- package/src/components/x/flex.css +163 -0
- package/src/components/x/flow.css +52 -0
- package/src/components/x/form.css +138 -0
- package/src/components/x/form.js +180 -0
- package/src/components/x/grid.css +109 -0
- package/src/components/x/helpers.css +928 -0
- package/src/components/x/hover.js +93 -0
- package/src/components/x/icons.css +58 -0
- package/src/components/x/lazyload.js +153 -0
- package/src/components/x/lib.js +679 -0
- package/src/components/x/links.css +114 -0
- package/src/components/x/loadmore.js +191 -0
- package/src/components/x/modal.css +286 -0
- package/src/components/x/modal.js +346 -0
- package/src/components/x/reset.css +213 -0
- package/src/components/x/scroll.css +100 -0
- package/src/components/x/scroll.js +301 -0
- package/src/components/x/sheets.css +15 -0
- package/src/components/x/sheets.js +147 -0
- package/src/components/x/slider.css +83 -0
- package/src/components/x/slider.js +330 -0
- package/src/components/x/space.css +56 -0
- package/src/components/x/sticky.css +28 -0
- package/src/components/x/sticky.js +156 -0
- package/src/components/x/typo.css +318 -0
- package/src/css/app.css +407 -0
- package/src/css/x.css +252 -0
- package/src/js/app.js +47 -0
- package/src/js/x.js +81 -0
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/*----------------------------------------
|
|
2
|
+
flex.css / x
|
|
3
|
+
Flexbox
|
|
4
|
+
|
|
5
|
+
Created by Andrey Shpigunov at 20.03.2025
|
|
6
|
+
All right reserved.
|
|
7
|
+
----------------------------------------*/
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
/*
|
|
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 }
|
|
41
|
+
|
|
42
|
+
&.ais { align-items: stretch }
|
|
43
|
+
&.aifs { align-items: flex-start }
|
|
44
|
+
&.aic { align-items: center }
|
|
45
|
+
&.aife { align-items: flex-end }
|
|
46
|
+
|
|
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 }
|
|
53
|
+
|
|
54
|
+
& > .noshrink { flex-shrink: 0 }
|
|
55
|
+
& > .nogrow { flex-grow: 0 }
|
|
56
|
+
|
|
57
|
+
/* Columns */
|
|
58
|
+
@for $cols from 1 to 12 {
|
|
59
|
+
@for $col from 1 to $cols {
|
|
60
|
+
& > .c$(col)\/$(cols) {
|
|
61
|
+
flex-basis: calc($(col) / $(cols) * 100%);
|
|
62
|
+
max-width: calc($(col) / $(cols) * 100%);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
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
|
+
|
|
89
|
+
@for $cols from 1 to 12 {
|
|
90
|
+
@for $col from 1 to $cols {
|
|
91
|
+
& > .m\:c$(col)\/$(cols) {
|
|
92
|
+
flex-basis: calc($(col) / $(cols) * 100%);
|
|
93
|
+
max-width: calc($(col) / $(cols) * 100%);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
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
|
+
|
|
121
|
+
@for $cols from 1 to 12 {
|
|
122
|
+
@for $col from 1 to $cols {
|
|
123
|
+
& > .l\:c$(col)\/$(cols) {
|
|
124
|
+
flex-basis: calc($(col) / $(cols) * 100%);
|
|
125
|
+
max-width: calc($(col) / $(cols) * 100%);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
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
|
+
|
|
153
|
+
@for $cols from 1 to 12 {
|
|
154
|
+
@for $col from 1 to $cols {
|
|
155
|
+
& > .xl\:c$(col)\/$(cols) {
|
|
156
|
+
flex-basis: calc($(col) / $(cols) * 100%);
|
|
157
|
+
max-width: calc($(col) / $(cols) * 100%);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/*----------------------------------------
|
|
2
|
+
flow.css / x
|
|
3
|
+
Flow — vertical flow of elements with margin-bottom space
|
|
4
|
+
|
|
5
|
+
Created by Andrey Shpigunov at 20.03.2025
|
|
6
|
+
All right reserved.
|
|
7
|
+
----------------------------------------*/
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
/*
|
|
11
|
+
.flow
|
|
12
|
+
.flow.s[0-12] (m,l,xl) - vertical space
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
.flow {
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
& > * { margin: 0 }
|
|
20
|
+
|
|
21
|
+
@for $i from 0 to 12 {
|
|
22
|
+
&.s$(i) > * {
|
|
23
|
+
margin-bottom: var(--space-$(i));
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
@media(--medium){
|
|
28
|
+
@for $i from 0 to 12 {
|
|
29
|
+
&.m\:s$(i) > * {
|
|
30
|
+
margin-bottom: var(--space-$(i));
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
@media(--large){
|
|
36
|
+
@for $i from 0 to 12 {
|
|
37
|
+
&.l\:s$(i) > * {
|
|
38
|
+
margin-bottom: var(--space-$(i));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
@media(--xlarge){
|
|
44
|
+
@for $i from 0 to 12 {
|
|
45
|
+
&.xl\:s$(i) > * {
|
|
46
|
+
margin-bottom: var(--space-$(i));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
& > *:last-child { margin-bottom: 0 }
|
|
52
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/*----------------------------------------
|
|
2
|
+
form.css / x
|
|
3
|
+
Form default styles
|
|
4
|
+
|
|
5
|
+
Created by Andrey Shpigunov at 20.03.2025
|
|
6
|
+
All right reserved.
|
|
7
|
+
----------------------------------------*/
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
:root {
|
|
11
|
+
--form-width: 40rem;
|
|
12
|
+
--form-height-coeff: 2.5;
|
|
13
|
+
--form-side-padding: 1rem;
|
|
14
|
+
|
|
15
|
+
--form-font-size: var(--font-size);
|
|
16
|
+
--form-font-color: #000;
|
|
17
|
+
--form-font-color-readonly: #000;
|
|
18
|
+
--form-font-color-disabled: #666;
|
|
19
|
+
--form-font-color-placeholder: #ccc;
|
|
20
|
+
|
|
21
|
+
--form-border-width: 0.1rem;
|
|
22
|
+
--form-border-radius: 0.8rem;
|
|
23
|
+
--form-border-color: #d4d4d4;
|
|
24
|
+
--form-border-color-focused: #8cf;
|
|
25
|
+
--form-border-color-error: #f00;
|
|
26
|
+
--form-border-color-readonly: #ddd;
|
|
27
|
+
--form-border-color-disabled: #ddd;
|
|
28
|
+
|
|
29
|
+
--form-background-color: #fff;
|
|
30
|
+
--form-background-color-readonly: #fafafa;
|
|
31
|
+
--form-background-color-disabled: #f6f6f6;
|
|
32
|
+
|
|
33
|
+
/*
|
|
34
|
+
Additional variables and their default values:
|
|
35
|
+
--form-height: 4rem;
|
|
36
|
+
*/
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
input[type="color"],
|
|
40
|
+
input[type="date"],
|
|
41
|
+
input[type="datetime"],
|
|
42
|
+
input[type="tel"],
|
|
43
|
+
input[type="email"],
|
|
44
|
+
input[type="month"],
|
|
45
|
+
input[type="number"],
|
|
46
|
+
input[type="password"],
|
|
47
|
+
input[type="search"],
|
|
48
|
+
input[type="text"],
|
|
49
|
+
input[type="time"],
|
|
50
|
+
input[type="url"],
|
|
51
|
+
input[type="week"],
|
|
52
|
+
input:not([type]),
|
|
53
|
+
textarea,
|
|
54
|
+
select {
|
|
55
|
+
position: relative;
|
|
56
|
+
width: 100%;
|
|
57
|
+
height: var(--form-height);
|
|
58
|
+
height: var(--form-height, calc(var(--form-font-size) * var(--form-height-coeff)));
|
|
59
|
+
margin: 0;
|
|
60
|
+
padding: 0 var(--form-side-padding);
|
|
61
|
+
font-size: var(--form-font-size);
|
|
62
|
+
color: var(--form-font-color);
|
|
63
|
+
outline: none;
|
|
64
|
+
border-width: var(--form-border-width);
|
|
65
|
+
border-style: solid;
|
|
66
|
+
border-color: var(--form-border-color);
|
|
67
|
+
border-radius: var(--form-border-radius);
|
|
68
|
+
background-color: var(--form-background-color);
|
|
69
|
+
appearance: none;
|
|
70
|
+
|
|
71
|
+
&[readonly],
|
|
72
|
+
&[readonly]:focus {
|
|
73
|
+
color: var(--form-font-color-readonly);
|
|
74
|
+
border-color: var(--form-border-color-readonly);
|
|
75
|
+
background-color: var(--form-background-color-readonly);
|
|
76
|
+
box-shadow: none;
|
|
77
|
+
}
|
|
78
|
+
&[disabled],
|
|
79
|
+
&[disabled]:focus {
|
|
80
|
+
color: var(--form-font-color-disabled);
|
|
81
|
+
border-color: var(--form-border-color-disabled);
|
|
82
|
+
background-color: var(--form-background-color-disabled);
|
|
83
|
+
cursor: not-allowed !important;
|
|
84
|
+
box-shadow: none;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
&:focus {
|
|
88
|
+
border-color: var(--form-border-color-focused);
|
|
89
|
+
box-shadow: 0 0 0 1px var(--form-border-color-focused);
|
|
90
|
+
}
|
|
91
|
+
&.error,
|
|
92
|
+
&.error:focus,
|
|
93
|
+
&[pattern]:invalid,
|
|
94
|
+
&[pattern]:invalid:focus {
|
|
95
|
+
border-color: var(--form-border-color-error);
|
|
96
|
+
box-shadow: 0 0 0 1px var(--form-border-color-error);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
select {
|
|
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;
|
|
104
|
+
background-position: right calc(var(--form-side-padding) - 4px) top 52%, 0 0;
|
|
105
|
+
background-size: 1.6rem;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
::placeholder {
|
|
109
|
+
color: var(--form-font-color-placeholder);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
input[placeholder],
|
|
113
|
+
input::-moz-placeholder,
|
|
114
|
+
input:-moz-placeholder,
|
|
115
|
+
input:-ms-input-placeholder {
|
|
116
|
+
text-overflow: ellipsis;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
input,
|
|
120
|
+
textarea,
|
|
121
|
+
select,
|
|
122
|
+
button {
|
|
123
|
+
max-width: var(--form-width);
|
|
124
|
+
font-family: inherit;
|
|
125
|
+
font-size: var(--font-size);
|
|
126
|
+
box-sizing: border-box;
|
|
127
|
+
box-shadow: none;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
textarea {
|
|
131
|
+
height: auto;
|
|
132
|
+
padding-top: calc(var(--form-side-padding) / 2);
|
|
133
|
+
padding-bottom: calc(var(--form-side-padding) / 2);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
::-webkit-file-upload-button {
|
|
137
|
+
cursor:pointer;
|
|
138
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Additional form utilities for inputs, checkboxes, selects, and contenteditable elements.
|
|
3
|
+
*
|
|
4
|
+
* Provides methods for setting values, managing checkboxes, attaching/removing event listeners,
|
|
5
|
+
* and manually dispatching events.
|
|
6
|
+
*
|
|
7
|
+
* Exported singleton: `form`
|
|
8
|
+
*
|
|
9
|
+
* Public API:
|
|
10
|
+
*
|
|
11
|
+
* - `form.setChecked(selector, checked)` – Set checked state of checkboxes/radios.
|
|
12
|
+
* - `form.setValue(selector, value)` – Set value of form elements or contenteditable.
|
|
13
|
+
* - `form.onUpdate(selector, callback)` – Attach listener for input/change events.
|
|
14
|
+
* - `form.offUpdate(selector)` – Remove previously added listeners (partial, due to anonymous functions).
|
|
15
|
+
* - `form.update(selector)` – Manually dispatch input/change event.
|
|
16
|
+
*
|
|
17
|
+
* Example usage:
|
|
18
|
+
*
|
|
19
|
+
* form.setValue('.name', 'John');
|
|
20
|
+
* form.setChecked('#agree', true);
|
|
21
|
+
* form.onUpdate('input', el => console.log(el.value));
|
|
22
|
+
*
|
|
23
|
+
* @author Andrey Shpigunov
|
|
24
|
+
* @version 0.2
|
|
25
|
+
* @since 2025-07-17
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import { lib } from './lib';
|
|
29
|
+
|
|
30
|
+
class Form {
|
|
31
|
+
/**
|
|
32
|
+
* Creates a Form utility instance.
|
|
33
|
+
* Stores internal references to event listeners.
|
|
34
|
+
*/
|
|
35
|
+
constructor() {
|
|
36
|
+
/**
|
|
37
|
+
* Stores event listener references to avoid duplicates.
|
|
38
|
+
*
|
|
39
|
+
* @readonly
|
|
40
|
+
* @type {{update: Set<HTMLElement>}}
|
|
41
|
+
*/
|
|
42
|
+
this.listen = Object.freeze({
|
|
43
|
+
update: new Set(),
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Determines the appropriate event type for an element.
|
|
49
|
+
*
|
|
50
|
+
* @param {HTMLElement} el - Target element.
|
|
51
|
+
* @returns {string} 'input' or 'change' depending on the element type.
|
|
52
|
+
* @throws {Error} If the element is unsupported.
|
|
53
|
+
*/
|
|
54
|
+
getEventType(el) {
|
|
55
|
+
if (el.isContentEditable) return 'input';
|
|
56
|
+
|
|
57
|
+
const tag = el.tagName.toLowerCase();
|
|
58
|
+
if (tag === 'input' || tag === 'textarea') return 'input';
|
|
59
|
+
if (tag === 'select') return 'change';
|
|
60
|
+
|
|
61
|
+
throw new Error(`Unsupported element <${tag}>`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Sets the checked state of checkboxes or radios and dispatches 'input' event if changed.
|
|
66
|
+
*
|
|
67
|
+
* @param {string} selector - CSS selector for checkboxes or radios.
|
|
68
|
+
* @param {boolean} [checked=false] - Desired checked state.
|
|
69
|
+
*/
|
|
70
|
+
setChecked(selector, checked = false) {
|
|
71
|
+
for (const el of lib.qsa(selector)) {
|
|
72
|
+
if (!el?.type || (el.type !== 'checkbox' && el.type !== 'radio')) {
|
|
73
|
+
console.error('setChecked: Not a checkbox/radio', el);
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (el.checked !== checked) {
|
|
78
|
+
el.checked = checked;
|
|
79
|
+
el.dispatchEvent(new Event('input', { bubbles: true }));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Sets the value of inputs, textareas, selects, or contenteditable elements and dispatches event.
|
|
86
|
+
*
|
|
87
|
+
* @param {string} selector - CSS selector for elements.
|
|
88
|
+
* @param {string|boolean|number} value - Value to set.
|
|
89
|
+
* @throws {Error} If element type is unsupported.
|
|
90
|
+
*/
|
|
91
|
+
setValue(selector, value) {
|
|
92
|
+
for (const el of lib.qsa(selector)) {
|
|
93
|
+
if (!el) {
|
|
94
|
+
console.error('setValue: Element not found', el);
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (el.isContentEditable) {
|
|
99
|
+
el.innerText = value;
|
|
100
|
+
} else {
|
|
101
|
+
const tag = el.tagName.toLowerCase();
|
|
102
|
+
const type = el.type;
|
|
103
|
+
|
|
104
|
+
if (tag === 'input') {
|
|
105
|
+
if (type === 'checkbox' || type === 'radio') {
|
|
106
|
+
el.checked = !!value;
|
|
107
|
+
} else {
|
|
108
|
+
el.value = value;
|
|
109
|
+
}
|
|
110
|
+
} else if (tag === 'textarea' || tag === 'select') {
|
|
111
|
+
el.value = value;
|
|
112
|
+
} else {
|
|
113
|
+
throw new Error(`setValue: Unsupported element <${tag}>`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
el.dispatchEvent(new Event(this.getEventType(el), { bubbles: true }));
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Attaches input/change listeners to elements, preventing duplicates.
|
|
123
|
+
*
|
|
124
|
+
* @param {string} selector - CSS selector for elements.
|
|
125
|
+
* @param {Function} callback - Function to call when input or change event fires.
|
|
126
|
+
*/
|
|
127
|
+
onUpdate(selector, callback) {
|
|
128
|
+
for (const el of lib.qsa(selector)) {
|
|
129
|
+
if (!el) {
|
|
130
|
+
console.error('onUpdate: Element not found', el);
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (this.listen.update.has(el)) continue;
|
|
135
|
+
|
|
136
|
+
el.addEventListener(this.getEventType(el), () => callback(el));
|
|
137
|
+
this.listen.update.add(el);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Removes references to update listeners added by `onUpdate`.
|
|
143
|
+
*
|
|
144
|
+
* Note: Actual event listeners are not removed because they were added as anonymous functions.
|
|
145
|
+
* This method only cleans the internal tracking set.
|
|
146
|
+
*
|
|
147
|
+
* @param {string} selector - CSS selector for elements.
|
|
148
|
+
*/
|
|
149
|
+
offUpdate(selector) {
|
|
150
|
+
for (const el of lib.qsa(selector)) {
|
|
151
|
+
if (!el) continue;
|
|
152
|
+
if (!this.listen.update.has(el)) continue;
|
|
153
|
+
|
|
154
|
+
console.warn('offUpdate: Cannot fully remove listener because anonymous functions were used.');
|
|
155
|
+
this.listen.update.delete(el);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Manually dispatches 'input' or 'change' events on the selected elements.
|
|
161
|
+
*
|
|
162
|
+
* @param {string} selector - CSS selector for elements.
|
|
163
|
+
*/
|
|
164
|
+
update(selector) {
|
|
165
|
+
for (const el of lib.qsa(selector)) {
|
|
166
|
+
if (!el) {
|
|
167
|
+
console.error('update: Element not found', el);
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
el.dispatchEvent(new Event(this.getEventType(el), { bubbles: true }));
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Singleton export of the Form utility.
|
|
178
|
+
* @type {Form}
|
|
179
|
+
*/
|
|
180
|
+
export const form = new Form();
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/*----------------------------------------
|
|
2
|
+
grid.css / x
|
|
3
|
+
Grid prototype
|
|
4
|
+
|
|
5
|
+
Created by Andrey Shpigunov at 20.03.2025
|
|
6
|
+
All right reserved.
|
|
7
|
+
----------------------------------------*/
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
/*
|
|
11
|
+
.grid
|
|
12
|
+
.grid.g[1-12] (m,l,xl) - columns limit in grid
|
|
13
|
+
.grid > .c[1-12] (m,l,xl) - column width
|
|
14
|
+
.grid > .c[1-12]/[1-12] (m,l,xl) - columns range for content
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
.grid {
|
|
19
|
+
display: grid;
|
|
20
|
+
width: 100%;
|
|
21
|
+
grid-template-columns: repeat(12, 1fr);
|
|
22
|
+
grid-auto-flow: dense;
|
|
23
|
+
|
|
24
|
+
@for $i from 1 to 12 {
|
|
25
|
+
&.g$(i) { grid-template-columns: repeat($(i), 1fr) }
|
|
26
|
+
& > .c$(i) { grid-column: span $(i) }
|
|
27
|
+
|
|
28
|
+
@for $m from 12 to $i {
|
|
29
|
+
& > .c$(i)\/$(m) {
|
|
30
|
+
grid-column: $(i) / calc($(m) + 1)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
& .jss { justify-self: start }
|
|
36
|
+
& .jse { justify-self: end }
|
|
37
|
+
& .jsc { justify-self: center }
|
|
38
|
+
& .jsstr { justify-self: stretch }
|
|
39
|
+
& .ass { align-self: start }
|
|
40
|
+
& .ase { align-self: end }
|
|
41
|
+
& .asc { align-self: center }
|
|
42
|
+
& .asstr { align-self: stretch }
|
|
43
|
+
|
|
44
|
+
@media (--medium) {
|
|
45
|
+
@for $i from 1 to 12 {
|
|
46
|
+
&.m\:g$(i) { grid-template-columns: repeat($(i), 1fr) }
|
|
47
|
+
& > .m\:c$(i) { grid-column: span $(i) }
|
|
48
|
+
|
|
49
|
+
@for $m from 12 to $i {
|
|
50
|
+
& > .m\:c$(i)\/$(m) {
|
|
51
|
+
grid-column: $(i) / calc($(m) + 1)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
& .m\:jss { justify-self: start }
|
|
57
|
+
& .m\:jse { justify-self: end }
|
|
58
|
+
& .m\:jsc { justify-self: center }
|
|
59
|
+
& .m\:jsstr { justify-self: stretch }
|
|
60
|
+
& .m\:ass { align-self: start }
|
|
61
|
+
& .m\:ase { align-self: end }
|
|
62
|
+
& .m\:asc { align-self: center }
|
|
63
|
+
& .m\:asstr { align-self: stretch }
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
@media (--large) {
|
|
67
|
+
@for $i from 1 to 12 {
|
|
68
|
+
&.l\:g$(i) { grid-template-columns: repeat($(i), 1fr) }
|
|
69
|
+
& > .l\:c$(i) { grid-column: span $(i) }
|
|
70
|
+
|
|
71
|
+
@for $m from 12 to $i {
|
|
72
|
+
& > .l\:c$(i)\/$(m) {
|
|
73
|
+
grid-column: $(i) / calc($(m) + 1)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
& .l\:jss { justify-self: start }
|
|
79
|
+
& .l\:jse { justify-self: end }
|
|
80
|
+
& .l\:jsc { justify-self: center }
|
|
81
|
+
& .l\:jsstr { justify-self: stretch }
|
|
82
|
+
& .l\:ass { align-self: start }
|
|
83
|
+
& .l\:ase { align-self: end }
|
|
84
|
+
& .l\:asc { align-self: center }
|
|
85
|
+
& .l\:asstr { align-self: stretch }
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
@media (--xlarge) {
|
|
89
|
+
@for $i from 1 to 12 {
|
|
90
|
+
&.xl\:g$(i) { grid-template-columns: repeat($(i), 1fr) }
|
|
91
|
+
& > .xl\:c$(i) { grid-column: span $(i) }
|
|
92
|
+
|
|
93
|
+
@for $m from 12 to $i {
|
|
94
|
+
& > .xl\:c$(i)\/$(m) {
|
|
95
|
+
grid-column: $(i) / calc($(m) + 1)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
& .xl\:jss { justify-self: start }
|
|
101
|
+
& .xl\:jse { justify-self: end }
|
|
102
|
+
& .xl\:jsc { justify-self: center }
|
|
103
|
+
& .xl\:jsstr { justify-self: stretch }
|
|
104
|
+
& .xl\:ass { align-self: start }
|
|
105
|
+
& .xl\:ase { align-self: end }
|
|
106
|
+
& .xl\:asc { align-self: center }
|
|
107
|
+
& .xl\:asstr { align-self: stretch }
|
|
108
|
+
}
|
|
109
|
+
}
|