@rvx/ui 0.1.13 → 0.1.15
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/theme.d.ts +10 -0
- package/dist/common/types.d.ts +0 -10
- package/dist/common/types.js +1 -9
- package/dist/common/types.js.map +1 -1
- 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 -4
- package/dist/components/dialog.js +11 -12
- 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 -2
- package/dist/components/link.js.map +1 -1
- package/dist/components/nav-list.js +2 -1
- package/dist/components/nav-list.js.map +1 -1
- 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.js +1 -2
- package/dist/components/slider.js.map +1 -1
- 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 +2 -3
- package/dist/index.js +2 -3
- package/dist/index.js.map +1 -1
- package/dist/theme.module.css +77 -2
- package/dist/theme.module.css.map +1 -1
- package/package.json +3 -3
- package/src/common/theme.tsx +11 -0
- package/src/common/types.tsx +0 -18
- 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 +10 -14
- 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 -2
- package/src/components/nav-list.tsx +2 -1
- 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 +1 -2
- 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 +2 -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/dialog.scss +2 -2
- package/src/theme/components/radio-buttons.scss +5 -0
- package/src/theme/components/slider.scss +1 -0
- package/src/theme/components/tabs.scss +72 -0
- package/src/theme/theme.scss +1 -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
|
@@ -1,62 +1,90 @@
|
|
|
1
|
-
import { $,
|
|
1
|
+
import { $, Component, Context, Emitter, memo, Signal, teardown, trigger, TriggerPipe, uniqueIdFor, untrack } from "rvx";
|
|
2
2
|
import { Queue } from "rvx/async";
|
|
3
|
-
import { Emitter, Event } from "rvx/event";
|
|
4
|
-
import { uniqueId } from "rvx/id";
|
|
5
3
|
import { THEME } from "../common/theme.js";
|
|
6
|
-
import {
|
|
4
|
+
import { CollapseFor, CollapseItem } from "./collapse.js";
|
|
7
5
|
import { Text } from "./text.js";
|
|
8
6
|
|
|
9
|
-
const VALIDATORS = new WeakMap<
|
|
7
|
+
const VALIDATORS = new WeakMap<Signal<unknown>, Validator>();
|
|
8
|
+
const ALERTS = new WeakMap<Component, Emitter<[]>>();
|
|
10
9
|
|
|
11
|
-
/**
|
|
12
|
-
* Context for validation options used by new validators.
|
|
13
|
-
*/
|
|
14
10
|
export const VALIDATION = new Context<ValidationOptions | undefined>();
|
|
15
11
|
|
|
16
|
-
|
|
17
|
-
* Defines when accessed signals trigger automatic validation.
|
|
18
|
-
*
|
|
19
|
-
* + **if-validated** - **Default.** Validate if validation did run before.
|
|
20
|
-
* + **if-invalid** - Validate if currently invalid.
|
|
21
|
-
* + **never** - Never validate automatically.
|
|
22
|
-
*/
|
|
23
|
-
export type ValidationSignalTrigger = "if-validated" | "if-invalid" | "never";
|
|
12
|
+
export type ValidationTrigger = "if-validated" | "if-invalid" | "never";
|
|
24
13
|
|
|
25
14
|
export interface ValidationOptions {
|
|
26
|
-
|
|
15
|
+
trigger?: ValidationTrigger;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ValidationRule {
|
|
19
|
+
/**
|
|
20
|
+
* Validate this rule.
|
|
21
|
+
*
|
|
22
|
+
* Immediately accessed signals may be tracked by the caller to trigger automatic re-validation when updated.
|
|
23
|
+
*
|
|
24
|
+
* @param abortSignal An optional abort signal to abort validaiton if supported.
|
|
25
|
+
* @returns An array of validation message components. If empty, this rule is considered valid.
|
|
26
|
+
*/
|
|
27
|
+
(abortSignal?: AbortSignal): Component[] | undefined | Promise<Component[] | undefined>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export class ValidationRuleEntry {
|
|
31
|
+
#rule: ValidationRule;
|
|
32
|
+
#pipe: TriggerPipe;
|
|
33
|
+
#result = $<Component[]>([]);
|
|
34
|
+
|
|
35
|
+
constructor(rule: ValidationRule, notify: () => void) {
|
|
36
|
+
this.#rule = rule;
|
|
37
|
+
this.#pipe = trigger(notify);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
get messages(): Component[] {
|
|
41
|
+
return this.#result.value;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async validate(abortSignal: AbortSignal | undefined, sideEffect: boolean): Promise<boolean> {
|
|
45
|
+
const messages = (await this.#pipe(() => this.#rule(abortSignal))) ?? [];
|
|
46
|
+
if (!sideEffect) {
|
|
47
|
+
for (const message of messages) {
|
|
48
|
+
if (this.#result.value.includes(message)) {
|
|
49
|
+
ALERTS.get(message)?.emit();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
this.#result.value = messages;
|
|
54
|
+
return messages.length === 0;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
reset(): void {
|
|
58
|
+
this.#pipe(() => { });
|
|
59
|
+
this.#result.value = [];
|
|
60
|
+
}
|
|
27
61
|
}
|
|
28
62
|
|
|
29
63
|
export class Validator {
|
|
30
64
|
#queue = new Queue();
|
|
31
|
-
#
|
|
65
|
+
#trigger: ValidationTrigger;
|
|
32
66
|
#rules = $<ValidationRuleEntry[]>([]);
|
|
33
|
-
#
|
|
67
|
+
#notified = false;
|
|
34
68
|
|
|
35
69
|
constructor() {
|
|
36
70
|
const options = VALIDATION.current;
|
|
37
|
-
this.#
|
|
71
|
+
this.#trigger = options?.trigger ?? "if-invalid";
|
|
38
72
|
}
|
|
39
73
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* An expression to check if this validator is in an invalid state.
|
|
52
|
-
*/
|
|
53
|
-
invalid = (): boolean => this.#invalid.value;
|
|
54
|
-
|
|
55
|
-
#addRule(rule: ValidationRule, add: (rules: ValidationRuleEntry[], entry: ValidationRuleEntry) => void): void {
|
|
56
|
-
const entry = new ValidationRuleEntry(rule, trigger(this.#ruleUpdated));
|
|
57
|
-
this.#rules.update(rules => {
|
|
58
|
-
add(rules, entry);
|
|
74
|
+
#notify = (): void => {
|
|
75
|
+
if (this.#notified || this.#trigger === "never" || (this.#trigger === "if-invalid" && !this.#invalid())) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
this.#notified = true;
|
|
79
|
+
queueMicrotask(() => {
|
|
80
|
+
if (this.#notified) {
|
|
81
|
+
this.#queue.sideEffect(abortSignal => this.#validate(abortSignal, true));
|
|
82
|
+
}
|
|
59
83
|
});
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
#createRuleEntry(rule: ValidationRule): ValidationRuleEntry {
|
|
87
|
+
const entry = new ValidationRuleEntry(rule, this.#notify);
|
|
60
88
|
teardown(() => {
|
|
61
89
|
this.#rules.update(rules => {
|
|
62
90
|
const index = rules.indexOf(entry);
|
|
@@ -66,97 +94,86 @@ export class Validator {
|
|
|
66
94
|
rules.splice(index, 1);
|
|
67
95
|
});
|
|
68
96
|
});
|
|
97
|
+
return entry;
|
|
69
98
|
}
|
|
70
99
|
|
|
71
|
-
#
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
100
|
+
#validate = async (abortSignal: AbortSignal | undefined, sideEffect: boolean): Promise<boolean> => {
|
|
101
|
+
this.#notified = false;
|
|
102
|
+
let valid = true;
|
|
103
|
+
// TODO: Support custom validation behavior.
|
|
104
|
+
for (const rule of untrack(() => this.#rules.value)) {
|
|
105
|
+
if (valid) {
|
|
106
|
+
valid = await rule.validate(abortSignal, sideEffect);
|
|
107
|
+
} else {
|
|
108
|
+
rule.reset();
|
|
109
|
+
}
|
|
75
110
|
}
|
|
76
|
-
|
|
111
|
+
return valid;
|
|
77
112
|
};
|
|
78
113
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
*/
|
|
82
|
-
prependRule(rule: ValidationRule): void {
|
|
83
|
-
this.#addRule(rule, (rules, entry) => rules.unshift(entry));
|
|
114
|
+
validate(abortSignal?: AbortSignal): Promise<boolean> {
|
|
115
|
+
return this.#queue.block(() => this.#validate(abortSignal, false));
|
|
84
116
|
}
|
|
85
117
|
|
|
118
|
+
#invalid = () => {
|
|
119
|
+
return this.#rules.value.some(r => r.messages.length > 0);
|
|
120
|
+
};
|
|
121
|
+
|
|
86
122
|
/**
|
|
87
|
-
*
|
|
123
|
+
* Reactively check if this validator is in an invalid state.
|
|
124
|
+
*
|
|
125
|
+
* @returns `true` if invalid.
|
|
88
126
|
*/
|
|
89
|
-
|
|
90
|
-
this.#addRule(rule, (rules, entry) => rules.push(entry));
|
|
91
|
-
}
|
|
127
|
+
invalid = memo<boolean>(this.#invalid);
|
|
92
128
|
|
|
93
129
|
/**
|
|
94
|
-
*
|
|
130
|
+
* Reactively get a space separated list of message ids.
|
|
95
131
|
*/
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}
|
|
132
|
+
messageIds = memo<string>(() => {
|
|
133
|
+
return this.#rules.value.flatMap(r => r.messages).map(uniqueIdFor).join(" ");
|
|
134
|
+
});
|
|
99
135
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
for (let i = 0; i < rules.length; i++) {
|
|
103
|
-
if (signal?.aborted) {
|
|
104
|
-
return false;
|
|
105
|
-
}
|
|
106
|
-
const { validate, visible, alert, updated } = rules[i];
|
|
107
|
-
const valid = await updated(() => validate(signal));
|
|
108
|
-
if (valid) {
|
|
109
|
-
visible.value = false;
|
|
110
|
-
} else {
|
|
111
|
-
const wasVisible = visible.value;
|
|
112
|
-
visible.value = true;
|
|
113
|
-
for (let r = i + 1; r < rules.length; r++) {
|
|
114
|
-
rules[r].visible.value = false;
|
|
115
|
-
}
|
|
116
|
-
this.#invalid.value = true;
|
|
117
|
-
if (wasVisible && !sideEffect) {
|
|
118
|
-
alert.emit();
|
|
119
|
-
}
|
|
120
|
-
return false;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
this.#invalid.value = false;
|
|
124
|
-
return true;
|
|
136
|
+
get rules(): ValidationRuleEntry[] {
|
|
137
|
+
return this.#rules.value;
|
|
125
138
|
}
|
|
126
139
|
|
|
127
140
|
/**
|
|
128
|
-
*
|
|
141
|
+
* Append a rule to this validator until the current lifecycle is disposed.
|
|
129
142
|
*/
|
|
130
|
-
|
|
131
|
-
|
|
143
|
+
appendRule(rule: ValidationRule): void {
|
|
144
|
+
const entry = this.#createRuleEntry(rule);
|
|
145
|
+
this.#rules.update(rules => {
|
|
146
|
+
rules.push(entry);
|
|
147
|
+
});
|
|
132
148
|
}
|
|
133
149
|
|
|
134
150
|
/**
|
|
135
|
-
*
|
|
151
|
+
* Prepend a rule to this validator until the current lifecycle is disposed.
|
|
136
152
|
*/
|
|
137
|
-
|
|
138
|
-
|
|
153
|
+
prependRule(rule: ValidationRule): void {
|
|
154
|
+
const entry = this.#createRuleEntry(rule);
|
|
155
|
+
this.#rules.update(rules => {
|
|
156
|
+
rules.unshift(entry);
|
|
157
|
+
});
|
|
139
158
|
}
|
|
140
159
|
|
|
141
160
|
/**
|
|
142
|
-
*
|
|
161
|
+
* Attach this validator to the specified target.
|
|
162
|
+
*
|
|
163
|
+
* This will overwrite existing validators on the target.
|
|
143
164
|
*/
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
const rules = untrack(() => this.#rules.value);
|
|
147
|
-
for (let i = 0; i < rules.length; i++) {
|
|
148
|
-
rules[i].visible.value = false;
|
|
149
|
-
}
|
|
165
|
+
attach(target: Signal<unknown>): void {
|
|
166
|
+
VALIDATORS.set(target, this);
|
|
150
167
|
}
|
|
151
168
|
|
|
152
169
|
/**
|
|
153
|
-
*
|
|
170
|
+
* Find the {@link closestValidator closest validator} or {@link Validator.prototype.attach attach} a new one to the target's {@link Signal.prototype.root root}.
|
|
154
171
|
*/
|
|
155
|
-
static
|
|
156
|
-
let validator =
|
|
172
|
+
static get(target: Signal<unknown>): Validator {
|
|
173
|
+
let validator = closestValidator(target);
|
|
157
174
|
if (!validator) {
|
|
158
175
|
validator = new Validator();
|
|
159
|
-
|
|
176
|
+
validator.attach(target.root);
|
|
160
177
|
}
|
|
161
178
|
return validator;
|
|
162
179
|
}
|
|
@@ -164,107 +181,88 @@ export class Validator {
|
|
|
164
181
|
|
|
165
182
|
/**
|
|
166
183
|
* Get the validator attached to the specified target.
|
|
184
|
+
*
|
|
185
|
+
* @param target The target.
|
|
186
|
+
* @returns The validator or `undefined` if there is none.
|
|
167
187
|
*/
|
|
168
|
-
export function validatorFor(target:
|
|
188
|
+
export function validatorFor(target: Signal<unknown>): Validator | undefined {
|
|
169
189
|
return VALIDATORS.get(target);
|
|
170
190
|
}
|
|
171
191
|
|
|
172
|
-
export interface ValidationRule {
|
|
173
|
-
validate(signal?: AbortSignal): boolean | Promise<boolean>;
|
|
174
|
-
message: unknown;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
export class ValidationRuleEntry {
|
|
178
|
-
readonly id = uniqueId();
|
|
179
|
-
readonly visible = $(false);
|
|
180
|
-
readonly alert = new Emitter<[]>();
|
|
181
|
-
readonly message: unknown;
|
|
182
|
-
readonly validate: ValidationRule["validate"];
|
|
183
|
-
readonly updated: TriggerPipe;
|
|
184
|
-
|
|
185
|
-
constructor(rule: ValidationRule, updated: TriggerPipe) {
|
|
186
|
-
this.message = rule.message;
|
|
187
|
-
this.validate = rule.validate.bind(rule);
|
|
188
|
-
this.updated = updated;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
export interface ValidateFn<T> {
|
|
193
|
-
(value: T, signal?: AbortSignal): boolean | Promise<boolean>;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
192
|
/**
|
|
197
|
-
*
|
|
193
|
+
* Find the closest validator attached to the specified target or any of it's {@link Signal.prototype.source sources}.
|
|
198
194
|
*
|
|
199
|
-
*
|
|
200
|
-
*
|
|
201
|
-
* @param target The signal to attach the validator to.
|
|
202
|
-
* @param validate A function to validate the current value. Immediately accessed signals may trigger re-validation when updated.
|
|
203
|
-
* @param message The validation message to show when invalid.
|
|
204
|
-
* @returns The target itself.
|
|
195
|
+
* @param target The target.
|
|
196
|
+
* @returns The validator or `undefined` if there is none.
|
|
205
197
|
*/
|
|
206
|
-
export function
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
198
|
+
export function closestValidator(target: Signal<unknown>): Validator | undefined;
|
|
199
|
+
export function closestValidator(target: Signal<unknown> | undefined): Validator | undefined {
|
|
200
|
+
while (target) {
|
|
201
|
+
const validator = VALIDATORS.get(target);
|
|
202
|
+
if (validator) {
|
|
203
|
+
return validator;
|
|
204
|
+
}
|
|
205
|
+
target = target.source;
|
|
206
|
+
}
|
|
214
207
|
}
|
|
215
208
|
|
|
216
209
|
/**
|
|
217
|
-
* Validate
|
|
210
|
+
* Validate the specified targets in parallel.
|
|
211
|
+
*
|
|
212
|
+
* @param targets The targets that have attached validators.
|
|
213
|
+
* @param abortSignal An optional abort signal to abort validation if supported.
|
|
214
|
+
* @returns `true` if valid.
|
|
218
215
|
*/
|
|
219
|
-
export async function validate(
|
|
216
|
+
export async function validate(targets: Signal<unknown>[], abortSignal?: AbortSignal): Promise<boolean> {
|
|
220
217
|
const tasks: Promise<boolean>[] = [];
|
|
221
|
-
for (
|
|
222
|
-
const validator = validatorFor(
|
|
218
|
+
for (let i = 0; i < targets.length; i++) {
|
|
219
|
+
const validator = validatorFor(targets[i]);
|
|
223
220
|
if (validator === undefined) {
|
|
224
|
-
throw new Error(
|
|
221
|
+
throw new Error(`targets[${i}] has no attached validator.`);
|
|
225
222
|
}
|
|
226
|
-
tasks.push(validator.validate());
|
|
223
|
+
tasks.push(validator.validate(abortSignal));
|
|
227
224
|
}
|
|
228
225
|
return !(await Promise.all(tasks)).includes(false);
|
|
229
226
|
}
|
|
230
227
|
|
|
231
228
|
export function ValidationMessage(props: {
|
|
232
|
-
|
|
233
|
-
alert?: Event<[]>;
|
|
234
|
-
class?: ClassValue;
|
|
235
|
-
style?: StyleValue;
|
|
236
|
-
id?: Expression<string | undefined>;
|
|
237
|
-
children?: unknown;
|
|
229
|
+
children: unknown;
|
|
238
230
|
}): unknown {
|
|
239
231
|
const theme = THEME.current;
|
|
240
|
-
return <
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
class={[
|
|
244
|
-
props.class,
|
|
245
|
-
theme?.validation_message_container,
|
|
246
|
-
]}
|
|
247
|
-
style={props.style}
|
|
248
|
-
id={props.id}
|
|
249
|
-
>
|
|
250
|
-
<div class={theme?.validation_message}>
|
|
251
|
-
<Text>
|
|
252
|
-
{props.children}
|
|
253
|
-
</Text>
|
|
254
|
-
</div>
|
|
255
|
-
</Collapse>;
|
|
232
|
+
return <Text class={theme?.validation_message}>
|
|
233
|
+
{props.children}
|
|
234
|
+
</Text>;
|
|
256
235
|
}
|
|
257
236
|
|
|
258
|
-
/**
|
|
259
|
-
* Render validation messages for the validator attached to a specific target.
|
|
260
|
-
*/
|
|
261
237
|
export function ValidationMessages(props: {
|
|
262
|
-
for:
|
|
263
|
-
})
|
|
264
|
-
const validator =
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
238
|
+
for: Signal<unknown> | Validator;
|
|
239
|
+
}) {
|
|
240
|
+
const validator = props.for instanceof Validator
|
|
241
|
+
? props.for
|
|
242
|
+
: closestValidator(props.for);
|
|
243
|
+
|
|
244
|
+
if (!validator) {
|
|
245
|
+
throw new Error("props.for is or has no attached validator.");
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return <CollapseFor each={function * (): IterableIterator<CollapseItem<Component>> {
|
|
249
|
+
for (const rule of validator.rules) {
|
|
250
|
+
for (const message of rule.messages) {
|
|
251
|
+
let alert = ALERTS.get(message);
|
|
252
|
+
if (!alert) {
|
|
253
|
+
alert = new Emitter<[]>();
|
|
254
|
+
ALERTS.set(message, alert);
|
|
255
|
+
}
|
|
256
|
+
yield {
|
|
257
|
+
value: message,
|
|
258
|
+
id: uniqueIdFor(message),
|
|
259
|
+
alert: alert.event,
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}}>
|
|
264
|
+
{message => <ValidationMessage>
|
|
265
|
+
{message()}
|
|
268
266
|
</ValidationMessage>}
|
|
269
|
-
</
|
|
267
|
+
</CollapseFor>;
|
|
270
268
|
}
|
package/src/index.tsx
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
export * from "./common/coupling.js";
|
|
2
|
-
export * from "./common/debounce.js";
|
|
3
2
|
export * from "./common/events.js";
|
|
4
|
-
export * from "./common/parsers.js";
|
|
5
3
|
export * from "./common/theme.js";
|
|
6
|
-
export * from "./common/trim.js";
|
|
7
4
|
export * from "./common/types.js";
|
|
8
5
|
export * from "./common/writing-mode.js";
|
|
9
6
|
export * from "./components/button.js";
|
|
@@ -28,7 +25,9 @@ export * from "./components/radio-buttons.js";
|
|
|
28
25
|
export * from "./components/row.js";
|
|
29
26
|
export * from "./components/scroll-view.js";
|
|
30
27
|
export * from "./components/slider.js";
|
|
28
|
+
export * from "./components/tabs.js";
|
|
31
29
|
export * from "./components/text-input.js";
|
|
32
30
|
export * from "./components/text.js";
|
|
31
|
+
export * from "./components/validation-rules.js";
|
|
33
32
|
export * from "./components/validation.js";
|
|
34
33
|
export * from "./components/value.js";
|
package/src/theme/base.scss
CHANGED
|
@@ -30,6 +30,10 @@ $root-size: 14px;
|
|
|
30
30
|
--control-disabled: opacity(.5);
|
|
31
31
|
@include common.define-quad(control-pad, px(8), px(10));
|
|
32
32
|
|
|
33
|
+
--input-padding: #{px(4)};
|
|
34
|
+
|
|
35
|
+
--separator: #{px(1)};
|
|
36
|
+
|
|
33
37
|
--focus-outline-offset: #{px(2)};
|
|
34
38
|
|
|
35
39
|
--overflow-safe-area: #{px(4)};
|
|
@@ -53,6 +57,9 @@ $root-size: 14px;
|
|
|
53
57
|
focus-outline: (
|
|
54
58
|
dark: var(--control-border) dashed var(--accent),
|
|
55
59
|
),
|
|
60
|
+
separator-color: (
|
|
61
|
+
dark: rgb(64, 64, 64),
|
|
62
|
+
),
|
|
56
63
|
));
|
|
57
64
|
|
|
58
65
|
body {
|
|
@@ -21,7 +21,10 @@
|
|
|
21
21
|
.card {
|
|
22
22
|
box-shadow: var(--content-shadow);
|
|
23
23
|
border-radius: var(--content-radius);
|
|
24
|
-
|
|
24
|
+
|
|
25
|
+
&:not(.card_raw) {
|
|
26
|
+
@include common.padding(content-pad, var(--content-border));
|
|
27
|
+
}
|
|
25
28
|
}
|
|
26
29
|
|
|
27
30
|
@each $name in (default, info, success, warning, danger) {
|
|
@@ -22,8 +22,8 @@
|
|
|
22
22
|
background-color: var(--dialog-container-bg);
|
|
23
23
|
|
|
24
24
|
display: grid;
|
|
25
|
-
grid-template-columns: 1fr auto 1fr;
|
|
26
|
-
grid-template-rows: 3fr auto 4fr;
|
|
25
|
+
grid-template-columns: 1fr minmax(auto, var(--dialog-inline-size)) 1fr;
|
|
26
|
+
grid-template-rows: 3fr minmax(auto, var(--dialog-block-size)) 4fr;
|
|
27
27
|
|
|
28
28
|
overflow: auto;
|
|
29
29
|
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
@use "../common";
|
|
2
|
+
|
|
3
|
+
@include common.theme((
|
|
4
|
+
tab-handle-marker: (
|
|
5
|
+
dark: rgb(150, 150, 150),
|
|
6
|
+
),
|
|
7
|
+
tab-handle-bg: (
|
|
8
|
+
dark: rgb(64, 64, 64),
|
|
9
|
+
),
|
|
10
|
+
tab-handle-bg-active: (
|
|
11
|
+
dark: rgb(72, 72, 72),
|
|
12
|
+
),
|
|
13
|
+
tab-handle-fg: (
|
|
14
|
+
dark: rgb(172, 172, 172),
|
|
15
|
+
),
|
|
16
|
+
tab-handle-fg-active: (
|
|
17
|
+
dark: var(--fg),
|
|
18
|
+
),
|
|
19
|
+
tab-handle-fg-current: (
|
|
20
|
+
dark: var(--fg),
|
|
21
|
+
),
|
|
22
|
+
));
|
|
23
|
+
|
|
24
|
+
.tab_list {
|
|
25
|
+
display: flex;
|
|
26
|
+
flex-direction: row;
|
|
27
|
+
border-bottom: var(--separator) solid var(--separator-color);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.tab_handle {
|
|
31
|
+
font-family: inherit;
|
|
32
|
+
font-size: inherit;
|
|
33
|
+
font-weight: 600;
|
|
34
|
+
line-height: 1;
|
|
35
|
+
|
|
36
|
+
cursor: pointer;
|
|
37
|
+
outline: none;
|
|
38
|
+
|
|
39
|
+
padding-block:
|
|
40
|
+
var(--control-pad-block-start)
|
|
41
|
+
calc(var(--control-pad-block-end) - var(--control-border));
|
|
42
|
+
padding-inline:
|
|
43
|
+
var(--control-pad-inline-start)
|
|
44
|
+
var(--control-pad-inline-end);
|
|
45
|
+
|
|
46
|
+
background-color: transparent;
|
|
47
|
+
color: var(--tab-handle-fg);
|
|
48
|
+
border: none;
|
|
49
|
+
border-block-end: transparent solid var(--control-border);
|
|
50
|
+
|
|
51
|
+
border-radius: var(--control-radius) var(--control-radius) 0 0;
|
|
52
|
+
transition: var(--color-transition) background-color,
|
|
53
|
+
var(--color-transition) border-color;
|
|
54
|
+
|
|
55
|
+
&:hover,
|
|
56
|
+
&:focus-visible {
|
|
57
|
+
color: var(--tab-handle-fg-active);
|
|
58
|
+
background-color: var(--tab-handle-bg-active);
|
|
59
|
+
}
|
|
60
|
+
&:active {
|
|
61
|
+
background-color: var(--tab-handle-bg);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.tab_handle_current {
|
|
66
|
+
border-block-end-color: var(--tab-handle-marker);
|
|
67
|
+
color: var(--tab-handle-fg-current);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.tab_panel_padded {
|
|
71
|
+
@include common.padding(content-pad, var(--content-border));
|
|
72
|
+
}
|
package/src/theme/theme.scss
CHANGED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { Signal } from "rvx";
|
|
2
|
-
/**
|
|
3
|
-
* Create a signal that debounces input changes.
|
|
4
|
-
*
|
|
5
|
-
* This supports validation.
|
|
6
|
-
*
|
|
7
|
-
* @example
|
|
8
|
-
* ```tsx
|
|
9
|
-
* <TextInput value={someSignal.pipe(debounce, 500)} />
|
|
10
|
-
* ```
|
|
11
|
-
*/
|
|
12
|
-
export declare function debounce<T>(source: Signal<T>, delay: number): Signal<T>;
|
package/dist/common/debounce.js
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { $, teardown, watchUpdates } from "rvx";
|
|
2
|
-
import { validatorFor } from "../components/validation.js";
|
|
3
|
-
export function debounce(source, delay) {
|
|
4
|
-
const input = $(source.value);
|
|
5
|
-
let timer;
|
|
6
|
-
watchUpdates(input, value => {
|
|
7
|
-
clearTimeout(timer);
|
|
8
|
-
if (source.value !== value) {
|
|
9
|
-
timer = setTimeout(() => {
|
|
10
|
-
source.value = value;
|
|
11
|
-
}, delay);
|
|
12
|
-
teardown(() => {
|
|
13
|
-
clearTimeout(timer);
|
|
14
|
-
});
|
|
15
|
-
}
|
|
16
|
-
});
|
|
17
|
-
watchUpdates(source, value => {
|
|
18
|
-
input.value = value;
|
|
19
|
-
});
|
|
20
|
-
validatorFor(source)?.attach(input);
|
|
21
|
-
return input;
|
|
22
|
-
}
|
|
23
|
-
//# sourceMappingURL=debounce.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"debounce.js","sourceRoot":"","sources":["../../src/common/debounce.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAU,QAAQ,EAAE,YAAY,EAAE,MAAM,KAAK,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAY3D,MAAM,UAAU,QAAQ,CAAI,MAAiB,EAAE,KAAa;IAC3D,MAAM,KAAK,GAAG,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC9B,IAAI,KAAyB,CAAC;IAE9B,YAAY,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE;QAC3B,YAAY,CAAC,KAAK,CAAC,CAAC;QACpB,IAAI,MAAM,CAAC,KAAK,KAAK,KAAK,EAAE,CAAC;YAC5B,KAAK,GAAI,UAAuC,CAAC,GAAG,EAAE;gBACrD,MAAM,CAAC,KAAK,GAAG,KAAK,CAAC;YACtB,CAAC,EAAE,KAAK,CAAC,CAAC;YACV,QAAQ,CAAC,GAAG,EAAE;gBACb,YAAY,CAAC,KAAK,CAAC,CAAC;YACrB,CAAC,CAAC,CAAC;QACJ,CAAC;IACF,CAAC,CAAC,CAAC;IAEH,YAAY,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE;QAC5B,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC;IACrB,CAAC,CAAC,CAAC;IAEH,YAAY,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;IACpC,OAAO,KAAK,CAAC;AACd,CAAC"}
|