@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.
Files changed (99) hide show
  1. package/dist/common/theme.d.ts +10 -0
  2. package/dist/common/types.d.ts +0 -10
  3. package/dist/common/types.js +1 -9
  4. package/dist/common/types.js.map +1 -1
  5. package/dist/components/button.js +3 -2
  6. package/dist/components/button.js.map +1 -1
  7. package/dist/components/card.d.ts +1 -0
  8. package/dist/components/card.js +6 -3
  9. package/dist/components/card.js.map +1 -1
  10. package/dist/components/checkbox.js +6 -6
  11. package/dist/components/checkbox.js.map +1 -1
  12. package/dist/components/collapse.d.ts +16 -2
  13. package/dist/components/collapse.js +85 -4
  14. package/dist/components/collapse.js.map +1 -1
  15. package/dist/components/column.d.ts +1 -0
  16. package/dist/components/column.js +1 -0
  17. package/dist/components/column.js.map +1 -1
  18. package/dist/components/dialog.d.ts +1 -4
  19. package/dist/components/dialog.js +11 -12
  20. package/dist/components/dialog.js.map +1 -1
  21. package/dist/components/dropdown-input.js +2 -2
  22. package/dist/components/dropdown-input.js.map +1 -1
  23. package/dist/components/dropdown.js +2 -2
  24. package/dist/components/dropdown.js.map +1 -1
  25. package/dist/components/label.js +1 -1
  26. package/dist/components/label.js.map +1 -1
  27. package/dist/components/link.js +2 -2
  28. package/dist/components/link.js.map +1 -1
  29. package/dist/components/nav-list.js +2 -1
  30. package/dist/components/nav-list.js.map +1 -1
  31. package/dist/components/popout.d.ts +1 -2
  32. package/dist/components/popout.js +1 -2
  33. package/dist/components/popout.js.map +1 -1
  34. package/dist/components/popover.js +1 -2
  35. package/dist/components/popover.js.map +1 -1
  36. package/dist/components/radio-buttons.js +11 -10
  37. package/dist/components/radio-buttons.js.map +1 -1
  38. package/dist/components/slider.js +1 -2
  39. package/dist/components/slider.js.map +1 -1
  40. package/dist/components/tabs.d.ts +10 -0
  41. package/dist/components/tabs.js +31 -0
  42. package/dist/components/tabs.js.map +1 -0
  43. package/dist/components/text-input.js +5 -4
  44. package/dist/components/text-input.js.map +1 -1
  45. package/dist/components/validation-rules.d.ts +11 -0
  46. package/dist/components/validation-rules.js +37 -0
  47. package/dist/components/validation-rules.js.map +1 -0
  48. package/dist/components/validation.d.ts +53 -77
  49. package/dist/components/validation.js +117 -95
  50. package/dist/components/validation.js.map +1 -1
  51. package/dist/index.d.ts +2 -3
  52. package/dist/index.js +2 -3
  53. package/dist/index.js.map +1 -1
  54. package/dist/theme.module.css +77 -2
  55. package/dist/theme.module.css.map +1 -1
  56. package/package.json +3 -3
  57. package/src/common/theme.tsx +11 -0
  58. package/src/common/types.tsx +0 -18
  59. package/src/components/button.tsx +3 -2
  60. package/src/components/card.tsx +10 -5
  61. package/src/components/checkbox.tsx +6 -6
  62. package/src/components/collapse.tsx +127 -5
  63. package/src/components/column.tsx +2 -0
  64. package/src/components/dialog.tsx +10 -14
  65. package/src/components/dropdown-input.tsx +2 -2
  66. package/src/components/dropdown.tsx +2 -2
  67. package/src/components/label.tsx +1 -3
  68. package/src/components/link.tsx +2 -2
  69. package/src/components/nav-list.tsx +2 -1
  70. package/src/components/popout.tsx +1 -2
  71. package/src/components/popover.tsx +1 -2
  72. package/src/components/radio-buttons.tsx +21 -20
  73. package/src/components/slider.tsx +1 -2
  74. package/src/components/tabs.tsx +67 -0
  75. package/src/components/text-input.tsx +5 -4
  76. package/src/components/validation-rules.tsx +50 -0
  77. package/src/components/validation.tsx +175 -177
  78. package/src/index.tsx +2 -3
  79. package/src/theme/base.scss +7 -0
  80. package/src/theme/components/card.scss +4 -1
  81. package/src/theme/components/checkbox.scss +5 -0
  82. package/src/theme/components/column.scss +5 -0
  83. package/src/theme/components/dialog.scss +2 -2
  84. package/src/theme/components/radio-buttons.scss +5 -0
  85. package/src/theme/components/slider.scss +1 -0
  86. package/src/theme/components/tabs.scss +72 -0
  87. package/src/theme/theme.scss +1 -0
  88. package/dist/common/debounce.d.ts +0 -12
  89. package/dist/common/debounce.js +0 -23
  90. package/dist/common/debounce.js.map +0 -1
  91. package/dist/common/parsers.d.ts +0 -88
  92. package/dist/common/parsers.js +0 -62
  93. package/dist/common/parsers.js.map +0 -1
  94. package/dist/common/trim.d.ts +0 -12
  95. package/dist/common/trim.js +0 -16
  96. package/dist/common/trim.js.map +0 -1
  97. package/src/common/debounce.tsx +0 -36
  98. package/src/common/parsers.tsx +0 -167
  99. package/src/common/trim.tsx +0 -30
