@rvx/ui 0.1.12 → 0.1.14
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/dist/common/coupling.d.ts +2 -0
- package/dist/common/coupling.js +18 -0
- package/dist/common/coupling.js.map +1 -0
- package/dist/common/theme.d.ts +14 -0
- package/dist/components/button.js +3 -2
- package/dist/components/button.js.map +1 -1
- package/dist/components/card.d.ts +1 -0
- package/dist/components/card.js +6 -3
- package/dist/components/card.js.map +1 -1
- package/dist/components/checkbox.js +6 -6
- package/dist/components/checkbox.js.map +1 -1
- package/dist/components/collapse.d.ts +16 -2
- package/dist/components/collapse.js +85 -4
- package/dist/components/collapse.js.map +1 -1
- package/dist/components/column.d.ts +1 -0
- package/dist/components/column.js +1 -0
- package/dist/components/column.js.map +1 -1
- package/dist/components/dialog.d.ts +1 -2
- package/dist/components/dialog.js +3 -5
- package/dist/components/dialog.js.map +1 -1
- package/dist/components/dropdown-input.js +2 -2
- package/dist/components/dropdown-input.js.map +1 -1
- package/dist/components/dropdown.js +2 -2
- package/dist/components/dropdown.js.map +1 -1
- package/dist/components/label.js +1 -1
- package/dist/components/label.js.map +1 -1
- package/dist/components/link.js +2 -1
- package/dist/components/link.js.map +1 -1
- package/dist/components/nav-list.d.ts +26 -0
- package/dist/components/nav-list.js +37 -0
- package/dist/components/nav-list.js.map +1 -0
- package/dist/components/popout.d.ts +1 -2
- package/dist/components/popout.js +1 -2
- package/dist/components/popout.js.map +1 -1
- package/dist/components/popover.js +1 -2
- package/dist/components/popover.js.map +1 -1
- package/dist/components/radio-buttons.js +11 -10
- package/dist/components/radio-buttons.js.map +1 -1
- package/dist/components/slider.d.ts +11 -0
- package/dist/components/slider.js +24 -0
- package/dist/components/slider.js.map +1 -0
- package/dist/components/tabs.d.ts +10 -0
- package/dist/components/tabs.js +31 -0
- package/dist/components/tabs.js.map +1 -0
- package/dist/components/text-input.js +5 -4
- package/dist/components/text-input.js.map +1 -1
- package/dist/components/validation-rules.d.ts +11 -0
- package/dist/components/validation-rules.js +37 -0
- package/dist/components/validation-rules.js.map +1 -0
- package/dist/components/validation.d.ts +53 -77
- package/dist/components/validation.js +117 -95
- package/dist/components/validation.js.map +1 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.js +5 -3
- package/dist/index.js.map +1 -1
- package/dist/theme.module.css +144 -0
- package/dist/theme.module.css.map +1 -1
- package/package.json +3 -3
- package/src/common/coupling.tsx +18 -0
- package/src/common/theme.tsx +17 -0
- package/src/components/button.tsx +3 -2
- package/src/components/card.tsx +10 -5
- package/src/components/checkbox.tsx +6 -6
- package/src/components/collapse.tsx +127 -5
- package/src/components/column.tsx +2 -0
- package/src/components/dialog.tsx +3 -5
- package/src/components/dropdown-input.tsx +2 -2
- package/src/components/dropdown.tsx +2 -2
- package/src/components/label.tsx +1 -3
- package/src/components/link.tsx +2 -1
- package/src/components/nav-list.tsx +81 -0
- package/src/components/popout.tsx +1 -2
- package/src/components/popover.tsx +1 -2
- package/src/components/radio-buttons.tsx +21 -20
- package/src/components/slider.tsx +55 -0
- package/src/components/tabs.tsx +67 -0
- package/src/components/text-input.tsx +5 -4
- package/src/components/validation-rules.tsx +50 -0
- package/src/components/validation.tsx +175 -177
- package/src/index.tsx +5 -3
- package/src/theme/base.scss +7 -0
- package/src/theme/components/card.scss +4 -1
- package/src/theme/components/checkbox.scss +5 -0
- package/src/theme/components/column.scss +5 -0
- package/src/theme/components/nav-list.scss +82 -0
- package/src/theme/components/radio-buttons.scss +5 -0
- package/src/theme/components/slider.scss +15 -0
- package/src/theme/components/tabs.scss +72 -0
- package/src/theme/theme.scss +3 -0
- package/dist/common/debounce.d.ts +0 -12
- package/dist/common/debounce.js +0 -23
- package/dist/common/debounce.js.map +0 -1
- package/dist/common/parsers.d.ts +0 -88
- package/dist/common/parsers.js +0 -62
- package/dist/common/parsers.js.map +0 -1
- package/dist/common/trim.d.ts +0 -12
- package/dist/common/trim.js +0 -16
- package/dist/common/trim.js.map +0 -1
- package/src/common/debounce.tsx +0 -36
- package/src/common/parsers.tsx +0 -167
- package/src/common/trim.tsx +0 -30
package/src/common/theme.tsx
CHANGED
|
@@ -33,6 +33,7 @@ export interface Theme {
|
|
|
33
33
|
button_text?: string;
|
|
34
34
|
|
|
35
35
|
card?: string;
|
|
36
|
+
card_raw?: string;
|
|
36
37
|
card_content?: string;
|
|
37
38
|
card_default?: string;
|
|
38
39
|
card_info?: string;
|
|
@@ -41,6 +42,7 @@ export interface Theme {
|
|
|
41
42
|
card_danger?: string;
|
|
42
43
|
|
|
43
44
|
checkbox_label?: string;
|
|
45
|
+
checkbox_padding?: string;
|
|
44
46
|
checkbox_input?: string;
|
|
45
47
|
checkbox_content?: string;
|
|
46
48
|
|
|
@@ -52,6 +54,7 @@ export interface Theme {
|
|
|
52
54
|
collapse_content?: string;
|
|
53
55
|
|
|
54
56
|
column?: string;
|
|
57
|
+
column_padded?: string;
|
|
55
58
|
column_content?: string;
|
|
56
59
|
column_control?: string;
|
|
57
60
|
|
|
@@ -78,6 +81,10 @@ export interface Theme {
|
|
|
78
81
|
|
|
79
82
|
link?: string;
|
|
80
83
|
|
|
84
|
+
nav_list?: string;
|
|
85
|
+
nav_list_item?: string;
|
|
86
|
+
nav_list_item_current?: string;
|
|
87
|
+
|
|
81
88
|
page?: string;
|
|
82
89
|
page_scrollbar_comp?: string;
|
|
83
90
|
page_content_col?: string;
|
|
@@ -91,6 +98,7 @@ export interface Theme {
|
|
|
91
98
|
|
|
92
99
|
radio_buttons?: string;
|
|
93
100
|
radio_button_label?: string;
|
|
101
|
+
radio_button_padding?: string;
|
|
94
102
|
radio_button_input?: string;
|
|
95
103
|
radio_button_content?: string;
|
|
96
104
|
|
|
@@ -105,6 +113,15 @@ export interface Theme {
|
|
|
105
113
|
scroll_view_indicator_end?: string;
|
|
106
114
|
scroll_view_indicator_visible?: string;
|
|
107
115
|
|
|
116
|
+
slider_host?: string;
|
|
117
|
+
|
|
118
|
+
tab_handle?: string;
|
|
119
|
+
tab_handle_current?: string;
|
|
120
|
+
tab_list?: string;
|
|
121
|
+
tab_list_padded?: string;
|
|
122
|
+
tab_panel?: string;
|
|
123
|
+
tab_panel_padded?: string;
|
|
124
|
+
|
|
108
125
|
text_input?: string;
|
|
109
126
|
|
|
110
127
|
text?: string;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { ClassValue, Expression, get,
|
|
1
|
+
import { ClassValue, Expression, get, StyleValue } from "rvx";
|
|
2
2
|
import { isPending } from "rvx/async";
|
|
3
|
+
import { optionalString } from "rvx/convert";
|
|
3
4
|
import { Action, handleActionEvent, keyFor } from "../common/events.js";
|
|
4
5
|
import { THEME } from "../common/theme.js";
|
|
5
6
|
import { Validator } from "./validation.js";
|
|
@@ -77,7 +78,7 @@ export function Button(props: {
|
|
|
77
78
|
aria-haspopup={props["aria-haspopup"]}
|
|
78
79
|
aria-controls={props["aria-controls"]}
|
|
79
80
|
aria-invalid={props.validator ? optionalString(props.validator.invalid) : undefined}
|
|
80
|
-
aria-errormessage={props.validator ? props.validator.
|
|
81
|
+
aria-errormessage={props.validator ? props.validator.messageIds : undefined}
|
|
81
82
|
autofocus={props.autofocus}
|
|
82
83
|
|
|
83
84
|
on:click={action}
|
package/src/components/card.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Expression,
|
|
1
|
+
import { Expression, map } from "rvx";
|
|
2
2
|
import { THEME } from "../common/theme.js";
|
|
3
3
|
import { Column } from "./column.js";
|
|
4
4
|
|
|
@@ -6,17 +6,22 @@ export type CardVariant = "default" | "info" | "success" | "warning" | "danger";
|
|
|
6
6
|
|
|
7
7
|
export function Card(props: {
|
|
8
8
|
variant?: Expression<CardVariant | undefined>;
|
|
9
|
+
raw?: boolean;
|
|
9
10
|
children?: unknown;
|
|
10
11
|
}): unknown {
|
|
11
12
|
const theme = THEME.current;
|
|
12
13
|
return <div
|
|
13
14
|
class={[
|
|
14
15
|
theme?.card,
|
|
15
|
-
(
|
|
16
|
+
map(props.variant, variant => theme?.[`card_${variant ?? "default"}`]),
|
|
17
|
+
map(props.raw, unpadded => unpadded ? theme?.card_raw : undefined),
|
|
16
18
|
]}
|
|
17
19
|
>
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
{props.raw
|
|
21
|
+
? props.children
|
|
22
|
+
: <Column class={theme?.card_content}>
|
|
23
|
+
{props.children}
|
|
24
|
+
</Column>
|
|
25
|
+
}
|
|
21
26
|
</div>;
|
|
22
27
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { ClassValue, Expression, get,
|
|
1
|
+
import { ClassValue, Expression, get, Signal, StyleValue, uniqueId, watch } from "rvx";
|
|
2
2
|
import { isPending } from "rvx/async";
|
|
3
|
-
import { uniqueId } from "rvx/id";
|
|
4
3
|
|
|
4
|
+
import { optionalString, string } from "rvx/convert";
|
|
5
5
|
import { THEME } from "../common/theme.js";
|
|
6
6
|
import { Text } from "./text.js";
|
|
7
|
-
import {
|
|
7
|
+
import { closestValidator } from "./validation.js";
|
|
8
8
|
|
|
9
9
|
export function Checkbox(props: {
|
|
10
10
|
checked?: Expression<boolean | undefined>;
|
|
@@ -23,7 +23,7 @@ export function Checkbox(props: {
|
|
|
23
23
|
? () => isPending() || get(props.disabled)
|
|
24
24
|
: () => true;
|
|
25
25
|
|
|
26
|
-
const validator = props.checked instanceof Signal ?
|
|
26
|
+
const validator = props.checked instanceof Signal ? closestValidator(props.checked) : undefined;
|
|
27
27
|
|
|
28
28
|
const input = <input
|
|
29
29
|
id={id}
|
|
@@ -36,7 +36,7 @@ export function Checkbox(props: {
|
|
|
36
36
|
}}
|
|
37
37
|
aria-readonly={string(!(props.checked instanceof Signal))}
|
|
38
38
|
aria-invalid={validator ? optionalString(validator.invalid) : undefined}
|
|
39
|
-
aria-errormessage={validator ? validator.
|
|
39
|
+
aria-errormessage={validator ? validator.messageIds : undefined}
|
|
40
40
|
autofocus={props.autofocus}
|
|
41
41
|
disabled={disabled}
|
|
42
42
|
/> as HTMLInputElement;
|
|
@@ -54,7 +54,7 @@ export function Checkbox(props: {
|
|
|
54
54
|
]}
|
|
55
55
|
style={props.style}
|
|
56
56
|
>
|
|
57
|
-
{input}
|
|
57
|
+
{theme?.checkbox_padding ? <div class={theme.checkbox_padding}>{input}</div> : input}
|
|
58
58
|
<Text class={theme?.checkbox_content}>
|
|
59
59
|
{props.children}
|
|
60
60
|
</Text>
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import { $, ClassValue, Expression, get, map,
|
|
2
|
-
import {
|
|
1
|
+
import { $, ClassValue, Component, Event, Expression, For, get, map, Signal, StyleValue, teardown, watch } from "rvx";
|
|
2
|
+
import { useMicrotask, useTimeout } from "rvx/async";
|
|
3
|
+
import { optionalString } from "rvx/convert";
|
|
3
4
|
import { THEME } from "../common/theme.js";
|
|
4
5
|
import { AriaLive, AriaRelevant } from "../common/types.js";
|
|
5
6
|
|
|
6
7
|
export function Collapse(props: {
|
|
7
8
|
visible?: Expression<boolean | undefined>;
|
|
9
|
+
fadein?: Expression<boolean | undefined>;
|
|
8
10
|
alert?: Event<[]>;
|
|
9
11
|
children?: unknown;
|
|
10
12
|
class?: ClassValue;
|
|
@@ -15,7 +17,6 @@ export function Collapse(props: {
|
|
|
15
17
|
"aria-atomic"?: Expression<boolean | undefined>;
|
|
16
18
|
}): unknown {
|
|
17
19
|
const theme = THEME.current;
|
|
18
|
-
const visible = map(props.visible, v => v ?? false);
|
|
19
20
|
const alert = $(false);
|
|
20
21
|
const size = $<number | undefined>(undefined);
|
|
21
22
|
|
|
@@ -37,7 +38,7 @@ export function Collapse(props: {
|
|
|
37
38
|
});
|
|
38
39
|
|
|
39
40
|
props.alert?.(() => {
|
|
40
|
-
if (get(visible)) {
|
|
41
|
+
if (get(props.visible) ?? false) {
|
|
41
42
|
alert.value = false;
|
|
42
43
|
// Force a reflow:
|
|
43
44
|
void root.offsetWidth;
|
|
@@ -45,8 +46,27 @@ export function Collapse(props: {
|
|
|
45
46
|
}
|
|
46
47
|
});
|
|
47
48
|
|
|
49
|
+
let visible = props.visible;
|
|
50
|
+
if (props.fadein !== undefined) {
|
|
51
|
+
const visibleSig = visible = $(false);
|
|
52
|
+
watch(props.visible, visible => {
|
|
53
|
+
const fadein = get(props.fadein);
|
|
54
|
+
if (fadein) {
|
|
55
|
+
visibleSig.value = false;
|
|
56
|
+
let handle = requestAnimationFrame(() => {
|
|
57
|
+
handle = requestAnimationFrame(() => {
|
|
58
|
+
visibleSig.value = visible ?? false;
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
teardown(() => cancelAnimationFrame(handle));
|
|
62
|
+
} else {
|
|
63
|
+
visibleSig.value = visible ?? false;
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
48
68
|
const root = <div
|
|
49
|
-
inert={map(visible, v => !v)}
|
|
69
|
+
inert={map(props.visible, v => !v)}
|
|
50
70
|
class={[
|
|
51
71
|
theme?.collapse,
|
|
52
72
|
() => size.value === undefined ? undefined : theme?.collapse_sized,
|
|
@@ -73,3 +93,105 @@ export function Collapse(props: {
|
|
|
73
93
|
</div> as HTMLDivElement;
|
|
74
94
|
return root;
|
|
75
95
|
}
|
|
96
|
+
|
|
97
|
+
export interface CollapseItem<T> {
|
|
98
|
+
value: T;
|
|
99
|
+
alert?: Event<[]>;
|
|
100
|
+
class?: ClassValue;
|
|
101
|
+
style?: StyleValue;
|
|
102
|
+
id?: Expression<string | undefined>;
|
|
103
|
+
"aria-live"?: Expression<AriaLive | undefined>;
|
|
104
|
+
"aria-relevant"?: Expression<AriaRelevant | undefined>;
|
|
105
|
+
"aria-atomic"?: Expression<boolean | undefined>;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function CollapseFor<T>(props: {
|
|
109
|
+
each: Expression<Iterable<CollapseItem<T>>>;
|
|
110
|
+
children: Component<T>;
|
|
111
|
+
}): unknown {
|
|
112
|
+
|
|
113
|
+
interface Entry {
|
|
114
|
+
/** item */
|
|
115
|
+
i: CollapseItem<T>;
|
|
116
|
+
/** visible */
|
|
117
|
+
v: Signal<boolean>;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const entries = $<Entry[]>([]);
|
|
121
|
+
const fadein = $(false);
|
|
122
|
+
useMicrotask(() => fadein.value = true);
|
|
123
|
+
|
|
124
|
+
watch(() => {
|
|
125
|
+
const iter = get(props.each);
|
|
126
|
+
return Array.isArray(iter) ? iter : Array.from(iter);
|
|
127
|
+
}, items => {
|
|
128
|
+
entries.update(entries => {
|
|
129
|
+
let itemIndex = 0;
|
|
130
|
+
let entryIndex = 0;
|
|
131
|
+
|
|
132
|
+
function hasRemainingItem(value: T): boolean {
|
|
133
|
+
for (let i = itemIndex + 1; i < items.length; i++) {
|
|
134
|
+
if (Object.is(items[i].value, value)) {
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function spliceRemainingEntry(value: T): Entry | undefined {
|
|
142
|
+
for (let i = entryIndex + 1; i < entries.length; i++) {
|
|
143
|
+
if (Object.is(entries[i].i.value, value)) {
|
|
144
|
+
return entries.splice(i, 1)[0];
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
items: while (itemIndex < items.length) {
|
|
150
|
+
const item = items[itemIndex];
|
|
151
|
+
let entry = entries[entryIndex] as Entry | undefined;
|
|
152
|
+
if (entry && Object.is(entry.i.value, item.value)) {
|
|
153
|
+
entry.v.value = true;
|
|
154
|
+
} else if (entry && !hasRemainingItem(entry.i.value)) {
|
|
155
|
+
entry.v.value = false;
|
|
156
|
+
entryIndex++;
|
|
157
|
+
continue items;
|
|
158
|
+
} else if (entry = spliceRemainingEntry(item.value)) {
|
|
159
|
+
entries.splice(entryIndex, 0, entry);
|
|
160
|
+
entry.v.value = true;
|
|
161
|
+
} else {
|
|
162
|
+
entries.splice(entryIndex, 0, { i: item, v: $(true) });
|
|
163
|
+
}
|
|
164
|
+
itemIndex++;
|
|
165
|
+
entryIndex++;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
while (entryIndex < entries.length) {
|
|
169
|
+
entries[entryIndex].v.value = false;
|
|
170
|
+
entryIndex++;
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
useTimeout(() => {
|
|
175
|
+
const filtered = entries.value.filter(e => e.v.value);
|
|
176
|
+
if (filtered.length < entries.value.length) {
|
|
177
|
+
entries.value = filtered;
|
|
178
|
+
}
|
|
179
|
+
}, 1000);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
return <For each={entries}>
|
|
183
|
+
{instance => <Collapse
|
|
184
|
+
visible={instance.v}
|
|
185
|
+
fadein={fadein}
|
|
186
|
+
alert={instance.i.alert}
|
|
187
|
+
class={instance.i.class}
|
|
188
|
+
style={instance.i.style}
|
|
189
|
+
id={instance.i.id}
|
|
190
|
+
aria-live={instance.i["aria-live"]}
|
|
191
|
+
aria-relevant={instance.i["aria-relevant"]}
|
|
192
|
+
aria-atomic={instance.i["aria-atomic"]}
|
|
193
|
+
>
|
|
194
|
+
{props.children(instance.i.value)}
|
|
195
|
+
</Collapse>}
|
|
196
|
+
</For>;
|
|
197
|
+
}
|
|
@@ -11,12 +11,14 @@ export function Column(props: {
|
|
|
11
11
|
style?: StyleValue;
|
|
12
12
|
id?: Expression<string | undefined>;
|
|
13
13
|
children?: unknown;
|
|
14
|
+
padded?: boolean;
|
|
14
15
|
}): unknown {
|
|
15
16
|
const theme = THEME.current;
|
|
16
17
|
return <div
|
|
17
18
|
class={[
|
|
18
19
|
theme?.column,
|
|
19
20
|
map(props.size, size => theme?.[`column_${size ?? "content"}`]),
|
|
21
|
+
map(props.padded, padded => padded ? theme?.column_padded : undefined),
|
|
20
22
|
props.class,
|
|
21
23
|
]}
|
|
22
24
|
style={props.style}
|
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
import { $, captureSelf, ClassValue, Context,
|
|
2
|
-
import { TASKS, Tasks } from "rvx/async";
|
|
3
|
-
import { Emitter, Event } from "rvx/event";
|
|
4
|
-
import { uniqueId } from "rvx/id";
|
|
1
|
+
import { $, captureSelf, ClassValue, Context, Emitter, Event, Expression, map, render, StyleValue, teardown, uniqueId } from "rvx";
|
|
2
|
+
import { TASKS, Tasks, useMicrotask } from "rvx/async";
|
|
5
3
|
import { FlexSpace, Heading, Row, Text, THEME } from "../index.js";
|
|
6
4
|
import { LAYER, Layer } from "./layer.js";
|
|
7
5
|
|
|
@@ -126,7 +124,7 @@ export function DialogBody(props: {
|
|
|
126
124
|
</div>
|
|
127
125
|
</div> as HTMLElement;
|
|
128
126
|
|
|
129
|
-
|
|
127
|
+
useMicrotask(() => {
|
|
130
128
|
if (theme?.dialog_fadein) {
|
|
131
129
|
body.offsetParent;
|
|
132
130
|
body.classList.add(theme.dialog_fadein);
|
|
@@ -2,7 +2,7 @@ import { ClassValue, Expression, get, map, Signal, StyleValue } from "rvx";
|
|
|
2
2
|
import { Button, ButtonVariant } from "./button.js";
|
|
3
3
|
import { Dropdown, DropdownItem } from "./dropdown.js";
|
|
4
4
|
import { PopoutAlignment, PopoutPlacement } from "./popout.js";
|
|
5
|
-
import {
|
|
5
|
+
import { closestValidator } from "./validation.js";
|
|
6
6
|
|
|
7
7
|
export interface DropdownValue<T> {
|
|
8
8
|
value: T;
|
|
@@ -46,7 +46,7 @@ export function DropdownInput<T>(props: {
|
|
|
46
46
|
role="combobox"
|
|
47
47
|
aria-label={props["aria-label"]}
|
|
48
48
|
aria-labelledby={props["aria-labelledby"]}
|
|
49
|
-
validator={props.value instanceof Signal ?
|
|
49
|
+
validator={props.value instanceof Signal ? closestValidator(props.value) : undefined}
|
|
50
50
|
>
|
|
51
51
|
{props.children ?? (() => {
|
|
52
52
|
const value = get(props.value);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { $, ClassValue, Expression, For, get, map, memo,
|
|
2
|
-
import {
|
|
1
|
+
import { $, ClassValue, Expression, For, get, map, memo, render, StyleValue, uniqueId, View, watch } from "rvx";
|
|
2
|
+
import { optionalString } from "rvx/convert";
|
|
3
3
|
import { Action, createPassiveActionEvent, handleActionEvent, keyFor, startDelayedHoverOnMouseenter } from "../common/events.js";
|
|
4
4
|
import { THEME } from "../common/theme.js";
|
|
5
5
|
import { LAYER } from "./layer.js";
|
package/src/components/label.tsx
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
import { ClassValue, Expression, StyleValue } from "rvx";
|
|
2
|
-
|
|
1
|
+
import { ClassValue, Expression, StyleValue, uniqueId } from "rvx";
|
|
3
2
|
import { THEME } from "../common/theme.js";
|
|
4
|
-
import { uniqueId } from "rvx/id";
|
|
5
3
|
|
|
6
4
|
export function Label(props: {
|
|
7
5
|
class?: ClassValue;
|
package/src/components/link.tsx
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { ClassValue, Expression, get, map,
|
|
1
|
+
import { ClassValue, Expression, get, map, StyleValue } from "rvx";
|
|
2
2
|
import { isPending } from "rvx/async";
|
|
3
|
+
import { optionalString } from "rvx/convert";
|
|
3
4
|
import { Action, handleActionEvent, keyFor } from "../common/events.js";
|
|
4
5
|
import { THEME } from "../common/theme.js";
|
|
5
6
|
import { separated } from "../common/types.js";
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { ClassValue, Expression, get, map, StyleValue } from "rvx";
|
|
2
|
+
import { isPending } from "rvx/async";
|
|
3
|
+
import { optionalString } from "rvx/convert";
|
|
4
|
+
import { Action, handleActionEvent, keyFor } from "../common/events.js";
|
|
5
|
+
import { THEME } from "../common/theme.js";
|
|
6
|
+
|
|
7
|
+
export function NavList(props: {
|
|
8
|
+
/**
|
|
9
|
+
* The element role or `false` to disable.
|
|
10
|
+
*
|
|
11
|
+
* @default "navigation"
|
|
12
|
+
*/
|
|
13
|
+
role?: Expression<string | false | undefined>;
|
|
14
|
+
|
|
15
|
+
class?: ClassValue;
|
|
16
|
+
style?: StyleValue;
|
|
17
|
+
id?: Expression<string | undefined>;
|
|
18
|
+
children?: unknown;
|
|
19
|
+
}): unknown {
|
|
20
|
+
const theme = THEME.current;
|
|
21
|
+
return <div
|
|
22
|
+
class={[
|
|
23
|
+
theme?.nav_list,
|
|
24
|
+
props.class,
|
|
25
|
+
]}
|
|
26
|
+
style={[
|
|
27
|
+
{ "--nav-list-depth": String(0) },
|
|
28
|
+
props.style,
|
|
29
|
+
]}
|
|
30
|
+
id={props.id}
|
|
31
|
+
role={map(props.role, role => role ?? "navigation")}
|
|
32
|
+
>
|
|
33
|
+
{props.children}
|
|
34
|
+
</div>;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function NavListButton(props: {
|
|
38
|
+
/**
|
|
39
|
+
* True if this is the currently selected item.
|
|
40
|
+
*/
|
|
41
|
+
current?: Expression<boolean | undefined>;
|
|
42
|
+
|
|
43
|
+
disabled?: Expression<boolean | undefined>;
|
|
44
|
+
class?: ClassValue;
|
|
45
|
+
style?: StyleValue;
|
|
46
|
+
id?: Expression<string | undefined>;
|
|
47
|
+
action?: Action;
|
|
48
|
+
children?: unknown;
|
|
49
|
+
}): unknown {
|
|
50
|
+
const theme = THEME.current;
|
|
51
|
+
const disabled = () => isPending() || get(props.disabled);
|
|
52
|
+
|
|
53
|
+
function action(event: Event) {
|
|
54
|
+
if (disabled() || !props.action) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
handleActionEvent(event, props.action);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return <button
|
|
61
|
+
type="button"
|
|
62
|
+
disabled={disabled}
|
|
63
|
+
class={[
|
|
64
|
+
theme?.nav_list_item,
|
|
65
|
+
map(props.current, current => current && theme?.nav_list_item_current),
|
|
66
|
+
props.class,
|
|
67
|
+
]}
|
|
68
|
+
style={props.style}
|
|
69
|
+
id={props.id}
|
|
70
|
+
on:click={action}
|
|
71
|
+
on:keydown={event => {
|
|
72
|
+
const key = keyFor(event);
|
|
73
|
+
if (key === "enter" || key === "space") {
|
|
74
|
+
action(event);
|
|
75
|
+
}
|
|
76
|
+
}}
|
|
77
|
+
aria-current={optionalString(props.current)}
|
|
78
|
+
>
|
|
79
|
+
{props.children}
|
|
80
|
+
</button>;
|
|
81
|
+
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { $, captureSelf, Context, Expression, get, render, teardown, TeardownHook, untrack, View, viewNodes } from "rvx";
|
|
2
|
-
import { Emitter, Event as RvxEvent } from "rvx/event";
|
|
1
|
+
import { $, captureSelf, Context, Emitter, Expression, get, render, Event as RvxEvent, teardown, TeardownHook, untrack, View, viewNodes } from "rvx";
|
|
3
2
|
import { PASSIVE_ACTION_EVENT } from "../common/events.js";
|
|
4
3
|
import { axisEquals, Direction, DOWN, flip, getBlockStart, getInlineStart, getSize, getWindowSize, getWindowSpaceAround, INSET, LEFT, RIGHT, ScriptDirection, UP, WritingMode } from "../common/writing-mode.js";
|
|
5
4
|
import { LAYER, Layer } from "./layer.js";
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { $, ClassValue, Expression, get, Inject, map, render, StyleValue, SVG, watch, XMLNS } from "rvx";
|
|
2
|
-
import { uniqueId } from "rvx/id";
|
|
1
|
+
import { $, ClassValue, Expression, get, Inject, map, render, StyleValue, SVG, uniqueId, watch, XMLNS } from "rvx";
|
|
3
2
|
import { Action } from "../common/events.js";
|
|
4
3
|
import { THEME } from "../common/theme.js";
|
|
5
4
|
import { DOWN, getSize, getXY, LEFT, RIGHT, UP } from "../common/writing-mode.js";
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { ClassValue, Expression, For, get, map,
|
|
1
|
+
import { ClassValue, Expression, For, get, map, Signal, StyleValue, uniqueId } from "rvx";
|
|
2
2
|
import { isPending } from "rvx/async";
|
|
3
|
+
import { optionalString, string } from "rvx/convert";
|
|
3
4
|
import { THEME } from "../common/theme.js";
|
|
4
5
|
import { Text } from "./text.js";
|
|
5
|
-
import {
|
|
6
|
-
import { uniqueId } from "rvx/id";
|
|
6
|
+
import { closestValidator } from "./validation.js";
|
|
7
7
|
|
|
8
8
|
export interface RadioOption<T> {
|
|
9
9
|
value: T;
|
|
@@ -32,7 +32,7 @@ export function RadioButtons<T>(props: {
|
|
|
32
32
|
? () => isPending() || get(props.disabled)
|
|
33
33
|
: true;
|
|
34
34
|
|
|
35
|
-
const validator = props.value instanceof Signal ?
|
|
35
|
+
const validator = props.value instanceof Signal ? closestValidator(props.value) : undefined;
|
|
36
36
|
|
|
37
37
|
return <div
|
|
38
38
|
role="radiogroup"
|
|
@@ -44,33 +44,34 @@ export function RadioButtons<T>(props: {
|
|
|
44
44
|
style={props.style}
|
|
45
45
|
aria-readonly={string(!(props.options instanceof Signal))}
|
|
46
46
|
aria-invalid={validator ? optionalString(validator.invalid) : undefined}
|
|
47
|
-
aria-errormessage={validator ? validator.
|
|
47
|
+
aria-errormessage={validator ? validator.messageIds : undefined}
|
|
48
48
|
aria-label={props["aria-label"]}
|
|
49
49
|
aria-labelledby={props["aria-labelledby"]}
|
|
50
50
|
>
|
|
51
51
|
<For each={props.options}>
|
|
52
52
|
{(option, index) => {
|
|
53
53
|
const id = uniqueId();
|
|
54
|
+
const input = <input
|
|
55
|
+
id={id}
|
|
56
|
+
type="radio"
|
|
57
|
+
class={theme?.radio_button_input}
|
|
58
|
+
name={group}
|
|
59
|
+
value={id}
|
|
60
|
+
disabled={disabled}
|
|
61
|
+
autofocus={() => get(props.autofocus) && index() === 0}
|
|
62
|
+
prop:checked={map(props.value, x => x === option.value)}
|
|
63
|
+
on:input={() => {
|
|
64
|
+
if (props.value instanceof Signal) {
|
|
65
|
+
props.value.value = option.value;
|
|
66
|
+
}
|
|
67
|
+
}}
|
|
68
|
+
/>;
|
|
54
69
|
|
|
55
70
|
return <label
|
|
56
71
|
for={id}
|
|
57
72
|
class={theme?.radio_button_label}
|
|
58
73
|
>
|
|
59
|
-
<input
|
|
60
|
-
id={id}
|
|
61
|
-
type="radio"
|
|
62
|
-
class={theme?.radio_button_input}
|
|
63
|
-
name={group}
|
|
64
|
-
value={id}
|
|
65
|
-
disabled={disabled}
|
|
66
|
-
autofocus={() => get(props.autofocus) && index() === 0}
|
|
67
|
-
prop:checked={map(props.value, x => x === option.value)}
|
|
68
|
-
on:input={() => {
|
|
69
|
-
if (props.value instanceof Signal) {
|
|
70
|
-
props.value.value = option.value;
|
|
71
|
-
}
|
|
72
|
-
}}
|
|
73
|
-
/>
|
|
74
|
+
{theme?.radio_button_padding ? <div class={theme.radio_button_padding}>{input}</div> : input}
|
|
74
75
|
<Text class={theme?.radio_button_content}>
|
|
75
76
|
{option.label}
|
|
76
77
|
</Text>
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { Expression, map, Show, Signal, uniqueId } from "rvx";
|
|
2
|
+
import { THEME } from "../common/theme.js";
|
|
3
|
+
|
|
4
|
+
export function Slider(props: {
|
|
5
|
+
id?: Expression<string | undefined>;
|
|
6
|
+
value?: Expression<number | undefined>;
|
|
7
|
+
min?: Expression<number | undefined>;
|
|
8
|
+
max?: Expression<number | undefined>;
|
|
9
|
+
step?: Expression<number | "any" | undefined>;
|
|
10
|
+
markers?: Expression<number[] | undefined>;
|
|
11
|
+
children?: unknown;
|
|
12
|
+
}) {
|
|
13
|
+
const theme = THEME.current;
|
|
14
|
+
const markerId = uniqueId();
|
|
15
|
+
|
|
16
|
+
const input = <input
|
|
17
|
+
id={props.id}
|
|
18
|
+
type="range"
|
|
19
|
+
min={map(props.min, min => min ?? 0)}
|
|
20
|
+
max={map(props.max, max => max ?? 100)}
|
|
21
|
+
step={map(props.step, step => step ?? 1)}
|
|
22
|
+
prop:value={map(props.value, value => value ?? "0")}
|
|
23
|
+
on:input={() => {
|
|
24
|
+
if (props.value instanceof Signal) {
|
|
25
|
+
props.value.value = Number(input.value);
|
|
26
|
+
}
|
|
27
|
+
}}
|
|
28
|
+
list={map(props.markers, markers => markers ? markerId : undefined)}
|
|
29
|
+
/> as HTMLInputElement;
|
|
30
|
+
|
|
31
|
+
return <div
|
|
32
|
+
class={theme?.slider_host}
|
|
33
|
+
>
|
|
34
|
+
{input}
|
|
35
|
+
|
|
36
|
+
<Show when={props.markers}>
|
|
37
|
+
{markers => <datalist id={markerId}>
|
|
38
|
+
{markers.map(marker => <option value={marker} />)}
|
|
39
|
+
</datalist>}
|
|
40
|
+
</Show>
|
|
41
|
+
|
|
42
|
+
{props.children}
|
|
43
|
+
</div>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function sliderMarkers(min: number, max: number, step: number): number[] {
|
|
47
|
+
if (min > max) {
|
|
48
|
+
[min, max] = [max, min];
|
|
49
|
+
}
|
|
50
|
+
const markers: number[] = [];
|
|
51
|
+
for (let i = min; i <= max; i += step) {
|
|
52
|
+
markers.push(i > max ? max : i);
|
|
53
|
+
}
|
|
54
|
+
return markers;
|
|
55
|
+
}
|