@streamscloud/kit 0.90.0 → 0.90.1

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.
@@ -11,5 +11,7 @@ export declare const getFormFieldLabel: () => FormFieldLabelContext | undefined;
11
11
  export declare const claimFormFieldLabel: (controlId: () => string) => void;
12
12
  /** The `aria-labelledby` of a control that is not a text input: the explicit one, else the FormField label's id — unless an explicit `aria-label` names it. */
13
13
  export declare const resolveAriaLabelledby: (formFieldLabel: FormFieldLabelContext | undefined, ariaLabel: string | undefined, ariaLabelledby: string | undefined) => string | undefined;
14
+ /** Whether `target` sits in a `<label>` whose `for` control lies inside `controlRoot` — a press there belongs to that control, not outside it. */
15
+ export declare const isInsideLabelOf: (target: Node, controlRoot: HTMLElement | undefined) => boolean;
14
16
  /** Below the calling component no control is named by the surrounding FormField label — for a field that renders other fields in its own panel. */
15
17
  export declare const stopFormFieldLabelHere: () => void;
@@ -14,6 +14,8 @@ export const claimFormFieldLabel = (controlId) => {
14
14
  };
15
15
  /** The `aria-labelledby` of a control that is not a text input: the explicit one, else the FormField label's id — unless an explicit `aria-label` names it. */
16
16
  export const resolveAriaLabelledby = (formFieldLabel, ariaLabel, ariaLabelledby) => ariaLabelledby ?? (ariaLabel ? undefined : formFieldLabel?.labelId);
17
+ /** Whether `target` sits in a `<label>` whose `for` control lies inside `controlRoot` — a press there belongs to that control, not outside it. */
18
+ export const isInsideLabelOf = (target, controlRoot) => target instanceof Element && !!controlRoot?.contains(target.closest('label')?.control ?? null);
17
19
  /** Below the calling component no control is named by the surrounding FormField label — for a field that renders other fields in its own panel. */