@@ -1,62 +1,90 @@
1
- import { $, ClassValue, Context, Expression, For, map, Signal, StyleValue, teardown, trigger, TriggerPipe, untrack } from "rvx";
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 { Collapse } from "./collapse.js";
4
+ import { CollapseFor, CollapseItem } from "./collapse.js";
7
5
  import { Text } from "./text.js";
8
6
 
9
- const VALIDATORS = new WeakMap<object, Validator>();
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
- signalTrigger?: ValidationSignalTrigger;
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
- #signalTrigger: ValidationSignalTrigger;
65
+ #trigger: ValidationTrigger;
32
66
  #rules = $<ValidationRuleEntry[]>([]);
33
- #invalid = $(false);
67
+ #notified = false;
34
68
 
35
69
  constructor() {
36
70
  const options = VALIDATION.current;
37
- this.#signalTrigger = options?.signalTrigger ?? "if-validated";
71
+ this.#trigger = options?.trigger ?? "if-invalid";
38
72
  }
39
73
 
40
- /**
41
- * Reactively get all validation rule entries.
42
- */
43
- rules = (): readonly ValidationRuleEntry[] => this.#rules.value;
44
-
45
- /**
46
- * An expression to get a space separated list of error message ids.
47
- */
48
- errorMessageIds = (): string => this.#rules.value.map(r => r.id).join(" ");
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
- #ruleUpdated = () => {
72
- const signalTrigger = this.#signalTrigger;
73
- if (signalTrigger === "never" || (signalTrigger === "if-invalid" && !this.#invalid.value)) {
74
- return;
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
- this.triggerValidation();
111
+ return valid;
77
112
  };
78
113
 
79
- /**
80
- * Add a rule to be validated first until the current lifecycle is disposed.
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
- * Add a rule to be validated last until the current lifecycle is disposed.
123
+ * Reactively check if this validator is in an invalid state.
124
+ *
125
+ * @returns `true` if invalid.
88
126
  */
89
- appendRule(rule: ValidationRule): void {
90
- this.#addRule(rule, (rules, entry) => rules.push(entry));
91
- }
127
+ invalid = memo<boolean>(this.#invalid);
92
128
 
93
129
  /**
94
- * Attach this validator to an object.
130
+ * Reactively get a space separated list of message ids.
95
131
  */
96
- attach(target: object): void {
97
- VALIDATORS.set(target, this);
98
- }
132
+ messageIds = memo<string>(() => {
133
+ return this.#rules.value.flatMap(r => r.messages).map(uniqueIdFor).join(" ");
134
+ });
99
135
 
100
- async #validate(sideEffect: boolean, signal?: AbortSignal): Promise<boolean> {
101
- const rules = untrack(() => this.#rules.value);
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
- * Validate using the currently attached rules.
141
+ * Append a rule to this validator until the current lifecycle is disposed.
129
142
  */
130
- async validate(signal?: AbortSignal): Promise<boolean> {
131
- return this.#queue.block(() => this.#validate(false, signal));
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
- * Trigger validation as a side effect.
151
+ * Prepend a rule to this validator until the current lifecycle is disposed.
136
152
  */
137
- triggerValidation(): void {
138
- this.#queue.sideEffect(signal => this.#validate(true, signal));
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
- * Reset this validator to it's initial state.
161
+ * Attach this validator to the specified target.
162
+ *
163
+ * This will overwrite existing validators on the target.
143
164
  */
144
- reset(): void {
145
- this.#invalid.value = false;
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
- * Get or attach a validator to an object.
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 attach(target: object): Validator {
156
- let validator = VALIDATORS.get(target);
172
+ static get(target: Signal<unknown>): Validator {
173
+ let validator = closestValidator(target);
157
174
  if (!validator) {
158
175
  validator = new Validator();
159
- VALIDATORS.set(target, validator);
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: object): Validator | undefined {
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
- * Prepend a validation rule to the validator attached to the specified signal.
193
+ * Find the closest validator attached to the specified target or any of it's {@link Signal.prototype.source sources}.
198
194
  *
199
- * This is a shorthand, meant to be used with `signal.pipe`.
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 rule<T>(target: Signal<T>, validate: ValidateFn<T>, message: unknown): Signal<T> {
207
- Validator.attach(target).prependRule({
208
- validate(signal) {
209
- return validate(target.value, signal);
210
- },
211
- message,
212
- });
213
- return target;
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 all specified targets in parallel.
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(...targets: object[]): Promise<boolean> {
216
+ export async function validate(targets: Signal<unknown>[], abortSignal?: AbortSignal): Promise<boolean> {
220
217
  const tasks: Promise<boolean>[] = [];
221
- for (const target of targets) {
222
- const validator = validatorFor(target);
218
+ for (let i = 0; i < targets.length; i++) {
219
+ const validator = validatorFor(targets[i]);
223
220
  if (validator === undefined) {
224
- throw new Error("target has no attached validator.");
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
- visible?: Expression<boolean | undefined>;
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 <Collapse
241
- visible={map(props.visible, v => v ?? true)}
242
- alert={props.alert}
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: object;
263
- }): unknown {
264
- const validator = Validator.attach(props.for);
265
- return <For each={validator.rules}>
266
- {rule => <ValidationMessage visible={rule.visible} alert={rule.alert.event} id={rule.id}>
267
- {rule.message}
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
- </For>;
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";
@@ -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
- @include common.padding(content-pad, var(--content-border));
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) {
@@ -18,6 +18,11 @@
18
18
  }
19
19
  }
20
20
 
21
+ .checkbox_padding {
22
+ padding: var(--input-padding);
23
+ margin: calc(var(--input-padding) * -1);
24
+ }
25
+
21
26
  .checkbox_input {
22
27
  margin: 0;
23
28
  outline: none;
@@ -1,3 +1,4 @@
1
+ @use "../common";
1
2
 
2
3
  .column {
3
4
  display: flex;
@@ -32,5 +33,9 @@
32
33
  & > * {
33
34
  --parent-row-gap: var(--#{$size}-row-gap);
34
35
  }
36
+
37
+ &.column_padded {
38
+ @include common.padding(#{$size}-pad, var(--content-border));
39
+ }
35
40
  }
36
41
  }
@@ -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
 
@@ -24,6 +24,11 @@
24
24
  }
25
25
  }
26
26
 
27
+ .radio_button_padding {
28
+ padding: var(--input-padding);
29
+ margin: calc(var(--input-padding) * -1);
30
+ }
31
+
27
32
  .radio_button_input {
28
33
  margin: 0;
29
34
  outline: none;
@@ -6,6 +6,7 @@
6
6
 
7
7
  & > input {
8
8
  outline: none;
9
+ cursor: grab;
9
10
 
10
11
  &:focus-visible {
11
12
  outline: var(--focus-outline);
@@ -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
+ }
@@ -19,6 +19,7 @@
19
19
  @forward "components/row";
20
20
  @forward "components/scroll-view";
21
21
  @forward "components/slider";
22
+ @forward "components/tabs";
22
23
  @forward "components/text-input";
23
24
  @forward "components/text";
24
25
  @forward "components/validation";
@@ -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>;
@@ -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"}