@streamscloud/kit 0.9.7 → 0.9.9

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.
@@ -288,7 +288,7 @@ DatePicker — single-day calendar picker built on Floating UI for positioning.
288
288
  font-family: inherit;
289
289
  font-size: var(--_dp--font-size);
290
290
  line-height: var(--sc-kit--leading--tight);
291
- box-shadow: inset 0 -0.13em var(--_dp--underline-color);
291
+ box-shadow: inset 0 calc(-1 * max(2px, 0.125em)) var(--_dp--underline-color);
292
292
  cursor: pointer;
293
293
  transition: background-color var(--sc-kit--duration--base) var(--sc-kit--ease--default), border-color var(--sc-kit--duration--base) var(--sc-kit--ease--default), color var(--sc-kit--duration--base) var(--sc-kit--ease--default), box-shadow var(--sc-kit--duration--base) var(--sc-kit--ease--default);
294
294
  }
@@ -1,6 +1,6 @@
1
1
  <script lang="ts" generics="T extends Record<string, unknown>, K extends keyof T & string">import { Validatable } from '../validatable';
2
2
  import { default as FormField } from './cmp.form-field.svelte';
3
- let { handler, name, label, required = false, validateOn, debounceMs, disableTouchedTracking, on, children } = $props();
3
+ let { handler, name, label, required = false, validateOn, debounceMs, disableTouchedTracking, reserveErrorSpace, on, children } = $props();
4
4
  </script>
5
5
 
6
6
  <FormField label={label} required={required}>
