bits-ui 1.2.0 → 1.3.0

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.
@@ -41,13 +41,6 @@ export declare class DateRangeFieldRootState {
41
41
  startValueComplete: boolean;
42
42
  endValueComplete: boolean;
43
43
  rangeComplete: boolean;
44
- mergedValues: {
45
- start: undefined;
46
- end: undefined;
47
- } | {
48
- start: DateValue;
49
- end: DateValue;
50
- };
51
44
  constructor(opts: DateRangeFieldRootStateProps);
52
45
  validationStatus: false | {
53
46
  readonly reason: "custom";
@@ -1,6 +1,5 @@
1
- import { untrack } from "svelte";
2
1
  import { box, onDestroyEffect, useRefById } from "svelte-toolbelt";
3
- import { Context } from "runed";
2
+ import { Context, watch } from "runed";
4
3
  import { DateFieldInputState, useDateFieldRoot } from "../date-field/date-field.svelte.js";
5
4
  import { useId } from "../../internal/use-id.js";
6
5
  import { getDataDisabled, getDataInvalid } from "../../internal/attrs.js";
@@ -22,21 +21,6 @@ export class DateRangeFieldRootState {
22
21
  startValueComplete = $derived.by(() => this.opts.startValue.current !== undefined);
23
22
  endValueComplete = $derived.by(() => this.opts.endValue.current !== undefined);
24
23
  rangeComplete = $derived(this.startValueComplete && this.endValueComplete);
25
- mergedValues = $derived.by(() => {
26
- if (this.opts.startValue.current === undefined ||
27
- this.opts.endValue.current === undefined) {
28
- return {
29
- start: undefined,
30
- end: undefined,
31
- };
32
- }
33
- else {
34
- return {
35
- start: this.opts.startValue.current,
36
- end: this.opts.endValue.current,
37
- };
38
- }
39
- });
40
24
  constructor(opts) {
41
25
  this.opts = opts;
42
26
  this.formatter = createFormatter(this.opts.locale.current);
@@ -54,48 +38,65 @@ export class DateRangeFieldRootState {
54
38
  return;
55
39
  this.formatter.setLocale(this.opts.locale.current);
56
40
  });
57
- $effect(() => {
58
- const startValue = this.opts.value.current.start;
59
- untrack(() => {
60
- if (startValue)
61
- this.opts.placeholder.current = startValue;
62
- });
63
- });
64
- $effect(() => {
65
- const endValue = this.opts.value.current.end;
66
- untrack(() => {
67
- if (endValue)
68
- this.opts.placeholder.current = endValue;
69
- });
41
+ /**
42
+ * Synchronize the start and end values with the `value` in case
43
+ * it is updated externally.
44
+ */
45
+ watch(() => this.opts.value.current, (value) => {
46
+ if (value.start && value.end) {
47
+ this.opts.startValue.current = value.start;
48
+ this.opts.endValue.current = value.end;
49
+ }
50
+ else if (value.start) {
51
+ this.opts.startValue.current = value.start;
52
+ this.opts.endValue.current = undefined;
53
+ }
54
+ else if (value.start === undefined && value.end === undefined) {
55
+ this.opts.startValue.current = undefined;
56
+ this.opts.endValue.current = undefined;
57
+ }
70
58
  });
71
59
  /**
72
- * Sync values set programatically with the `startValue` and `endValue`
60
+ * Synchronize the placeholder value with the current start value
73
61
  */
74
- $effect(() => {
75
- const value = this.opts.value.current;
76
- untrack(() => {
77
- if (value.start !== undefined && value.start !== this.opts.startValue.current) {
78
- this.#setStartValue(value.start);
79
- }
80
- if (value.end !== undefined && value.end !== this.opts.endValue.current) {
81
- this.#setEndValue(value.end);
82
- }
83
- });
62
+ watch(() => this.opts.value.current, (value) => {
63
+ const startValue = value.start;
64
+ if (startValue && this.opts.placeholder.current !== startValue) {
65
+ this.opts.placeholder.current = startValue;
66
+ }
84
67
  });
85
- // TODO: Handle description element
86
- $effect(() => {
87
- const placeholder = untrack(() => this.opts.placeholder.current);
88
- const startValue = untrack(() => this.opts.startValue.current);
89
- if (this.startValueComplete && placeholder !== startValue) {
90
- untrack(() => {
91
- if (startValue) {
92
- this.opts.placeholder.current = startValue;
68
+ watch([() => this.opts.startValue.current, () => this.opts.endValue.current], ([startValue, endValue]) => {
69
+ if (this.opts.value.current &&
70
+ this.opts.value.current.start === startValue &&
71
+ this.opts.value.current.end === endValue) {
72
+ return;
73
+ }
74
+ if (startValue && endValue) {
75
+ this.#updateValue((prev) => {
76
+ if (prev.start === startValue && prev.end === endValue) {
77
+ return prev;
78
+ }
79
+ if (isBefore(endValue, startValue)) {
80
+ const start = startValue;
81
+ const end = endValue;
82
+ this.#setStartValue(end);
83
+ this.#setEndValue(start);
84
+ return { start: endValue, end: startValue };
85
+ }
86
+ else {
87
+ return {
88
+ start: startValue,
89
+ end: endValue,
90
+ };
93
91
  }
94
92
  });
95
93
  }
96
- });
97
- $effect(() => {
98
- this.opts.value.current = this.mergedValues;
94
+ else if (this.opts.value.current &&
95
+ this.opts.value.current.start &&
96
+ this.opts.value.current.end) {
97
+ this.opts.value.current.start = undefined;
98
+ this.opts.value.current.end = undefined;
99
+ }
99
100
  });
100
101
  }
101
102
  validationStatus = $derived.by(() => {
@@ -134,6 +135,11 @@ export class DateRangeFieldRootState {
134
135
  return false;
135
136
  return true;
136
137
  });
138
+ #updateValue(cb) {
139
+ const value = this.opts.value.current;
140
+ const newValue = cb(value);
141
+ this.opts.value.current = newValue;
142
+ }
137
143
  #setStartValue(value) {
138
144
  this.opts.startValue.current = value;
139
145
  }
@@ -1,5 +1,5 @@
1
1
  import type { ReadableBoxedValues, WritableBoxedValues } from "../../internal/box.svelte.js";
2
- import type { BitsKeyboardEvent, BitsMouseEvent, BitsPointerEvent, WithRefProps } from "../../internal/types.js";
2
+ import type { BitsKeyboardEvent, BitsMouseEvent, WithRefProps } from "../../internal/types.js";
3
3
  type DialogVariant = "alert-dialog" | "dialog";
4
4
  type DialogRootStateProps = WritableBoxedValues<{
5
5
  open: boolean;
@@ -42,7 +42,6 @@ declare class DialogTriggerState {
42
42
  readonly root: DialogRootState;
43
43
  constructor(opts: DialogTriggerStateProps, root: DialogRootState);
44
44
  onclick(e: BitsMouseEvent): void;
45
- onpointerdown(e: BitsPointerEvent): void;
46
45
  onkeydown(e: BitsKeyboardEvent): void;
47
46
  props: {
48
47
  readonly "data-state": "open" | "closed";
@@ -50,7 +49,6 @@ declare class DialogTriggerState {
50
49
  readonly "aria-haspopup": "dialog";
51
50
  readonly "aria-expanded": "true" | "false";
52
51
  readonly "aria-controls": string | undefined;
53
- readonly onpointerdown: (e: BitsPointerEvent) => void;
54
52
  readonly onkeydown: (e: BitsKeyboardEvent) => void;
55
53
  readonly onclick: (e: BitsMouseEvent) => void;
56
54
  readonly disabled: true | undefined;
@@ -51,9 +51,6 @@ class DialogTriggerState {
51
51
  constructor(opts, root) {
52
52
  this.opts = opts;
53
53
  this.root = root;
54
- this.onclick = this.onclick.bind(this);
55
- this.onpointerdown = this.onpointerdown.bind(this);
56
- this.onkeydown = this.onkeydown.bind(this);
57
54
  useRefById({
58
55
  ...opts,
59
56
  onRefChange: (node) => {
@@ -62,7 +59,6 @@ class DialogTriggerState {
62
59
  },
63
60
  });
64
61
  this.onclick = this.onclick.bind(this);
65
- this.onpointerdown = this.onpointerdown.bind(this);
66
62
  this.onkeydown = this.onkeydown.bind(this);
67
63
  }
68
64
  onclick(e) {
@@ -72,15 +68,6 @@ class DialogTriggerState {
72
68
  return;
73
69
  this.root.handleOpen();
74
70
  }
75
- onpointerdown(e) {
76
- if (this.opts.disabled.current)
77
- return;
78
- if (e.button > 0)
79
- return;
80
- // by default, it will attempt to focus this trigger on pointerdown
81
- // since this also opens the dialog we want to prevent that behavior
82
- e.preventDefault();
83
- }
84
71
  onkeydown(e) {
85
72
  if (this.opts.disabled.current)
86
73
  return;
@@ -95,7 +82,6 @@ class DialogTriggerState {
95
82
  "aria-expanded": getAriaExpanded(this.root.opts.open.current),
96
83
  "aria-controls": this.root.contentId,
97
84
  [this.root.attrs.trigger]: "",
98
- onpointerdown: this.onpointerdown,
99
85
  onkeydown: this.onkeydown,
100
86
  onclick: this.onclick,
101
87
  disabled: this.opts.disabled.current ? true : undefined,
@@ -18,6 +18,7 @@ export { DropdownMenu } from "./dropdown-menu/index.js";
18
18
  export { Label } from "./label/index.js";
19
19
  export { LinkPreview } from "./link-preview/index.js";
20
20
  export { Menubar } from "./menubar/index.js";
21
+ export { Meter } from "./meter/index.js";
21
22
  export { NavigationMenu } from "./navigation-menu/index.js";
22
23
  export { Pagination } from "./pagination/index.js";
23
24
  export { PinInput } from "./pin-input/index.js";
@@ -18,6 +18,7 @@ export { DropdownMenu } from "./dropdown-menu/index.js";
18
18
  export { Label } from "./label/index.js";
19
19
  export { LinkPreview } from "./link-preview/index.js";
20
20
  export { Menubar } from "./menubar/index.js";
21
+ export { Meter } from "./meter/index.js";
21
22
  export { NavigationMenu } from "./navigation-menu/index.js";
22
23
  export { Pagination } from "./pagination/index.js";
23
24
  export { PinInput } from "./pin-input/index.js";
@@ -0,0 +1,38 @@
1
+ <script lang="ts">
2
+ import { box, mergeProps } from "svelte-toolbelt";
3
+ import type { MeterRootProps } from "../types.js";
4
+ import { useMeterRootState } from "../meter.svelte.js";
5
+ import { useId } from "../../../internal/use-id.js";
6
+
7
+ let {
8
+ child,
9
+ children,
10
+ value = 0,
11
+ max = 100,
12
+ min = 0,
13
+ id = useId(),
14
+ ref = $bindable(null),
15
+ ...restProps
16
+ }: MeterRootProps = $props();
17
+
18
+ const rootState = useMeterRootState({
19
+ value: box.with(() => value),
20
+ max: box.with(() => max),
21
+ min: box.with(() => min),
22
+ id: box.with(() => id),
23
+ ref: box.with(
24
+ () => ref,
25
+ (v) => (ref = v)
26
+ ),
27
+ });
28
+
29
+ const mergedProps = $derived(mergeProps(restProps, rootState.props));
30
+ </script>
31
+
32
+ {#if child}
33
+ {@render child({ props: mergedProps })}
34
+ {:else}
35
+ <div {...mergedProps}>
36
+ {@render children?.()}
37
+ </div>
38
+ {/if}
@@ -0,0 +1,4 @@
1
+ import type { MeterRootProps } from "../types.js";
2
+ declare const Meter: import("svelte").Component<MeterRootProps, {}, "ref">;
3
+ type Meter = ReturnType<typeof Meter>;
4
+ export default Meter;
@@ -0,0 +1,2 @@
1
+ export { default as Root } from "./components/meter.svelte";
2
+ export type { MeterRootProps as RootProps } from "./types.js";
@@ -0,0 +1 @@
1
+ export { default as Root } from "./components/meter.svelte";
@@ -0,0 +1 @@
1
+ export * as Meter from "./exports.js";
@@ -0,0 +1 @@
1
+ export * as Meter from "./exports.js";
@@ -0,0 +1,24 @@
1
+ import type { ReadableBoxedValues } from "../../internal/box.svelte.js";
2
+ import type { WithRefProps } from "../../internal/types.js";
3
+ type MeterRootStateProps = WithRefProps<ReadableBoxedValues<{
4
+ value: number;
5
+ max: number;
6
+ min: number;
7
+ }>>;
8
+ declare class MeterRootState {
9
+ readonly opts: MeterRootStateProps;
10
+ constructor(opts: MeterRootStateProps);
11
+ props: {
12
+ readonly role: "meter";
13
+ readonly value: number;
14
+ readonly "aria-valuemin": number;
15
+ readonly "aria-valuemax": number;
16
+ readonly "aria-valuenow": number;
17
+ readonly "data-value": number;
18
+ readonly "data-max": number;
19
+ readonly "data-min": number;
20
+ readonly "data-meter-root": "";
21
+ };
22
+ }
23
+ export declare function useMeterRootState(props: MeterRootStateProps): MeterRootState;
24
+ export {};
@@ -0,0 +1,23 @@
1
+ import { useRefById } from "svelte-toolbelt";
2
+ const METER_ROOT_ATTR = "data-meter-root";
3
+ class MeterRootState {
4
+ opts;
5
+ constructor(opts) {
6
+ this.opts = opts;
7
+ useRefById(opts);
8
+ }
9
+ props = $derived.by(() => ({
10
+ role: "meter",
11
+ value: this.opts.value.current,
12
+ "aria-valuemin": this.opts.min.current,
13
+ "aria-valuemax": this.opts.max.current,
14
+ "aria-valuenow": this.opts.value.current,
15
+ "data-value": this.opts.value.current,
16
+ "data-max": this.opts.max.current,
17
+ "data-min": this.opts.min.current,
18
+ [METER_ROOT_ATTR]: "",
19
+ }));
20
+ }
21
+ export function useMeterRootState(props) {
22
+ return new MeterRootState(props);
23
+ }
@@ -0,0 +1,23 @@
1
+ import type { WithChild, Without } from "../../internal/types.js";
2
+ import type { BitsPrimitiveDivAttributes } from "../../shared/attributes.js";
3
+ export type MeterRootPropsWithoutHTML = WithChild<{
4
+ /**
5
+ * The current value of the meter.
6
+ *
7
+ * @default 0
8
+ */
9
+ value?: number;
10
+ /**
11
+ * The maximum value of the meter.
12
+ *
13
+ * @default 100
14
+ */
15
+ max?: number;
16
+ /**
17
+ * The minimum value of the meter.
18
+ *
19
+ * @default 0
20
+ */
21
+ min?: number;
22
+ }>;
23
+ export type MeterRootProps = MeterRootPropsWithoutHTML & Without<BitsPrimitiveDivAttributes, MeterRootPropsWithoutHTML>;
@@ -0,0 +1 @@
1
+ export {};
@@ -50,6 +50,13 @@ class RadioGroupItemState {
50
50
  id: this.opts.id,
51
51
  ref: this.opts.ref,
52
52
  });
53
+ if (this.opts.value.current === this.root.opts.value.current) {
54
+ this.root.rovingFocusGroup.setCurrentTabStopId(this.opts.id.current);
55
+ this.#tabIndex = 0;
56
+ }
57
+ else if (!this.root.opts.value.current) {
58
+ this.#tabIndex = 0;
59
+ }
53
60
  $effect(() => {
54
61
  this.#tabIndex = this.root.rovingFocusGroup.getTabIndex(this.opts.ref.current);
55
62
  });
@@ -77,7 +84,7 @@ class RadioGroupItemState {
77
84
  }
78
85
  this.root.rovingFocusGroup.handleKeydown(this.opts.ref.current, e, true);
79
86
  }
80
- #tabIndex = $state(0);
87
+ #tabIndex = $state(-1);
81
88
  snippetProps = $derived.by(() => ({ checked: this.#isChecked }));
82
89
  props = $derived.by(() => ({
83
90
  id: this.opts.id.current,
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { Accordion, AlertDialog, AspectRatio, Avatar, Button, Calendar, Checkbox, Collapsible, Combobox, Command, ContextMenu, DateField, DatePicker, DateRangeField, DateRangePicker, Dialog, DropdownMenu, Label, LinkPreview, Menubar, NavigationMenu, Pagination, PinInput, Popover, Progress, RadioGroup, RangeCalendar, ScrollArea, Select, Separator, Slider, Switch, Tabs, Toggle, ToggleGroup, Toolbar, Tooltip, Portal, IsUsingKeyboard, computeCommandScore, } from "./bits/index.js";
1
+ export { Accordion, AlertDialog, AspectRatio, Avatar, Button, Calendar, Checkbox, Collapsible, Combobox, Command, ContextMenu, DateField, DatePicker, DateRangeField, DateRangePicker, Dialog, DropdownMenu, Label, LinkPreview, Menubar, Meter, NavigationMenu, Pagination, PinInput, Popover, Progress, RadioGroup, RangeCalendar, ScrollArea, Select, Separator, Slider, Switch, Tabs, Toggle, ToggleGroup, Toolbar, Tooltip, Portal, IsUsingKeyboard, computeCommandScore, } from "./bits/index.js";
2
2
  export * from "./shared/index.js";
3
3
  export type * from "./shared/index.js";
4
4
  export * from "./types.js";
package/dist/index.js CHANGED
@@ -1,3 +1,3 @@
1
- export { Accordion, AlertDialog, AspectRatio, Avatar, Button, Calendar, Checkbox, Collapsible, Combobox, Command, ContextMenu, DateField, DatePicker, DateRangeField, DateRangePicker, Dialog, DropdownMenu, Label, LinkPreview, Menubar, NavigationMenu, Pagination, PinInput, Popover, Progress, RadioGroup, RangeCalendar, ScrollArea, Select, Separator, Slider, Switch, Tabs, Toggle, ToggleGroup, Toolbar, Tooltip, Portal, IsUsingKeyboard, computeCommandScore, } from "./bits/index.js";
1
+ export { Accordion, AlertDialog, AspectRatio, Avatar, Button, Calendar, Checkbox, Collapsible, Combobox, Command, ContextMenu, DateField, DatePicker, DateRangeField, DateRangePicker, Dialog, DropdownMenu, Label, LinkPreview, Menubar, Meter, NavigationMenu, Pagination, PinInput, Popover, Progress, RadioGroup, RangeCalendar, ScrollArea, Select, Separator, Slider, Switch, Tabs, Toggle, ToggleGroup, Toolbar, Tooltip, Portal, IsUsingKeyboard, computeCommandScore, } from "./bits/index.js";
2
2
  export * from "./shared/index.js";
3
3
  export * from "./types.js";
package/dist/types.d.ts CHANGED
@@ -19,6 +19,7 @@ export type * from "./bits/label/types.js";
19
19
  export type * from "./bits/link-preview/types.js";
20
20
  export type * from "./bits/select/types.js";
21
21
  export type * from "./bits/menubar/types.js";
22
+ export type * from "./bits/meter/types.js";
22
23
  export type * from "./bits/navigation-menu/types.js";
23
24
  export type * from "./bits/pagination/types.js";
24
25
  export type * from "./bits/pin-input/types.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bits-ui",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "license": "MIT",
5
5
  "repository": "github:huntabyte/bits-ui",
6
6
  "funding": "https://github.com/sponsors/huntabyte",