@jxsuite/ui 0.10.3 → 0.11.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/components/jx-accordion-item.json +5 -1
- package/components/jx-action-button.json +1 -1
- package/components/jx-field.json +8 -0
- package/components/jx-slider.json +205 -0
- package/components/jx-tree-item.json +5 -0
- package/icons/list.json +1 -0
- package/icons/manifest.json +3 -0
- package/package.json +3 -3
- package/project.json +8 -2
- package/src/behaviors/slider.ts +126 -0
- package/src/documents.ts +2 -0
- package/src/index.ts +2 -0
- package/src/loaders.ts +1 -0
- package/stylebook/jx-icon.json +1 -0
- package/stylebook/jx-slider.json +151 -0
|
@@ -167,7 +167,11 @@
|
|
|
167
167
|
"overflow": "hidden",
|
|
168
168
|
"textOverflow": "ellipsis",
|
|
169
169
|
"whiteSpace": "nowrap",
|
|
170
|
-
"
|
|
170
|
+
"color": "var(--jx-heading-color)",
|
|
171
|
+
"fontSize": "var(--jx-heading-size)",
|
|
172
|
+
"fontWeight": "var(--jx-heading-weight)",
|
|
173
|
+
"letterSpacing": "var(--jx-heading-tracking)",
|
|
174
|
+
"textTransform": "uppercase"
|
|
171
175
|
},
|
|
172
176
|
"& > [part=\"details\"] > [part=\"summary\"] > [part=\"actions\"]": {
|
|
173
177
|
"display": "inline-flex",
|
|
@@ -432,7 +432,7 @@
|
|
|
432
432
|
"gap": "2px",
|
|
433
433
|
"width": "100%",
|
|
434
434
|
"minWidth": "0",
|
|
435
|
-
"padding": "var(--jx-space-2)
|
|
435
|
+
"padding": "var(--jx-space-2) 0",
|
|
436
436
|
"borderRadius": "0",
|
|
437
437
|
"fontSize": "var(--jx-text-xs)",
|
|
438
438
|
"lineHeight": "var(--jx-leading-xs)"
|
package/components/jx-field.json
CHANGED
|
@@ -85,6 +85,14 @@
|
|
|
85
85
|
"textOverflow": "ellipsis",
|
|
86
86
|
"whiteSpace": "nowrap"
|
|
87
87
|
},
|
|
88
|
+
"@container (max-width: 288px)": {
|
|
89
|
+
"$description": "A narrow row stacks on its own: when the nearest inline-size container is under 288px, the label goes above the control and reads from the left, exactly as `span` draws it. Under that width the 80px label column leaves the control too little room and cuts long labels off. A host opts its regions in by declaring `container-type: inline-size` (Studio's side panels do); with no container above it a row keeps its two columns, so a dialog's form is unchanged. The width is a px literal because a container condition cannot read a custom property, and not rem because Studio roots its type at 12px, which would make 18rem 216px.",
|
|
90
|
+
"gridTemplateColumns": "minmax(0, 1fr)",
|
|
91
|
+
"& > [part=\"label\"]": {
|
|
92
|
+
"width": "auto",
|
|
93
|
+
"textAlign": "left"
|
|
94
|
+
}
|
|
95
|
+
},
|
|
88
96
|
"&[data-span] > [part=\"label\"]": {
|
|
89
97
|
"width": "auto",
|
|
90
98
|
"textAlign": "left"
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://jxsuite.com/schema/v1",
|
|
3
|
+
"$id": "JxSlider",
|
|
4
|
+
"tagName": "jx-slider",
|
|
5
|
+
"observedAttributes": [
|
|
6
|
+
"label",
|
|
7
|
+
"labelledby",
|
|
8
|
+
"value",
|
|
9
|
+
"min",
|
|
10
|
+
"max",
|
|
11
|
+
"step",
|
|
12
|
+
"valuetext",
|
|
13
|
+
"disabled"
|
|
14
|
+
],
|
|
15
|
+
"state": {
|
|
16
|
+
"label": {
|
|
17
|
+
"type": "string",
|
|
18
|
+
"default": "",
|
|
19
|
+
"attribute": "label",
|
|
20
|
+
"description": "The accessible name, forwarded to the inner range as aria-label. Required unless labelledby names a visible one: a slider is in the accessibility lint's LABELLED_ROLES, so it may not take its name from anything it contains, and an unnamed track is announced as \"slider\" and a number."
|
|
21
|
+
},
|
|
22
|
+
"labelledby": {
|
|
23
|
+
"type": "string",
|
|
24
|
+
"default": "",
|
|
25
|
+
"attribute": "labelledby",
|
|
26
|
+
"description": "Forwarded to the inner range as aria-labelledby, for a visible label the element does not own."
|
|
27
|
+
},
|
|
28
|
+
"value": {
|
|
29
|
+
"type": "number",
|
|
30
|
+
"default": 0,
|
|
31
|
+
"attribute": "value",
|
|
32
|
+
"description": "Where the thumb is, in the track's own units. It is bound to the inner range BOTH ways: as the value property, which is the live value once the reader's first drag has set the control's dirty flag, and as the value attribute, which is the control's default."
|
|
33
|
+
},
|
|
34
|
+
"min": {
|
|
35
|
+
"type": "number",
|
|
36
|
+
"default": 0,
|
|
37
|
+
"attribute": "min",
|
|
38
|
+
"description": "The bottom of the range."
|
|
39
|
+
},
|
|
40
|
+
"max": {
|
|
41
|
+
"type": "number",
|
|
42
|
+
"default": 100,
|
|
43
|
+
"attribute": "max",
|
|
44
|
+
"description": "The top of the range."
|
|
45
|
+
},
|
|
46
|
+
"step": {
|
|
47
|
+
"type": "number",
|
|
48
|
+
"default": 1,
|
|
49
|
+
"attribute": "step",
|
|
50
|
+
"description": "The grid the value moves on, for the platform's own arrow keys and for the kit's ten-step Shift+Arrow."
|
|
51
|
+
},
|
|
52
|
+
"valuetext": {
|
|
53
|
+
"type": "string",
|
|
54
|
+
"default": "",
|
|
55
|
+
"attribute": "valuetext",
|
|
56
|
+
"description": "What the value is announced as, forwarded to aria-valuetext: \"125%\" rather than the bare track position. Empty leaves the number to be read, which is right for a track whose units are its position."
|
|
57
|
+
},
|
|
58
|
+
"disabled": {
|
|
59
|
+
"type": "boolean",
|
|
60
|
+
"default": false,
|
|
61
|
+
"attribute": "disabled",
|
|
62
|
+
"description": "Cannot be moved or focused. Forwarded to the inner range, so the refusal is the platform's rather than a style that only looks like one."
|
|
63
|
+
},
|
|
64
|
+
"fill": "${state.max > state.min ? Math.min(100, Math.max(0, (state.value - state.min) / (state.max - state.min) * 100)) : 0}",
|
|
65
|
+
"onInput": {
|
|
66
|
+
"$prototype": "Function",
|
|
67
|
+
"$src": "jx-ui:/behaviors/slider.ts",
|
|
68
|
+
"$export": "onSliderInput",
|
|
69
|
+
"emits": [
|
|
70
|
+
{
|
|
71
|
+
"name": "input",
|
|
72
|
+
"description": "The value is moving under the reader's hand. It comes from the ELEMENT, so e.target.value is the number in the track's own units."
|
|
73
|
+
}
|
|
74
|
+
],
|
|
75
|
+
"description": "Mirrors the inner range into value and re-says the platform's own input from the host."
|
|
76
|
+
},
|
|
77
|
+
"onChange": {
|
|
78
|
+
"$prototype": "Function",
|
|
79
|
+
"$src": "jx-ui:/behaviors/slider.ts",
|
|
80
|
+
"$export": "onSliderChange",
|
|
81
|
+
"emits": [
|
|
82
|
+
{
|
|
83
|
+
"name": "change",
|
|
84
|
+
"description": "The reader committed the value; e.target is the element."
|
|
85
|
+
}
|
|
86
|
+
],
|
|
87
|
+
"description": "The same mirror and the same re-say, for the commit."
|
|
88
|
+
},
|
|
89
|
+
"onKeydown": {
|
|
90
|
+
"$prototype": "Function",
|
|
91
|
+
"$src": "jx-ui:/behaviors/slider.ts",
|
|
92
|
+
"$export": "onSliderKeydown",
|
|
93
|
+
"description": "Shift+Arrow moves ten steps, which no range has of its own. A plain arrow, Home, End and the Page keys are the platform's and are not touched."
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
"oninput": {
|
|
97
|
+
"$ref": "#/state/onInput"
|
|
98
|
+
},
|
|
99
|
+
"onchange": {
|
|
100
|
+
"$ref": "#/state/onChange"
|
|
101
|
+
},
|
|
102
|
+
"onkeydown": {
|
|
103
|
+
"$ref": "#/state/onKeydown"
|
|
104
|
+
},
|
|
105
|
+
"attributes": {
|
|
106
|
+
"data-disabled": "${state.disabled ? '' : null}"
|
|
107
|
+
},
|
|
108
|
+
"style": {
|
|
109
|
+
"display": "block",
|
|
110
|
+
"&[hidden]": {
|
|
111
|
+
"display": "none"
|
|
112
|
+
},
|
|
113
|
+
"boxSizing": "border-box",
|
|
114
|
+
"inlineSize": "100%",
|
|
115
|
+
"minInlineSize": "64px",
|
|
116
|
+
"--jx-slider-track-h": "4px",
|
|
117
|
+
"--jx-slider-thumb": "12px",
|
|
118
|
+
"--jx-slider-fill": "${state.fill}%",
|
|
119
|
+
"&[data-disabled]": {
|
|
120
|
+
"opacity": "0.5"
|
|
121
|
+
},
|
|
122
|
+
"& [part=\"input\"]": {
|
|
123
|
+
"appearance": "none",
|
|
124
|
+
"display": "block",
|
|
125
|
+
"boxSizing": "border-box",
|
|
126
|
+
"inlineSize": "100%",
|
|
127
|
+
"blockSize": "var(--jx-control-h)",
|
|
128
|
+
"margin": "0",
|
|
129
|
+
"padding": "0",
|
|
130
|
+
"border": "0",
|
|
131
|
+
"background": "transparent",
|
|
132
|
+
"font": "inherit",
|
|
133
|
+
"cursor": "pointer",
|
|
134
|
+
"outline": "none",
|
|
135
|
+
":focus-visible": {
|
|
136
|
+
"outline": "var(--jx-focus-ring)",
|
|
137
|
+
"outlineOffset": "1px",
|
|
138
|
+
"borderRadius": "var(--jx-radius-sm)"
|
|
139
|
+
},
|
|
140
|
+
":disabled": {
|
|
141
|
+
"cursor": "default"
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
"& [part=\"input\"]::-webkit-slider-runnable-track": {
|
|
145
|
+
"blockSize": "var(--jx-slider-track-h)",
|
|
146
|
+
"borderRadius": "var(--jx-radius-sm)",
|
|
147
|
+
"background": "linear-gradient(to right, var(--jx-accent-solid) var(--jx-slider-fill), var(--jx-border-strong) var(--jx-slider-fill))"
|
|
148
|
+
},
|
|
149
|
+
"& [part=\"input\"]::-moz-range-track": {
|
|
150
|
+
"blockSize": "var(--jx-slider-track-h)",
|
|
151
|
+
"borderRadius": "var(--jx-radius-sm)",
|
|
152
|
+
"background": "var(--jx-border-strong)"
|
|
153
|
+
},
|
|
154
|
+
"& [part=\"input\"]::-moz-range-progress": {
|
|
155
|
+
"blockSize": "var(--jx-slider-track-h)",
|
|
156
|
+
"borderRadius": "var(--jx-radius-sm)",
|
|
157
|
+
"background": "var(--jx-accent-solid)"
|
|
158
|
+
},
|
|
159
|
+
"& [part=\"input\"]::-webkit-slider-thumb": {
|
|
160
|
+
"appearance": "none",
|
|
161
|
+
"boxSizing": "border-box",
|
|
162
|
+
"inlineSize": "var(--jx-slider-thumb)",
|
|
163
|
+
"blockSize": "var(--jx-slider-thumb)",
|
|
164
|
+
"marginBlockStart": "calc((var(--jx-slider-track-h) - var(--jx-slider-thumb)) / 2)",
|
|
165
|
+
"borderRadius": "50%",
|
|
166
|
+
"border": "2px solid var(--jx-bg)",
|
|
167
|
+
"boxShadow": "0 0 0 1px var(--jx-fg)",
|
|
168
|
+
"background": "var(--jx-fg)",
|
|
169
|
+
"cursor": "inherit"
|
|
170
|
+
},
|
|
171
|
+
"& [part=\"input\"]::-moz-range-thumb": {
|
|
172
|
+
"appearance": "none",
|
|
173
|
+
"boxSizing": "border-box",
|
|
174
|
+
"inlineSize": "var(--jx-slider-thumb)",
|
|
175
|
+
"blockSize": "var(--jx-slider-thumb)",
|
|
176
|
+
"borderRadius": "50%",
|
|
177
|
+
"border": "2px solid var(--jx-bg)",
|
|
178
|
+
"boxShadow": "0 0 0 1px var(--jx-fg)",
|
|
179
|
+
"background": "var(--jx-fg)",
|
|
180
|
+
"cursor": "inherit"
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
"children": [
|
|
184
|
+
{
|
|
185
|
+
"tagName": "input",
|
|
186
|
+
"attributes": {
|
|
187
|
+
"part": "input",
|
|
188
|
+
"type": "range",
|
|
189
|
+
"aria-label": "${state.label || null}",
|
|
190
|
+
"aria-labelledby": "${state.labelledby || null}",
|
|
191
|
+
"aria-valuetext": "${state.valuetext || null}",
|
|
192
|
+
"min": "${state.min}",
|
|
193
|
+
"max": "${state.max}",
|
|
194
|
+
"step": "${state.step}",
|
|
195
|
+
"value": "${state.value}",
|
|
196
|
+
"disabled": {
|
|
197
|
+
"$ref": "#/state/disabled"
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
"value": {
|
|
201
|
+
"$ref": "#/state/value"
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
]
|
|
205
|
+
}
|
|
@@ -181,9 +181,14 @@
|
|
|
181
181
|
"justifyContent": "center",
|
|
182
182
|
"inlineSize": "14px",
|
|
183
183
|
"flex": "none",
|
|
184
|
+
"alignSelf": "stretch",
|
|
184
185
|
"color": "var(--jx-fg-dim)",
|
|
185
186
|
"--jx-icon-size": "12px"
|
|
186
187
|
},
|
|
188
|
+
"&[aria-expanded] [part=\"twisty\"]": {
|
|
189
|
+
"$description": "A row that can open answers a press on its twisty, so it says so: the cursor is `pointer` rather than whatever the row inherits (a host that makes the whole row a drag handle draws `grab`, and a chevron that looks grabbable is an invitation to start a drag instead of a toggle). The box is the row's whole height (`align-self: stretch` above), because a click target as tall as a 12px glyph loses the press to a pixel of vertical drift. A leaf has no `aria-expanded` and keeps its row's cursor: its dead cell is not a control.",
|
|
190
|
+
"cursor": "pointer"
|
|
191
|
+
},
|
|
187
192
|
"& [part=\"label\"]": {
|
|
188
193
|
"flex": "1",
|
|
189
194
|
"minWidth": "0",
|
package/icons/list.json
CHANGED
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
"arrows-clockwise": ["regular"],
|
|
21
21
|
"arrows-horizontal": ["regular"],
|
|
22
22
|
"arrows-in-line-horizontal": ["regular"],
|
|
23
|
+
"arrows-in-line-vertical": ["regular"],
|
|
23
24
|
"arrows-out": ["regular"],
|
|
24
25
|
"arrows-out-line-horizontal": ["regular"],
|
|
25
26
|
"arrows-out-line-vertical": ["regular"],
|
package/icons/manifest.json
CHANGED
|
@@ -67,6 +67,9 @@
|
|
|
67
67
|
"arrows-in-line-horizontal": {
|
|
68
68
|
"regular": "M136,40V216a8,8,0,0,1-16,0V40a8,8,0,0,1,16,0ZM69.66,90.34a8,8,0,0,0-11.32,11.32L76.69,120H16a8,8,0,0,0,0,16H76.69L58.34,154.34a8,8,0,0,0,11.32,11.32l32-32a8,8,0,0,0,0-11.32ZM240,120H179.31l18.35-18.34a8,8,0,0,0-11.32-11.32l-32,32a8,8,0,0,0,0,11.32l32,32a8,8,0,0,0,11.32-11.32L179.31,136H240a8,8,0,0,0,0-16Z"
|
|
69
69
|
},
|
|
70
|
+
"arrows-in-line-vertical": {
|
|
71
|
+
"regular": "M224,128a8,8,0,0,1-8,8H40a8,8,0,0,1,0-16H216A8,8,0,0,1,224,128ZM122.34,101.66a8,8,0,0,0,11.32,0l32-32a8,8,0,0,0-11.32-11.32L136,76.69V16a8,8,0,0,0-16,0V76.69L101.66,58.34A8,8,0,0,0,90.34,69.66Zm11.32,52.68a8,8,0,0,0-11.32,0l-32,32a8,8,0,0,0,11.32,11.32L120,179.31V240a8,8,0,0,0,16,0V179.31l18.34,18.35a8,8,0,0,0,11.32-11.32Z"
|
|
72
|
+
},
|
|
70
73
|
"arrows-out": {
|
|
71
74
|
"regular": "M216,48V96a8,8,0,0,1-16,0V67.31l-42.34,42.35a8,8,0,0,1-11.32-11.32L188.69,56H160a8,8,0,0,1,0-16h48A8,8,0,0,1,216,48ZM98.34,146.34,56,188.69V160a8,8,0,0,0-16,0v48a8,8,0,0,0,8,8H96a8,8,0,0,0,0-16H67.31l42.35-42.34a8,8,0,0,0-11.32-11.32ZM208,152a8,8,0,0,0-8,8v28.69l-42.34-42.35a8,8,0,0,0-11.32,11.32L188.69,200H160a8,8,0,0,0,0,16h48a8,8,0,0,0,8-8V160A8,8,0,0,0,208,152ZM67.31,56H96a8,8,0,0,0,0-16H48a8,8,0,0,0-8,8V96a8,8,0,0,0,16,0V67.31l42.34,42.35a8,8,0,0,0,11.32-11.32Z"
|
|
72
75
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jxsuite/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "The Jx UI kit: interface elements authored as Jx documents, with their theme and icons",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -58,8 +58,8 @@
|
|
|
58
58
|
"upgrade": "bun update --latest && bun install"
|
|
59
59
|
},
|
|
60
60
|
"dependencies": {
|
|
61
|
-
"@jxsuite/runtime": "^6.
|
|
62
|
-
"@jxsuite/schema": "^5.
|
|
61
|
+
"@jxsuite/runtime": "^6.3.0",
|
|
62
|
+
"@jxsuite/schema": "^5.6.0"
|
|
63
63
|
},
|
|
64
64
|
"devDependencies": {
|
|
65
65
|
"@happy-dom/global-registrator": "^21.0.4",
|
package/project.json
CHANGED
|
@@ -15,7 +15,9 @@
|
|
|
15
15
|
"--jx-gray-200": "#e1e1e7",
|
|
16
16
|
"--jx-gray-300": "#c9c9d2",
|
|
17
17
|
"--jx-gray-400": "#a2a2af",
|
|
18
|
+
"--jx-gray-450": "#8f8f9d",
|
|
18
19
|
"--jx-gray-500": "#7c7c8b",
|
|
20
|
+
"--jx-gray-550": "#6d6d7c",
|
|
19
21
|
"--jx-gray-600": "#5e5e6c",
|
|
20
22
|
"--jx-gray-700": "#45454f",
|
|
21
23
|
"--jx-gray-800": "#2f2f36",
|
|
@@ -76,7 +78,7 @@
|
|
|
76
78
|
"--jx-border-strong": "light-dark(var(--jx-gray-300), var(--jx-gray-700))",
|
|
77
79
|
"--jx-fg": "light-dark(var(--jx-gray-900), var(--jx-gray-100))",
|
|
78
80
|
"--jx-fg-dim": "light-dark(var(--jx-gray-600), var(--jx-gray-400))",
|
|
79
|
-
"--jx-fg-muted": "var(--jx-gray-
|
|
81
|
+
"--jx-fg-muted": "light-dark(var(--jx-gray-550), var(--jx-gray-450))",
|
|
80
82
|
"--jx-accent": "light-dark(var(--jx-blue-600), var(--jx-blue-500))",
|
|
81
83
|
"--jx-accent-solid": "var(--jx-blue-600)",
|
|
82
84
|
"--jx-accent-hover": "light-dark(var(--jx-blue-700), var(--jx-blue-400))",
|
|
@@ -99,7 +101,7 @@
|
|
|
99
101
|
"--jx-switch-thumb": "light-dark(#ffffff, var(--jx-gray-100))",
|
|
100
102
|
"--jx-font-sans": "\"Inter Variable\", \"Inter\", system-ui, -apple-system, \"Segoe UI\", sans-serif",
|
|
101
103
|
"--jx-font-mono": "\"JetBrains Mono\", \"SF Mono\", \"Fira Code\", monospace",
|
|
102
|
-
"--jx-text-xs": "
|
|
104
|
+
"--jx-text-xs": "11px",
|
|
103
105
|
"--jx-text-sm": "11px",
|
|
104
106
|
"--jx-text-md": "12px",
|
|
105
107
|
"--jx-text-lg": "14px",
|
|
@@ -107,6 +109,10 @@
|
|
|
107
109
|
"--jx-leading-sm": "16px",
|
|
108
110
|
"--jx-leading-md": "18px",
|
|
109
111
|
"--jx-leading-lg": "20px",
|
|
112
|
+
"--jx-heading-size": "var(--jx-text-sm)",
|
|
113
|
+
"--jx-heading-weight": "600",
|
|
114
|
+
"--jx-heading-tracking": "0.06em",
|
|
115
|
+
"--jx-heading-color": "var(--jx-fg-dim)",
|
|
110
116
|
"--jx-space-1": "2px",
|
|
111
117
|
"--jx-space-2": "4px",
|
|
112
118
|
"--jx-space-3": "8px",
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `jx-slider`'s behaviour sidecar: ten steps, and one event contract.
|
|
3
|
+
*
|
|
4
|
+
* The element is ONE native `<input type="range">`, so almost nothing is left for this file. The
|
|
5
|
+
* platform owns the thumb, the drag, the along-axis arrows with their clamping and snapping, Home,
|
|
6
|
+
* End, PageUp, PageDown, the focus ring, `:disabled`, and every word of `role="slider"` with its
|
|
7
|
+
* `aria-valuemin`, `aria-valuemax` and `aria-valuenow`. An arrow key with no `Shift` on it returns
|
|
8
|
+
* from {@link onSliderKeydown} untouched so that stays true.
|
|
9
|
+
*
|
|
10
|
+
* Two things are the kit's, and both are `jx-color-slider`'s (its sidecar is the model):
|
|
11
|
+
* `Shift`+Arrow moves ten steps, which no range has of its own, and the inner control's own `input`
|
|
12
|
+
* and `change` are STOPPED and re-dispatched from the host, so a listener reads `e.target.value`
|
|
13
|
+
* off the ELEMENT as a number and never has to ask which node fired. A native `dblclick` is not
|
|
14
|
+
* touched: it bubbles from the range, and the host document that wants "double-click resets"
|
|
15
|
+
* listens for it.
|
|
16
|
+
*
|
|
17
|
+
* @docs extending/ui-kit
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { ARROW_SIGN, snapToStep } from "../color.ts";
|
|
21
|
+
|
|
22
|
+
/** The tag this sidecar belongs to. */
|
|
23
|
+
const HOST = "jx-slider";
|
|
24
|
+
|
|
25
|
+
/** The reactive scope a `jx-slider` document hands its handlers. */
|
|
26
|
+
export interface SliderState {
|
|
27
|
+
/** Where the thumb is, in the track's own units. */
|
|
28
|
+
value: number;
|
|
29
|
+
/** The bottom of the range. */
|
|
30
|
+
min: number;
|
|
31
|
+
/** The top of the range. */
|
|
32
|
+
max: number;
|
|
33
|
+
/** The grid the value moves on. */
|
|
34
|
+
step: number;
|
|
35
|
+
/** Whether the element refuses to move. */
|
|
36
|
+
disabled: boolean;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** The `jx-slider` an event is being handled on. */
|
|
40
|
+
function hostOf(event: Event): HTMLElement | null {
|
|
41
|
+
const target = event.currentTarget;
|
|
42
|
+
return target instanceof HTMLElement && target.localName === HOST ? target : null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** The element's own native range. */
|
|
46
|
+
function controlOf(host: HTMLElement): HTMLInputElement | null {
|
|
47
|
+
return host.querySelector<HTMLInputElement>('input[part="input"]');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Say the value moved, from the HOST, in the platform's own names. */
|
|
51
|
+
function announce(host: HTMLElement, ...names: string[]): void {
|
|
52
|
+
for (const name of names) {
|
|
53
|
+
host.dispatchEvent(new Event(name, { bubbles: true }));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* The inner range's `input`: mirror it, and re-say it from the host.
|
|
59
|
+
*
|
|
60
|
+
* @param state The element's reactive scope.
|
|
61
|
+
* @param event The range's own input event.
|
|
62
|
+
*/
|
|
63
|
+
export function onSliderInput(state: SliderState, event: Event): void {
|
|
64
|
+
const host = hostOf(event);
|
|
65
|
+
if (!host || event.target === host) {
|
|
66
|
+
/* The listener sits on the ROOT so `currentTarget` is the element, which means it also hears
|
|
67
|
+
the element's own re-dispatch. That one has already been accounted for. */
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
event.stopPropagation();
|
|
71
|
+
/* SAFETY: the element's only form control is its `input[part="input"]` range, so an `input` event
|
|
72
|
+
reaching the host from anything other than the host itself was fired by that range. */
|
|
73
|
+
state.value = Number((event.target as HTMLInputElement).value);
|
|
74
|
+
announce(host, "input");
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* The inner range's `change`: the same stop, and the commit re-said from the host.
|
|
79
|
+
*
|
|
80
|
+
* @param state The element's reactive scope.
|
|
81
|
+
* @param event The range's own change event.
|
|
82
|
+
*/
|
|
83
|
+
export function onSliderChange(state: SliderState, event: Event): void {
|
|
84
|
+
const host = hostOf(event);
|
|
85
|
+
if (!host || event.target === host) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
event.stopPropagation();
|
|
89
|
+
/* SAFETY: the element's only form control is its `input[part="input"]` range, so a `change` event
|
|
90
|
+
reaching the host from anything other than the host itself was fired by that range. */
|
|
91
|
+
state.value = Number((event.target as HTMLInputElement).value);
|
|
92
|
+
announce(host, "change");
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* `Shift`+Arrow: ten steps, cancelled so the platform's own single step does not run on top.
|
|
97
|
+
*
|
|
98
|
+
* @param state The element's reactive scope.
|
|
99
|
+
* @param event The keydown.
|
|
100
|
+
*/
|
|
101
|
+
export function onSliderKeydown(state: SliderState, event: KeyboardEvent): void {
|
|
102
|
+
const host = hostOf(event);
|
|
103
|
+
const sign = ARROW_SIGN.get(event.key);
|
|
104
|
+
if (!host || !sign || !event.shiftKey || state.disabled === true) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
event.preventDefault();
|
|
108
|
+
const min = Number.isFinite(Number(state.min)) ? Number(state.min) : 0;
|
|
109
|
+
const top = Number(state.max);
|
|
110
|
+
const max = Number.isFinite(top) && top > min ? top : min + 100;
|
|
111
|
+
const step =
|
|
112
|
+
Number(state.step) > 0 && Number.isFinite(Number(state.step)) ? Number(state.step) : 1;
|
|
113
|
+
const next = snapToStep(Number(state.value) + sign * step * 10, step, min, max);
|
|
114
|
+
if (next === Number(state.value)) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
state.value = next;
|
|
118
|
+
/* The control follows through its own binding, but the binding is what a RENDER does and the
|
|
119
|
+
events go out now, so the control is written here too: a host that reads the range back out of
|
|
120
|
+
`e.target` during the handler must not see the value it had before the key. */
|
|
121
|
+
const control = controlOf(host);
|
|
122
|
+
if (control) {
|
|
123
|
+
control.value = String(next);
|
|
124
|
+
}
|
|
125
|
+
announce(host, "input", "change");
|
|
126
|
+
}
|
package/src/documents.ts
CHANGED
|
@@ -27,6 +27,7 @@ import jxCombobox from "../components/jx-combobox.json";
|
|
|
27
27
|
import jxListbox from "../components/jx-listbox.json";
|
|
28
28
|
import jxOption from "../components/jx-option.json";
|
|
29
29
|
import jxSelect from "../components/jx-select.json";
|
|
30
|
+
import jxSlider from "../components/jx-slider.json";
|
|
30
31
|
import jxSpinner from "../components/jx-spinner.json";
|
|
31
32
|
import jxSplit from "../components/jx-split.json";
|
|
32
33
|
import jxTooltip from "../components/jx-tooltip.json";
|
|
@@ -86,6 +87,7 @@ export const documents = {
|
|
|
86
87
|
"jx-switch": kitDocument(jxSwitch),
|
|
87
88
|
"jx-number-field": kitDocument(jxNumberField),
|
|
88
89
|
"jx-select": kitDocument(jxSelect),
|
|
90
|
+
"jx-slider": kitDocument(jxSlider),
|
|
89
91
|
"jx-option": kitDocument(jxOption),
|
|
90
92
|
"jx-listbox": kitDocument(jxListbox),
|
|
91
93
|
"jx-combobox": kitDocument(jxCombobox),
|
package/src/index.ts
CHANGED
|
@@ -23,6 +23,7 @@ import * as numberField from "./behaviors/number-field.ts";
|
|
|
23
23
|
import * as actionGroup from "./behaviors/action-group.ts";
|
|
24
24
|
import * as popover from "./behaviors/popover.ts";
|
|
25
25
|
import * as select from "./behaviors/select.ts";
|
|
26
|
+
import * as slider from "./behaviors/slider.ts";
|
|
26
27
|
import * as split from "./behaviors/split.ts";
|
|
27
28
|
import * as swatchGroup from "./behaviors/swatch-group.ts";
|
|
28
29
|
import * as tabs from "./behaviors/tabs.ts";
|
|
@@ -68,6 +69,7 @@ export const KIT_MODULES = {
|
|
|
68
69
|
"jx-ui:/behaviors/action-group.ts": actionGroup,
|
|
69
70
|
"jx-ui:/behaviors/popover.ts": popover,
|
|
70
71
|
"jx-ui:/behaviors/select.ts": select,
|
|
72
|
+
"jx-ui:/behaviors/slider.ts": slider,
|
|
71
73
|
"jx-ui:/behaviors/split.ts": split,
|
|
72
74
|
"jx-ui:/behaviors/swatch-group.ts": swatchGroup,
|
|
73
75
|
"jx-ui:/behaviors/tabs.ts": tabs,
|
package/src/loaders.ts
CHANGED
|
@@ -38,6 +38,7 @@ export const KIT_LOADERS = {
|
|
|
38
38
|
"jx-ui:/behaviors/action-group.ts": () => import("./behaviors/action-group.ts"),
|
|
39
39
|
"jx-ui:/behaviors/popover.ts": () => import("./behaviors/popover.ts"),
|
|
40
40
|
"jx-ui:/behaviors/select.ts": () => import("./behaviors/select.ts"),
|
|
41
|
+
"jx-ui:/behaviors/slider.ts": () => import("./behaviors/slider.ts"),
|
|
41
42
|
"jx-ui:/behaviors/split.ts": () => import("./behaviors/split.ts"),
|
|
42
43
|
"jx-ui:/behaviors/swatch-group.ts": () => import("./behaviors/swatch-group.ts"),
|
|
43
44
|
"jx-ui:/behaviors/tabs.ts": () => import("./behaviors/tabs.ts"),
|
package/stylebook/jx-icon.json
CHANGED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://jxsuite.com/schema/v1",
|
|
3
|
+
"$id": "StylebookSlider",
|
|
4
|
+
"tagName": "main",
|
|
5
|
+
"$elements": [
|
|
6
|
+
{
|
|
7
|
+
"$ref": "../components/jx-slider.json"
|
|
8
|
+
}
|
|
9
|
+
],
|
|
10
|
+
"state": {
|
|
11
|
+
"level": 40
|
|
12
|
+
},
|
|
13
|
+
"style": {
|
|
14
|
+
"fontFamily": "var(--jx-font-sans)",
|
|
15
|
+
"fontSize": "var(--jx-text-md)",
|
|
16
|
+
"color": "var(--jx-fg)",
|
|
17
|
+
"background": "var(--jx-bg)",
|
|
18
|
+
"padding": "var(--jx-space-6)",
|
|
19
|
+
"minHeight": "100vh",
|
|
20
|
+
"& > h1": {
|
|
21
|
+
"fontSize": "var(--jx-text-lg)",
|
|
22
|
+
"fontWeight": "600",
|
|
23
|
+
"margin": "0 0 var(--jx-space-5)"
|
|
24
|
+
},
|
|
25
|
+
"& > h2": {
|
|
26
|
+
"fontSize": "var(--jx-text-md)",
|
|
27
|
+
"fontWeight": "600",
|
|
28
|
+
"margin": "var(--jx-space-6) 0 var(--jx-space-3)"
|
|
29
|
+
},
|
|
30
|
+
"& > p": {
|
|
31
|
+
"color": "var(--jx-fg-dim)",
|
|
32
|
+
"margin": "0 0 var(--jx-space-5)"
|
|
33
|
+
},
|
|
34
|
+
"& [role=\"group\"]": {
|
|
35
|
+
"display": "flex",
|
|
36
|
+
"alignItems": "center",
|
|
37
|
+
"gap": "var(--jx-space-4)",
|
|
38
|
+
"margin": "0 0 var(--jx-space-4)",
|
|
39
|
+
"maxWidth": "420px"
|
|
40
|
+
},
|
|
41
|
+
"& [role=\"group\"] > span": {
|
|
42
|
+
"width": "90px",
|
|
43
|
+
"color": "var(--jx-fg-muted)",
|
|
44
|
+
"fontSize": "var(--jx-text-sm)"
|
|
45
|
+
},
|
|
46
|
+
"& [role=\"group\"] > jx-slider": {
|
|
47
|
+
"flex": "1 1 200px"
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
"children": [
|
|
51
|
+
{
|
|
52
|
+
"tagName": "h1",
|
|
53
|
+
"textContent": "jx-slider"
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"tagName": "p",
|
|
57
|
+
"textContent": "One native range. The platform supplies the slider role, the announced value, the thumb, the drag, the arrow keys with their clamping, and Home, End, PageUp and PageDown; the element adds a filled track, a ten-step Shift+Arrow and valuetext, and re-says the platform's own input and change from itself, so a listener always reads e.target.value off the element."
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"tagName": "h2",
|
|
61
|
+
"textContent": "Bound"
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"tagName": "div",
|
|
65
|
+
"attributes": {
|
|
66
|
+
"role": "group",
|
|
67
|
+
"aria-label": "Level, bound both ways"
|
|
68
|
+
},
|
|
69
|
+
"children": [
|
|
70
|
+
{
|
|
71
|
+
"tagName": "span",
|
|
72
|
+
"textContent": "Level"
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"tagName": "jx-slider",
|
|
76
|
+
"$props": {
|
|
77
|
+
"label": "Level",
|
|
78
|
+
"valuetext": "${Math.round(state.level)} percent",
|
|
79
|
+
"value": {
|
|
80
|
+
"$ref": "#/state/level"
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
"oninput": {
|
|
84
|
+
"$prototype": "Function",
|
|
85
|
+
"body": [
|
|
86
|
+
{
|
|
87
|
+
"operator": "=",
|
|
88
|
+
"target": {
|
|
89
|
+
"$ref": "#/state/level"
|
|
90
|
+
},
|
|
91
|
+
"value": {
|
|
92
|
+
"$ref": "event#/target/value"
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
]
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"tagName": "span",
|
|
100
|
+
"textContent": "${Math.round(state.level)}%"
|
|
101
|
+
}
|
|
102
|
+
]
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
"tagName": "h2",
|
|
106
|
+
"textContent": "States"
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
"tagName": "div",
|
|
110
|
+
"attributes": {
|
|
111
|
+
"role": "group",
|
|
112
|
+
"aria-label": "A coarse grid and a refusal"
|
|
113
|
+
},
|
|
114
|
+
"children": [
|
|
115
|
+
{
|
|
116
|
+
"tagName": "span",
|
|
117
|
+
"textContent": "Step 25"
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"tagName": "jx-slider",
|
|
121
|
+
"$props": {
|
|
122
|
+
"label": "Quarter steps",
|
|
123
|
+
"step": 25,
|
|
124
|
+
"value": 50
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
]
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
"tagName": "div",
|
|
131
|
+
"attributes": {
|
|
132
|
+
"role": "group",
|
|
133
|
+
"aria-label": "Disabled"
|
|
134
|
+
},
|
|
135
|
+
"children": [
|
|
136
|
+
{
|
|
137
|
+
"tagName": "span",
|
|
138
|
+
"textContent": "Disabled"
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
"tagName": "jx-slider",
|
|
142
|
+
"$props": {
|
|
143
|
+
"label": "Locked",
|
|
144
|
+
"disabled": true,
|
|
145
|
+
"value": 70
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
]
|
|
149
|
+
}
|
|
150
|
+
]
|
|
151
|
+
}
|