@@ -10,6 +10,7 @@ let { handler, name, label, required = false, validateOn, debounceMs, disableTou
10
10
  validateOn={validateOn}
11
11
  debounceMs={debounceMs}
12
12
  disableTouchedTracking={disableTouchedTracking}
13
+ reserveErrorSpace={reserveErrorSpace}
13
14
  on={on}
14
15
  children={children} />
15
16
  </FormField>
@@ -17,6 +17,8 @@ declare function $$render<T extends Record<string, unknown>, K extends keyof T &
17
17
  debounceMs?: number;
18
18
  /** Show errors before the field has been touched. Forwarded to `<Validatable>`. @default false */
19
19
  disableTouchedTracking?: boolean;
20
+ /** Reserve space below the field so a toggling error doesn't shift layout. Forwarded to `<Validatable>`. @default true */
21
+ reserveErrorSpace?: boolean;
20
22
  /** Extra consumer callbacks. Forwarded to `<Validatable>`. */
21
23
  on?: {
22
24
  input?: (value: T[K]) => void;
@@ -216,7 +216,7 @@ or the clear button (see `HandleInput`'s availability pill).
216
216
  content: "";
217
217
  position: absolute;
218
218
  inset: auto 0 0 0;
219
- height: 0.13em;
219
+ height: max(2px, 0.125em);
220
220
  background: var(--_in--underline-color);
221
221
  pointer-events: none;
222
222
  z-index: 1;
@@ -240,7 +240,7 @@ or the clear button (see `HandleInput`'s availability pill).
240
240
  --sc-kit--input--font-size: var(--sc-kit--font-size--lg);
241
241
  --sc-kit--input--icon--size: var(--sc-kit--field--icon--size--lg);
242
242
  }
243
- .input:where(:hover) {
243
+ .input:where(:hover:not(:focus-within)) {
244
244
  --sc-kit--input--border-color: var(--_in--border-color-hover);
245
245
  }
246
246
  .input:where(:focus-within) {
@@ -245,7 +245,7 @@ requires a non-null value. CSS API mirrors `Input` for visual-language compatibi
245
245
  font-size: var(--_ni--font-size);
246
246
  line-height: var(--sc-kit--leading--tight);
247
247
  cursor: text;
248
- box-shadow: inset 0 -0.13em var(--_ni--underline-color);
248
+ box-shadow: inset 0 calc(-1 * max(2px, 0.125em)) var(--_ni--underline-color);
249
249
  transition: background-color var(--sc-kit--duration--base) var(--sc-kit--ease--default), border-color var(--sc-kit--duration--base) var(--sc-kit--ease--default), color var(--sc-kit--duration--base) var(--sc-kit--ease--default), box-shadow var(--sc-kit--duration--base) var(--sc-kit--ease--default);
250
250
  }
251
251
  .numeral-input--sm {
@@ -266,7 +266,7 @@ requires a non-null value. CSS API mirrors `Input` for visual-language compatibi
266
266
  --sc-kit--numeral-input--font-size: var(--sc-kit--font-size--lg);
267
267
  --sc-kit--numeral-input--icon--size: var(--sc-kit--field--icon--size--lg);
268
268
  }
269
- .numeral-input:where(:hover) {
269
+ .numeral-input:where(:hover:not(:focus-within)) {
270
270
  --sc-kit--numeral-input--border-color: var(--_ni--border-color-hover);
271
271
  }
272
272
  .numeral-input:where(:focus-within) {
@@ -212,7 +212,7 @@ body, or an external host element addressed by `options.fixedToolbarId`). Impera
212
212
  border-radius: var(--_rti--border-radius);
213
213
  color: var(--_rti--color);
214
214
  cursor: text;
215
- box-shadow: inset 0 -0.13em var(--_rti--underline-color);
215
+ box-shadow: inset 0 calc(-1 * max(2px, 0.125em)) var(--_rti--underline-color);
216
216
  transition: background-color var(--sc-kit--duration--base) var(--sc-kit--ease--default), border-color var(--sc-kit--duration--base) var(--sc-kit--ease--default), color var(--sc-kit--duration--base) var(--sc-kit--ease--default), box-shadow var(--sc-kit--duration--base) var(--sc-kit--ease--default);
217
217
  }
218
218
  .rich-text-input--sm {
@@ -224,7 +224,7 @@ body, or an external host element addressed by `options.fixedToolbarId`). Impera
224
224
  .rich-text-input--lg {
225
225
  --sc-kit--rich-text-input--root--font-size: 1rem;
226
226
  }
227
- .rich-text-input:where(:hover) {
227
+ .rich-text-input:where(:hover:not(:focus-within)) {
228
228
  --sc-kit--rich-text-input--border-color: var(--_rti--border-color-hover);
229
229
  }
230
230
  .rich-text-input:where(:focus-within) {
@@ -40,7 +40,8 @@
40
40
  font-size: var(--_sel--font-size);
41
41
  line-height: var(--sc-kit--leading--tight);
42
42
  cursor: pointer;
43
- box-shadow: inset 0 -0.13em var(--_sel--underline-color);
43
+ // prevents hairline anti-aliasing
44
+ box-shadow: inset 0 calc(-1 * max(2px, 0.125em)) var(--_sel--underline-color);
44
45
  @include transitions.transition(background-color border-color color box-shadow);
45
46
 
46
47
  &--sm {
@@ -62,13 +63,18 @@
62
63
  --sc-kit--select--trigger--icon--size: var(--sc-kit--field--icon--size--lg);
63
64
  }
64
65
 
65
- &:where(:hover) {
66
+ &:where(:hover:not(:focus-within)) {
66
67
  --sc-kit--select--trigger--border-color: var(--_sel--border-color-hover);
67
68
  }
68
69
  &:where(:focus-within),
69
70
  &--open {
70
71
  --sc-kit--select--trigger--underline-color: var(--sc-kit--color--accent);
71
72
  }
73
+ // Popover portals focus away — :focus-within won't suppress the hover border while open.
74
+ // --error must stay after this block: it wins border-color by source order even while open.
75
+ &--open {
76
+ --sc-kit--select--trigger--border-color: unset;
77
+ }
72
78
 
73
79
  &--error {
74
80
  --sc-kit--select--trigger--border-color: var(--_sel--border-color-error);
@@ -213,7 +213,7 @@ background--hover}`.
213
213
  font-size: var(--_sel--font-size);
214
214
  line-height: var(--sc-kit--leading--tight);
215
215
  cursor: pointer;
216
- box-shadow: inset 0 -0.13em var(--_sel--underline-color);
216
+ box-shadow: inset 0 calc(-1 * max(2px, 0.125em)) var(--_sel--underline-color);
217
217
  transition: background-color var(--sc-kit--duration--base) var(--sc-kit--ease--default), border-color var(--sc-kit--duration--base) var(--sc-kit--ease--default), color var(--sc-kit--duration--base) var(--sc-kit--ease--default), box-shadow var(--sc-kit--duration--base) var(--sc-kit--ease--default);
218
218
  }
219
219
  .singleselect--sm {
@@ -234,12 +234,15 @@ background--hover}`.
234
234
  --sc-kit--select--trigger--font-size: var(--sc-kit--font-size--lg);
235
235
  --sc-kit--select--trigger--icon--size: var(--sc-kit--field--icon--size--lg);
236
236
  }
237
- .singleselect:where(:hover) {
237
+ .singleselect:where(:hover:not(:focus-within)) {
238
238
  --sc-kit--select--trigger--border-color: var(--_sel--border-color-hover);
239
239
  }
240
240
  .singleselect:where(:focus-within), .singleselect--open {
241
241
  --sc-kit--select--trigger--underline-color: var(--sc-kit--color--accent);
242
242
  }
243
+ .singleselect--open {
244
+ --sc-kit--select--trigger--border-color: unset;
245
+ }
243
246
  .singleselect--error {
244
247
  --sc-kit--select--trigger--border-color: var(--_sel--border-color-error);
245
248
  --sc-kit--select--trigger--underline-color: var(--sc-kit--color--danger);
@@ -428,7 +428,7 @@ padding-inline, font-size, gap, max-width, remove-color, remove-color--hover}`.
428
428
  font-size: var(--_sel--font-size);
429
429
  line-height: var(--sc-kit--leading--tight);
430
430
  cursor: pointer;
431
- box-shadow: inset 0 -0.13em var(--_sel--underline-color);
431
+ box-shadow: inset 0 calc(-1 * max(2px, 0.125em)) var(--_sel--underline-color);
432
432
  transition: background-color var(--sc-kit--duration--base) var(--sc-kit--ease--default), border-color var(--sc-kit--duration--base) var(--sc-kit--ease--default), color var(--sc-kit--duration--base) var(--sc-kit--ease--default), box-shadow var(--sc-kit--duration--base) var(--sc-kit--ease--default);
433
433
  }
434
434
  .multiselect-trigger--sm {
@@ -449,12 +449,15 @@ padding-inline, font-size, gap, max-width, remove-color, remove-color--hover}`.
449
449
  --sc-kit--select--trigger--font-size: var(--sc-kit--font-size--lg);
450
450
  --sc-kit--select--trigger--icon--size: var(--sc-kit--field--icon--size--lg);
451
451
  }
452
- .multiselect-trigger:where(:hover) {
452
+ .multiselect-trigger:where(:hover:not(:focus-within)) {
453
453
  --sc-kit--select--trigger--border-color: var(--_sel--border-color-hover);
454
454
  }
455
455
  .multiselect-trigger:where(:focus-within), .multiselect-trigger--open {
456
456
  --sc-kit--select--trigger--underline-color: var(--sc-kit--color--accent);
457
457
  }
458
+ .multiselect-trigger--open {
459
+ --sc-kit--select--trigger--border-color: unset;
460
+ }
458
461
  .multiselect-trigger--error {
459
462
  --sc-kit--select--trigger--border-color: var(--_sel--border-color-error);
460
463
  --sc-kit--select--trigger--underline-color: var(--sc-kit--color--danger);
@@ -186,7 +186,7 @@ exposed via `bind:this`.
186
186
  font-size: var(--_ta--font-size);
187
187
  line-height: var(--sc-kit--leading--normal);
188
188
  cursor: text;
189
- box-shadow: inset 0 -0.13em var(--_ta--underline-color);
189
+ box-shadow: inset 0 calc(-1 * max(2px, 0.125em)) var(--_ta--underline-color);
190
190
  transition: background-color var(--sc-kit--duration--base) var(--sc-kit--ease--default), border-color var(--sc-kit--duration--base) var(--sc-kit--ease--default), color var(--sc-kit--duration--base) var(--sc-kit--ease--default), box-shadow var(--sc-kit--duration--base) var(--sc-kit--ease--default);
191
191
  }
192
192
  .textarea--sm {
@@ -204,7 +204,7 @@ exposed via `bind:this`.
204
204
  --sc-kit--textarea--padding-inline: var(--sc-kit--field--padding-inline--lg);
205
205
  --sc-kit--textarea--font-size: var(--sc-kit--font-size--lg);
206
206
  }
207
- .textarea:where(:hover) {
207
+ .textarea:where(:hover:not(:focus-within)) {
208
208
  --sc-kit--textarea--border-color: var(--_ta--border-color-hover);
209
209
  }
210
210
  .textarea:where(:focus-within) {
@@ -1,8 +1,8 @@
1
- <script lang="ts">let { error, children } = $props();
1
+ <script lang="ts">let { error, reserveErrorSpace = true, children } = $props();
2
2
  export {};
3
3
  </script>
4
4
 
5
- <div class="field-error" class:field-error--errored={!!error}>
5
+ <div class="field-error" class:field-error--errored={!!error} class:field-error--no-reserve={!reserveErrorSpace}>
6
6
  {@render children()}
7
7
  {#if error}
8
8
  <small class="field-error__message">{error}</small>
@@ -12,9 +12,10 @@ export {};
12
12
  <!--
13
13
  @component
14
14
  Internal helper — wraps a form field and renders its error message in an absolutely-positioned slot
15
- below, with reserved space so the layout doesn't shift when the error toggles. Used internally by
16
- `Validatable` / `ValidationError` (and any other field-shaped kit consumer that needs the same
17
- visual contract).
15
+ below, with reserved space so the layout doesn't shift when the error toggles. With
16
+ `reserveErrorSpace={false}` the reserve is dropped the message stays absolutely positioned and
17
+ overlays whatever sits below the field. Used internally by `Validatable` / `ValidationError` (and
18
+ any other field-shaped kit consumer that needs the same visual contract).
18
19
 
19
20
  Not exported from `index.ts` — for kit-internal use only.
20
21
  -->
@@ -24,6 +25,9 @@ Not exported from `index.ts` — for kit-internal use only.
24
25
  margin-bottom: 1.1em;
25
26
  display: block;
26
27
  }
28
+ .field-error--no-reserve {
29
+ margin-bottom: 0;
30
+ }
27
31
  .field-error__message {
28
32
  position: absolute;
29
33
  top: calc(100% + 0.1em);
@@ -2,13 +2,16 @@ import type { Snippet } from 'svelte';
2
2
  type Props = {
3
3
  /** Error message to display below the field. Falsy hides the message and the `--errored` flag. */
4
4
  error?: string | null | undefined;
5
+ /** Reserve space below the field for the error message. Off drops the reserve — the message overlays content below. @default true */
6
+ reserveErrorSpace?: boolean;
5
7
  children: Snippet;
6
8
  };
7
9
  /**
8
10
  * Internal helper — wraps a form field and renders its error message in an absolutely-positioned slot
9
- * below, with reserved space so the layout doesn't shift when the error toggles. Used internally by
10
- * `Validatable` / `ValidationError` (and any other field-shaped kit consumer that needs the same
11
- * visual contract).
11
+ * below, with reserved space so the layout doesn't shift when the error toggles. With
12
+ * `reserveErrorSpace={false}` the reserve is dropped the message stays absolutely positioned and
13
+ * overlays whatever sits below the field. Used internally by `Validatable` / `ValidationError` (and
14
+ * any other field-shaped kit consumer that needs the same visual contract).
12
15
  *
13
16
  * Not exported from `index.ts` — for kit-internal use only.
14
17
  */
@@ -3,7 +3,7 @@
3
3
 
4
4
  <script lang="ts" generics="T extends Record<string, unknown>, K extends keyof T & string">import { Utils } from '../../core/utils';
5
5
  import { default as FieldError } from './cmp.field-error.svelte';
6
- let { handler, name, validateOn = ['input', 'change', 'blur'], debounceMs = 0, disableTouchedTracking = false, on, children } = $props();
6
+ let { handler, name, validateOn = ['input', 'change', 'blur'], debounceMs = 0, disableTouchedTracking = false, reserveErrorSpace = true, on, children } = $props();
7
7
  // True between a value change and the next completed validation. Hides the stale error in the
8
8
  // UI without mutating handler.errors — the model keeps the last known result, the view just
9
9
  // doesn't trust it until the validator has caught up.
@@ -59,7 +59,7 @@ const field = $derived({
59
59
  });
60
60
  </script>
61
61
 
62
- <FieldError error={showErrors ? handler.errors[name] : null}>
62
+ <FieldError error={showErrors ? handler.errors[name] : null} reserveErrorSpace={reserveErrorSpace}>
63
63
  {@render children(field)}
64
64
  </FieldError>
65
65
 
@@ -68,7 +68,7 @@ const field = $derived({
68
68
  Binds a form field to any kit input via a render-prop snippet. The base inputs stay unchanged;
69
69
  `Validatable` is the only place that knows about `FormValidationHandler`. Wraps children in an
70
70
  internal `FieldError` frame that reserves space for the error message so toggling doesn't shift
71
- layout.
71
+ layout; `reserveErrorSpace={false}` drops the reserve — the message overlays content below.
72
72
 
73
73
  ### Usage
74
74
  ```svelte
@@ -30,6 +30,12 @@ declare function $$render<T extends Record<string, unknown>, K extends keyof T &
30
30
  debounceMs?: number;
31
31
  /** Show errors even when the field has not been touched. @default false */
32
32
  disableTouchedTracking?: boolean;
33
+ /**
34
+ * Reserve space below the field for the error message. Turn off when a sibling must align to
35
+ * the field's real bottom edge — the message then overlays whatever sits below the field.
36
+ * @default true
37
+ */
38
+ reserveErrorSpace?: boolean;
33
39
  /**
34
40
  * Extra consumer callbacks. Run AFTER the validation wiring; they cannot override it.
35
41
  * The `children` snippet always receives fully-wired `field.on.*`.
@@ -65,7 +71,7 @@ interface $$IsomorphicComponent {
65
71
  * Binds a form field to any kit input via a render-prop snippet. The base inputs stay unchanged;
66
72
  * `Validatable` is the only place that knows about `FormValidationHandler`. Wraps children in an
67
73
  * internal `FieldError` frame that reserves space for the error message so toggling doesn't shift
68
- * layout.
74
+ * layout; `reserveErrorSpace={false}` drops the reserve — the message overlays content below.
69
75
  *
70
76
  * ### Usage
71
77
  * ```svelte
@@ -1,9 +1,9 @@
1
1
  <script lang="ts" generics="T extends Record<string, unknown>">import { default as FieldError } from './cmp.field-error.svelte';
2
- let { name, validationResult, children } = $props();
2
+ let { name, validationResult, reserveErrorSpace = true, children } = $props();
3
3
  const errorText = $derived(validationResult?.errors[name] ?? null);
4
4
  </script>
5
5
 
6
- <FieldError error={errorText}>
6
+ <FieldError error={errorText} reserveErrorSpace={reserveErrorSpace}>
7
7
  {@render children()}
8
8
  </FieldError>
9
9
 
@@ -11,5 +11,6 @@ const errorText = $derived(validationResult?.errors[name] ?? null);
11
11
  @component
12
12
  Wraps a form field and displays validation errors from a `FormValidateResult` object. Stateless
13
13
  sibling of `Validatable` for cases where the consumer already has the validation result in hand
14
- (e.g. server-validated forms) and does not need a `FormValidationHandler`.
14
+ (e.g. server-validated forms) and does not need a `FormValidationHandler`. Pass
15
+ `reserveErrorSpace={false}` to overlay the message instead of reserving layout space.
15
16
  -->
@@ -6,6 +6,12 @@ declare function $$render<T extends Record<string, unknown>>(): {
6
6
  name: keyof T & string;
7
7
  /** Validation result object containing error messages */
8
8
  validationResult?: FormValidateResult<T>;
9
+ /**
10
+ * Reserve space below the field for the error message. Turn off when a sibling must align to
11
+ * the field's real bottom edge — the message then overlays whatever sits below the field.
12
+ * @default true
13
+ */
14
+ reserveErrorSpace?: boolean;
9
15
  children: Snippet;
10
16
  };
11
17
  exports: {};
@@ -30,7 +36,8 @@ interface $$IsomorphicComponent {
30
36
  /**
31
37
  * Wraps a form field and displays validation errors from a `FormValidateResult` object. Stateless
32
38
  * sibling of `Validatable` for cases where the consumer already has the validation result in hand
33
- * (e.g. server-validated forms) and does not need a `FormValidationHandler`.
39
+ * (e.g. server-validated forms) and does not need a `FormValidationHandler`. Pass
40
+ * `reserveErrorSpace={false}` to overlay the message instead of reserving layout space.
34
41
  */
35
42
  declare const Cmp: $$IsomorphicComponent;
36
43
  type Cmp<T extends Record<string, unknown>> = InstanceType<typeof Cmp<T>>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@streamscloud/kit",
3
- "version": "0.9.7",
3
+ "version": "0.9.9",
4
4
  "author": "StreamsCloud",
5
5
  "repository": {
6
6
  "type": "git",