18
20
  export const stopFormFieldLabelHere = () => {
19
21
  setContext(KEY, undefined);
@@ -1 +1 @@
1
- export { claimFormFieldLabel, getFormFieldLabel, provideFormFieldLabel, resolveAriaLabelledby, stopFormFieldLabelHere, type FormFieldLabelContext } from './form-field-label-context.svelte';
1
+ export { claimFormFieldLabel, getFormFieldLabel, isInsideLabelOf, provideFormFieldLabel, resolveAriaLabelledby, stopFormFieldLabelHere, type FormFieldLabelContext } from './form-field-label-context.svelte';
@@ -1 +1 @@
1
- export { claimFormFieldLabel, getFormFieldLabel, provideFormFieldLabel, resolveAriaLabelledby, stopFormFieldLabelHere } from './form-field-label-context.svelte';
1
+ export { claimFormFieldLabel, getFormFieldLabel, isInsideLabelOf, provideFormFieldLabel, resolveAriaLabelledby, stopFormFieldLabelHere } from './form-field-label-context.svelte';
@@ -1,5 +1,5 @@
1
1
  <script lang="ts">import { ColorHelper } from '../../core/utils';
2
- import { getFormFieldLabel, resolveAriaLabelledby, stopFormFieldLabelHere } from '../_internal/form-field-label-context';
2
+ import { claimFormFieldLabel, getFormFieldLabel, resolveAriaLabelledby, stopFormFieldLabelHere } from '../_internal/form-field-label-context';
3
3
  import { Icon } from '../icon';
4
4
  import { Input } from '../input';
5
5
  import { Popover } from '../popover';
@@ -11,6 +11,8 @@ import { colord } from 'colord';
11
11
  import AwesomeColorPicker from 'svelte-awesome-color-picker';
12
12
  let { value, variant = 'fill', label, size = 'md', defaultColor, clearable = false, disabled = false, placeholder, on, children, previewIcon, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledby } = $props();
13
13
  const formFieldLabel = getFormFieldLabel();
14
+ const triggerId = $props.id();
15
+ claimFormFieldLabel(() => triggerId);
14
16
  stopFormFieldLabelHere();
15
17
  const loc = new ColorPickerLocalization();
16
18
  const displayColor = $derived(value || defaultColor || '');
@@ -54,6 +56,7 @@ const onColorPickerInput = ({ hex }) => {
54
56
  panel={false}
55
57
  fixedPosition
56
58
  disabled={disabled}
59
+ id={triggerId}
57
60
  aria-label={ariaLabel}
58
61
  aria-labelledby={resolveAriaLabelledby(formFieldLabel, ariaLabel, ariaLabelledby)}>
59
62
  {#snippet trigger()}
@@ -1,4 +1,4 @@
1
- <script lang="ts">import { getFormFieldLabel, resolveAriaLabelledby } from '../_internal/form-field-label-context';
1
+ <script lang="ts">import { claimFormFieldLabel, getFormFieldLabel, isInsideLabelOf, resolveAriaLabelledby } from '../_internal/form-field-label-context';
2
2
  import { InputClearButton } from '../_internal/input-clear-button';
3
3
  import { Icon, IconSlot } from '../icon';
4
4
  import DatePickerCalendar from './cmp.date-picker-calendar.svelte';
@@ -7,6 +7,9 @@ import { autoUpdate, computePosition, flip, offset, shift } from '@floating-ui/d
7
7
  import IconCalendar from '@fluentui/svg-icons/icons/calendar_16_regular.svg?raw';
8
8
  const { size = 'md', placeholder = '', disabled = false, readonly = false, inert = false, error = false, borderless = false, min, max, locale, name, id = null, autofocus = false, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledby, 'aria-describedby': ariaDescribedby, 'aria-required': ariaRequired, icon, ...mode } = $props();
9
9
  const formFieldLabel = getFormFieldLabel();
10
+ const fallbackId = $props.id();
11
+ const controlId = $derived(id ?? fallbackId);
12
+ claimFormFieldLabel(() => controlId);
10
13
  const localization = new DatePickerLocalization();
11
14
  /** Resolved locale: explicit prop or `'en-US'` default. */
12
15
  const resolvedLocale = $derived(locale ?? 'en-US');
@@ -95,7 +98,7 @@ $effect(() => {
95
98
  if (!target) {
96
99
  return;
97
100
  }
98
- if (triggerEl?.contains(target) || panelEl?.contains(target)) {
101
+ if (triggerEl?.contains(target) || panelEl?.contains(target) || isInsideLabelOf(target, triggerEl)) {
99
102
  return;
100
103
  }
101
104
  open = false;
@@ -129,7 +132,7 @@ $effect(() => {
129
132
  bind:this={triggerEl}
130
133
  type="button"
131
134
  class="date-picker__trigger"
132
- id={id}
135
+ id={controlId}
133
136
  disabled={disabled}
134
137
  inert={inert}
135
138
  autofocus={autofocus}
@@ -1,10 +1,11 @@
1
- <script lang="ts">import { Icon } from '../icon';
1
+ <script lang="ts">import { isInsideLabelOf } from '../_internal/form-field-label-context';
2
+ import { Icon } from '../icon';
2
3
  import { isIgnored } from './popover-ignore';
3
4
  import { validateTrigger } from './validate-trigger';
4
5
  import { autoUpdate, computePosition, flip, offset as offsetMiddleware, shift, size } from '@floating-ui/dom';
5
6
  import IconChevronDown from '@fluentui/svg-icons/icons/chevron_down_20_regular.svg?raw';
6
7
  import { tick } from 'svelte';
7
- let { position = 'bottom-start', disabled = false, fixedPosition = false, offset = 4, boundaryMargin = 8, backdrop = false, matchWidth = false, panel = true, keepOpen = false, fillContainer = false, tabbable = true, on, children, trigger, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledby } = $props();
8
+ let { position = 'bottom-start', disabled = false, fixedPosition = false, offset = 4, boundaryMargin = 8, backdrop = false, matchWidth = false, panel = true, keepOpen = false, fillContainer = false, tabbable = true, on, children, trigger, id, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledby } = $props();
8
9
  let opened = $state(false);
9
10
  let triggerEl = $state.raw(undefined);
10
11
  let contentEl = $state.raw(undefined);
@@ -106,7 +107,7 @@ $effect(() => {
106
107
  if (!target) {
107
108
  return;
108
109
  }
109
- if (triggerEl?.contains(target) || contentEl?.contains(target)) {
110
+ if (triggerEl?.contains(target) || contentEl?.contains(target) || isInsideLabelOf(target, triggerEl)) {
110
111
  return;
111
112
  }
112
113
  close();
@@ -179,6 +180,7 @@ const handleBackdropClick = (e) => {
179
180
  type="button"
180
181
  class="popover__trigger"
181
182
  class:popover__trigger--fill={fillContainer}
183
+ id={id}
182
184
  disabled={disabled}
183
185
  tabindex={tabbable ? undefined : -1}
184
186
  aria-label={ariaLabel}
@@ -29,6 +29,7 @@ type Props = {
29
29
  children: Snippet;
30
30
  /** Content of the trigger `<button>`, so non-interactive only — `Button type="presentational"` for a button look. @default chevron-down icon */
31
31
  trigger?: Snippet;
32
+ id?: string;
32
33
  'aria-label'?: string;
33
34
  'aria-labelledby'?: string;
34
35
  };
@@ -1,7 +1,7 @@
1
1
  <script lang="ts" module>export {};
2
2
  </script>
3
3
 
4
- <script lang="ts" generics="T">import { getFormFieldLabel, resolveAriaLabelledby } from '../_internal/form-field-label-context';
4
+ <script lang="ts" generics="T">import { claimFormFieldLabel, getFormFieldLabel, resolveAriaLabelledby } from '../_internal/form-field-label-context';
5
5
  import { InputClearButton } from '../_internal/input-clear-button';
6
6
  import { Spinner } from '../_internal/spinner';
7
7
  import { Icon, IconSlot } from '../icon';
@@ -12,6 +12,9 @@ import { SingleselectLocalization } from './singleselect-localization';
12
12
  import IconChevronDown from '@fluentui/svg-icons/icons/chevron_down_16_regular.svg?raw';
13
13
  const { loadOptions, debounceMs = 400, size = 'md', placeholder = '', disabled = false, readonly = false, inert = false, error = false, borderless = false, searchable = true, groupHeader = 'static', compare = defaultCompare, createOptions, icon, chevronIcon, optionSnippet, selectionSnippet, listbox, id, name, autocomplete = 'off', 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledby, 'aria-describedby': ariaDescribedby, 'aria-required': ariaRequired, ...mode } = $props();
14
14
  const formFieldLabel = getFormFieldLabel();
15
+ const fallbackId = $props.id();
16
+ const controlId = $derived(id ?? fallbackId);
17
+ claimFormFieldLabel(() => controlId);
15
18
  const localization = new SingleselectLocalization();
16
19
  const selectedOption = $derived(mode.value ?? null);
17
20
  const selectionLabel = $derived(selectedOption?.label ?? '');
@@ -107,7 +110,7 @@ const showClear = $derived(mode.clearable && hasValue && isInteractive && !core.
107
110
  aria-describedby={ariaDescribedby}
108
111
  aria-required={ariaRequired}
109
112
  aria-invalid={error ? 'true' : undefined}
110
- id={id}
113
+ id={controlId}
111
114
  name={name}
112
115
  autocomplete={autocomplete}
113
116
  placeholder={inputPlaceholder}
@@ -1,7 +1,7 @@
1
1
  <script lang="ts" module>export {};
2
2
  </script>
3
3
 
4
- <script lang="ts" generics="T">import { getFormFieldLabel, resolveAriaLabelledby, stopFormFieldLabelHere } from '../_internal/form-field-label-context';
4
+ <script lang="ts" generics="T">import { claimFormFieldLabel, getFormFieldLabel, resolveAriaLabelledby, stopFormFieldLabelHere } from '../_internal/form-field-label-context';
5
5
  import { Spinner } from '../_internal/spinner';
6
6
  import { Button } from '../button';
7
7
  import { Icon, IconSlot } from '../icon';
@@ -16,6 +16,9 @@ import IconChevronDown from '@fluentui/svg-icons/icons/chevron_down_16_regular.s
16
16
  import { dndzone } from 'svelte-dnd-action';
17
17
  const { loadOptions, debounceMs = 400, value, size = 'md', placeholder = '', disabled = false, readonly = false, inert = false, error = false, borderless = false, searchable = true, groupHeader = 'static', compare = defaultCompare, selectionMode = 'children-only', selectedDisplay = 'checkmark', createOptions, chipMode = 'inline', reorderable = false, maxVisible = Infinity, showFullItem = false, chipVariant, showChevron = true, hideSelected = false, icon, chevronIcon, optionSnippet, chipSnippet, selectionSnippet, parentSource, id, name, autocomplete = 'off', 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledby, 'aria-describedby': ariaDescribedby, 'aria-required': ariaRequired, on } = $props();
18
18
  const formFieldLabel = getFormFieldLabel();
19
+ const fallbackId = $props.id();
20
+ const controlId = $derived(id ?? fallbackId);
21
+ claimFormFieldLabel(() => controlId);
19
22
  stopFormFieldLabelHere();
20
23
  const localization = new MultiselectLocalization();
21
24
  const isPicked = (v) => value.some((existing) => compare(existing.value, v));
@@ -232,7 +235,7 @@ const handleDndFinalize = (e) => {
232
235
  aria-describedby={ariaDescribedby}
233
236
  aria-required={ariaRequired}
234
237
  aria-invalid={error ? 'true' : undefined}
235
- id={id}
238
+ id={controlId}
236
239
  name={name}
237
240
  autocomplete={autocomplete}
238
241
  placeholder={!hasValue && !core.isFiltering ? placeholder : ''}
@@ -1,4 +1,5 @@
1
- <script lang="ts" generics="T">import { Icon } from '../icon';
1
+ <script lang="ts" generics="T">import { isInsideLabelOf } from '../_internal/form-field-label-context';
2
+ import { Icon } from '../icon';
2
3
  import { SelectListboxLocalization } from './select-listbox-localization';
3
4
  import { autoUpdate, computePosition, flip, shift, size as floatingSize } from '@floating-ui/dom';
4
5
  import IconAdd from '@fluentui/svg-icons/icons/add_16_regular.svg?raw';
@@ -71,7 +72,7 @@ $effect(() => {
71
72
  if (!target) {
72
73
  return;
73
74
  }
74
- if (triggerEl?.contains(target) || panelEl?.contains(target)) {
75
+ if (triggerEl?.contains(target) || panelEl?.contains(target) || isInsideLabelOf(target, triggerEl)) {
75
76
  return;
76
77
  }
77
78
  on.dismiss();
@@ -195,6 +195,7 @@ const handleActionsClosed = () => {
195
195
  class:tree-node__leaf--draggable={node.drag}
196
196
  class:tree-node__leaf--expandable={children.length}
197
197
  class:tree-node__leaf--drag-over={dragOver}
198
+ class:tree-node__leaf--with-actions={actionsShown}
198
199
  draggable={!!node.drag && !node.renaming}
199
200
  ondragstart={handleDragStart}
200
201
  ondragend={handleDragEnd}
@@ -307,7 +308,6 @@ const handleActionsClosed = () => {
307
308
  --_tree-node--padding-inline: var(--sc-kit--tree-node--padding-inline, 0.5rem);
308
309
  --_tree-node--padding-block: var(--sc-kit--tree-node--padding-block, 0.375rem);
309
310
  --_tree-node--indent: var(--sc-kit--tree-node--indent, 1rem);
310
- --_tree-node--action-opacity: 0;
311
311
  position: relative;
312
312
  width: 100%;
313
313
  color: var(--sc-kit--color--text--primary);
@@ -325,17 +325,25 @@ const handleActionsClosed = () => {
325
325
  transition: background-color var(--sc-kit--duration--base) var(--sc-kit--ease--default), color var(--sc-kit--duration--base) var(--sc-kit--ease--default);
326
326
  }
327
327
  .tree-node__leaf:hover {
328
- --_tree-node--action-opacity: 1;
329
328
  color: var(--sc-kit--color--text--primary);
330
329
  background: var(--sc-kit--color--bg--hover);
331
330
  }
332
331
  .tree-node__leaf--active {
333
- --_tree-node--action-opacity: 1;
334
332
  color: var(--sc-kit--color--text--primary);
335
333
  background: var(--sc-kit--color--bg--active);
336
334
  }
337
- .tree-node__leaf:focus-within {
338
- --_tree-node--action-opacity: 1;
335
+ @media (any-hover: hover) {
336
+ .tree-node__leaf--with-actions:not(:hover, :has(:global(:focus-visible)), :has(.node-actions--opened)) .node-actions, .tree-node__leaf--with-actions:is(:hover, :has(:global(:focus-visible)), :has(.node-actions--opened)) .tree-node__count {
337
+ position: absolute;
338
+ width: 1px;
339
+ height: 1px;
340
+ margin: -1px;
341
+ padding: 0;
342
+ overflow: hidden;
343
+ clip-path: inset(50%);
344
+ white-space: nowrap;
345
+ border: 0;
346
+ }
339
347
  }
340
348
  .tree-node__leaf:has(.tree-node__anchor:focus-visible) {
341
349
  outline: 2px solid var(--sc-kit--color--border--focus);
@@ -433,11 +441,6 @@ const handleActionsClosed = () => {
433
441
  flex-shrink: 0;
434
442
  align-items: center;
435
443
  margin-left: var(--sc-kit--space--1);
436
- opacity: var(--_tree-node--action-opacity);
437
- transition: opacity var(--sc-kit--duration--base) var(--sc-kit--ease--default);
438
- }
439
- .node-actions--opened {
440
- opacity: 1;
441
444
  }
442
445
  .node-actions__trigger {
443
446
  --sc-kit--icon--size: 1rem;
@@ -452,9 +455,4 @@ const handleActionsClosed = () => {
452
455
  .node-actions__trigger:hover {
453
456
  background: var(--sc-kit--color--bg--active);
454
457
  color: var(--sc-kit--color--text--primary);
455
- }
456
- @media (any-hover: none) {
457
- .node-actions {
458
- opacity: 1;
459
- }
460
458
  }</style>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@streamscloud/kit",
3
- "version": "0.90.0",
3
+ "version": "0.90.1",
4
4
  "author": "StreamsCloud",
5
5
  "repository": {
6
6
  "type": "git",