@toife/vue 3.1.3 → 3.1.5

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 (157) hide show
  1. package/README.md +1 -1
  2. package/package.json +1 -1
  3. package/src/components/action/action.scss +1 -1
  4. package/src/components/action/action.vue +5 -5
  5. package/src/components/app/app.scss +2 -2
  6. package/src/components/app/app.type.ts +6 -0
  7. package/src/components/app/app.vue +8 -2
  8. package/src/components/avatar/avatar.scss +6 -4
  9. package/src/components/avatar/avatar.vue +6 -6
  10. package/src/components/button/button.scss +19 -16
  11. package/src/components/button/button.type.ts +3 -1
  12. package/src/components/button/button.vue +7 -7
  13. package/src/components/cable/cable.vue +2 -2
  14. package/src/components/card/card/card.scss +5 -3
  15. package/src/components/card/card/card.vue +5 -5
  16. package/src/components/card/card-body/card-body.scss +2 -2
  17. package/src/components/card/card-body/card-body.vue +2 -2
  18. package/src/components/card/card-footer/card-footer.scss +3 -3
  19. package/src/components/card/card-footer/card-footer.vue +2 -2
  20. package/src/components/card/card-header/card-header.scss +3 -3
  21. package/src/components/card/card-header/card-header.vue +2 -2
  22. package/src/components/checkbox/checkbox.html +1 -1
  23. package/src/components/checkbox/checkbox.scss +27 -27
  24. package/src/components/checkbox/checkbox.type.ts +4 -0
  25. package/src/components/checkbox/checkbox.vue +8 -6
  26. package/src/components/collapse/collapse.html +1 -1
  27. package/src/components/collapse/collapse.scss +2 -2
  28. package/src/components/collapse/collapse.vue +9 -9
  29. package/src/components/container/container.vue +2 -2
  30. package/src/components/decision-modal/decision-modal.scss +12 -10
  31. package/src/components/decision-modal/decision-modal.vue +8 -8
  32. package/src/components/divider/divider.scss +2 -2
  33. package/src/components/divider/divider.vue +4 -4
  34. package/src/components/dropdown/dropdown.scss +8 -5
  35. package/src/components/dropdown/dropdown.type.ts +4 -1
  36. package/src/components/dropdown/dropdown.vue +8 -6
  37. package/src/components/field/field.type.ts +4 -4
  38. package/src/components/field/outline/outline.scss +24 -21
  39. package/src/components/field/outline/outline.vue +26 -18
  40. package/src/components/form-group/form-group.vue +2 -2
  41. package/src/components/gesture-indicator/gesture-indicator.scss +4 -2
  42. package/src/components/gesture-indicator/gesture-indicator.vue +4 -4
  43. package/src/components/image/image.vue +12 -5
  44. package/src/components/index.ts +1 -0
  45. package/src/components/layout/flex/flex.scss +0 -2
  46. package/src/components/layout/flex/flex.vue +8 -8
  47. package/src/components/layout/flex-item/flex-item.html +1 -0
  48. package/src/components/layout/flex-item/flex-item.scss +48 -0
  49. package/src/components/layout/flex-item/flex-item.type.ts +11 -0
  50. package/src/components/layout/flex-item/flex-item.vue +27 -0
  51. package/src/components/layout/flex-item/index.ts +2 -0
  52. package/src/components/layout/grid/grid.scss +0 -2
  53. package/src/components/layout/grid/grid.vue +6 -6
  54. package/src/components/layout/grid-item/grid-item.html +1 -0
  55. package/src/components/layout/grid-item/grid-item.scss +49 -0
  56. package/src/components/layout/grid-item/grid-item.type.ts +14 -0
  57. package/src/components/layout/grid-item/grid-item.vue +27 -0
  58. package/src/components/layout/grid-item/index.ts +2 -0
  59. package/src/components/layout/index.ts +2 -1
  60. package/src/components/modal/modal.scss +4 -2
  61. package/src/components/modal/modal.vue +68 -5
  62. package/src/components/page/page.vue +2 -2
  63. package/src/components/present/present.scss +3 -3
  64. package/src/components/present/present.vue +14 -14
  65. package/src/components/radio/radio/radio.html +1 -1
  66. package/src/components/radio/radio/radio.scss +25 -18
  67. package/src/components/radio/radio/radio.type.ts +4 -0
  68. package/src/components/radio/radio/radio.vue +7 -5
  69. package/src/components/radio/radio-group/radio-group.vue +2 -2
  70. package/src/components/refresher/refresher.html +1 -4
  71. package/src/components/refresher/refresher.scss +8 -26
  72. package/src/components/refresher/refresher.vue +2 -16
  73. package/src/components/route/route-navigator/route-navigator.scss +2 -2
  74. package/src/components/route/route-navigator/route-navigator.vue +10 -13
  75. package/src/components/route/route-wrapper/route-wrapper.composable.ts +5 -15
  76. package/src/components/route/route-wrapper/route-wrapper.type.ts +0 -4
  77. package/src/components/route/route-wrapper/route-wrapper.vue +4 -12
  78. package/src/components/route/route.type.ts +0 -1
  79. package/src/components/segmented-field/segmented-field.html +1 -1
  80. package/src/components/segmented-field/segmented-field.scss +2 -2
  81. package/src/components/segmented-field/segmented-field.vue +8 -8
  82. package/src/components/select/select.html +2 -2
  83. package/src/components/select/select.scss +10 -10
  84. package/src/components/select/select.vue +10 -10
  85. package/src/components/skeleton/skeleton.scss +3 -1
  86. package/src/components/skeleton/skeleton.vue +7 -7
  87. package/src/components/slide-range/index.ts +2 -0
  88. package/src/components/slide-range/slide-range.html +19 -0
  89. package/src/components/slide-range/slide-range.scss +161 -0
  90. package/src/components/slide-range/slide-range.type.ts +16 -0
  91. package/src/components/slide-range/slide-range.vue +229 -0
  92. package/src/components/switch/switch.html +1 -1
  93. package/src/components/switch/switch.scss +29 -38
  94. package/src/components/switch/switch.type.ts +6 -0
  95. package/src/components/switch/switch.vue +24 -8
  96. package/src/components/tabs/tab/tab.html +1 -1
  97. package/src/components/tabs/tab/tab.scss +13 -0
  98. package/src/components/tabs/tab/tab.vue +4 -2
  99. package/src/components/tabs/tabs/index.ts +1 -0
  100. package/src/components/tabs/tabs/tabs.scss +194 -122
  101. package/src/components/tabs/tabs/tabs.type.ts +5 -2
  102. package/src/components/tabs/tabs/tabs.vue +47 -27
  103. package/src/components/toast/toast/toast.vue +2 -2
  104. package/src/components/toast/toast-content/toast-content.scss +6 -5
  105. package/src/components/toast/toast-content/toast-content.vue +5 -5
  106. package/src/components/toolbar/toolbar.scss +4 -4
  107. package/src/components/toolbar/toolbar.type.ts +1 -1
  108. package/src/components/toolbar/toolbar.vue +7 -7
  109. package/src/factory.ts +108 -50
  110. package/src/index.ts +1 -1
  111. package/src/type.ts +2 -1
  112. package/src/utils/style/index.ts +9 -9
  113. package/src/utils/style.md +9 -9
  114. package/src/components/action/action.md +0 -115
  115. package/src/components/app/app.md +0 -77
  116. package/src/components/avatar/avatar.md +0 -64
  117. package/src/components/button/button.md +0 -66
  118. package/src/components/cable/cable.md +0 -57
  119. package/src/components/card/card/card.md +0 -57
  120. package/src/components/card/card-body/card-body.md +0 -34
  121. package/src/components/card/card-footer/card-footer.md +0 -42
  122. package/src/components/card/card-header/card-header.md +0 -44
  123. package/src/components/checkbox/checkbox.md +0 -60
  124. package/src/components/collapse/collapse.md +0 -59
  125. package/src/components/container/container.md +0 -38
  126. package/src/components/decision-modal/decision-modal.md +0 -79
  127. package/src/components/divider/divider.md +0 -42
  128. package/src/components/field/field.md +0 -68
  129. package/src/components/field/outline/outline-field.md +0 -44
  130. package/src/components/form-group/form-group.md +0 -41
  131. package/src/components/gesture-indicator/gesture-indicator.md +0 -42
  132. package/src/components/image/image.md +0 -41
  133. package/src/components/layout/cell/cell.html +0 -1
  134. package/src/components/layout/cell/cell.md +0 -47
  135. package/src/components/layout/cell/cell.scss +0 -54
  136. package/src/components/layout/cell/cell.type.ts +0 -19
  137. package/src/components/layout/cell/cell.vue +0 -35
  138. package/src/components/layout/cell/index.ts +0 -2
  139. package/src/components/layout/grid/grid.md +0 -50
  140. package/src/components/modal/modal.md +0 -65
  141. package/src/components/page/page.md +0 -39
  142. package/src/components/present/present.md +0 -60
  143. package/src/components/radio/radio/radio.md +0 -53
  144. package/src/components/radio/radio-group/radio-group.md +0 -62
  145. package/src/components/refresher/refresher.md +0 -53
  146. package/src/components/route/route-navigator/route-navigator.md +0 -50
  147. package/src/components/route/route-outlet/route-outlet.md +0 -30
  148. package/src/components/route/route-provider/route-provider.md +0 -46
  149. package/src/components/route/route-wrapper/route-wrapper.md +0 -45
  150. package/src/components/segmented-field/segmented-field.md +0 -58
  151. package/src/components/skeleton/skeleton.md +0 -47
  152. package/src/components/switch/switch.md +0 -57
  153. package/src/components/tabs/tab/tab.md +0 -52
  154. package/src/components/tabs/tabs/tabs.md +0 -59
  155. package/src/components/toast/toast/toast.md +0 -56
  156. package/src/components/toast/toast-content/toast-content.md +0 -41
  157. package/src/components/toolbar/toolbar.md +0 -54
@@ -3,7 +3,7 @@
3
3
  <script lang="ts" setup>
4
4
  import { computed, inject, ref } from "vue";
5
5
  import { type SelectOption, type SelectProps, type SelectEmit } from "./select.type";
6
- import { property, withPrefix } from "../../utils";
6
+ import { cssProperty, cssPrefix } from "../../utils";
7
7
  import { type AppProviderState, APP_PROVIDER_STATE_KEY } from "../app";
8
8
  import { Field } from "../field";
9
9
  import { Dropdown } from "../dropdown";
@@ -48,11 +48,11 @@ const shape = computed(() => {
48
48
  const selectAttrs = computed(() => {
49
49
  return {
50
50
  class: [
51
- withPrefix(["layer", "select"]),
52
- withPrefix(["role", role.value]),
53
- withPrefix("select"),
54
- withPrefix(["direction", direction.value]),
55
- withPrefix(["size", props.size]),
51
+ cssPrefix(["layer", "select"]),
52
+ cssPrefix(["role", role.value]),
53
+ cssPrefix("select"),
54
+ cssPrefix(["direction", direction.value]),
55
+ cssPrefix(["size", props.size]),
56
56
  {
57
57
  disabled: props.disabled,
58
58
  },
@@ -93,19 +93,19 @@ const fieldAttrs = computed(() => {
93
93
  });
94
94
 
95
95
  const selectIconAttrs = computed(() => ({
96
- class: [withPrefix("select-icon")],
96
+ class: [cssPrefix("select-icon")],
97
97
  }));
98
98
 
99
99
  const selectOptionAttrs = computed(() => ({
100
- class: [withPrefix("select-option")],
100
+ class: [cssPrefix("select-option")],
101
101
  }));
102
102
 
103
103
  const selectMessageAttrs = computed(() => ({
104
- class: [withPrefix("select-message")],
104
+ class: [cssPrefix("select-message")],
105
105
  }));
106
106
 
107
107
  const selectHelpAttrs = computed(() => ({
108
- class: [withPrefix("select-help")],
108
+ class: [cssPrefix("select-help")],
109
109
  }));
110
110
 
111
111
  // Methods
@@ -8,8 +8,10 @@ $skeleton-width: sass.fn-naming-var("skeleton", "width");
8
8
  $skeleton-height: sass.fn-naming-var("skeleton", "height");
9
9
  $skeleton-background-color: sass.fn-naming-var("skeleton", "background-color");
10
10
 
11
- $border-radius: sass.fn-naming-var("border-radius");
11
+ $radius-ratio: sass.fn-naming-var("radius-ratio");
12
12
  $transition-duration: sass.fn-naming-var("motion", "duration");
13
+ $radius-size: sass.fn-naming-var("radius-size");
14
+ $border-radius: calc(#{$radius-size} * #{$radius-ratio});
13
15
 
14
16
  .#{$skeleton} {
15
17
  width: #{$skeleton-width};
@@ -3,7 +3,7 @@
3
3
  <script lang="ts" setup>
4
4
  import { computed, inject } from "vue";
5
5
  import { type SkeletonProps } from "./skeleton.type";
6
- import { property, withPrefix } from "../../utils";
6
+ import { cssProperty, cssPrefix } from "../../utils";
7
7
  import { type AppProviderState, APP_PROVIDER_STATE_KEY } from "../app";
8
8
 
9
9
  // Component setup (props, emits, injects)
@@ -22,15 +22,15 @@ const skeletonAttrs = computed(() => {
22
22
 
23
23
  return {
24
24
  class: [
25
- withPrefix(["layer", "skeleton"]),
26
- withPrefix(["role", role]),
27
- withPrefix(["shape", shape]),
28
- withPrefix("skeleton"),
25
+ cssPrefix(["layer", "skeleton"]),
26
+ cssPrefix(["role", role]),
27
+ cssPrefix(["shape", shape]),
28
+ cssPrefix("skeleton"),
29
29
  ],
30
30
  style: {
31
- [property(["skeleton", "width"])]:
31
+ [cssProperty(["skeleton", "width"])]:
32
32
  props.width + (typeof props.width === "number" ? "px" : ""),
33
- [property(["skeleton", "height"])]:
33
+ [cssProperty(["skeleton", "height"])]:
34
34
  props.height + (typeof props.height === "number" ? "px" : ""),
35
35
  },
36
36
  };
@@ -0,0 +1,2 @@
1
+ export { default as SlideRange } from "./slide-range.vue";
2
+ export type { SlideRangeProps, SlideRangeEmit } from "./slide-range.type";
@@ -0,0 +1,19 @@
1
+ <div v-bind="slideRangeAttrs">
2
+ <div v-bind="trackContainerAttrs" ref="container">
3
+ <div v-bind="trackBodyAttrs">
4
+ <div v-bind="trackBackAttrs" @pointerup="onClickPath"></div>
5
+ <div v-bind="trackFrontAttrs" @pointerup="onClickPath"></div>
6
+ <div
7
+ v-for="value in ranges"
8
+ :key="value"
9
+ v-bind="nodeAttrs(value)"
10
+ @pointerup="onNodeSelect(value)"
11
+ ></div>
12
+ </div>
13
+
14
+ <div v-bind="thumbAttrs" ref="point">
15
+ <div v-bind="thumbInnerAttrs"></div>
16
+ <span v-if="percent > 0 && isShowTooltip" v-bind="tooltipAttrs">{{ displayValue }}</span>
17
+ </div>
18
+ </div>
19
+ </div>
@@ -0,0 +1,161 @@
1
+ @use "@toife/sass-layer" as sass;
2
+
3
+ // Class name
4
+ $slide-range: sass.fn-naming-prefix("slide-range");
5
+ $slide-range-track-container: sass.fn-naming-prefix("slide-range-track-container");
6
+ $slide-range-track-body: sass.fn-naming-prefix("slide-range-track-body");
7
+ $slide-range-track: sass.fn-naming-prefix("slide-range-track");
8
+ $slide-range-node: sass.fn-naming-prefix("slide-range-node");
9
+ $slide-range-thumb: sass.fn-naming-prefix("slide-range-thumb");
10
+ $slide-range-thumb-inner: sass.fn-naming-prefix("slide-range-thumb-inner");
11
+ $slide-range-tooltip: sass.fn-naming-prefix("slide-range-tooltip");
12
+ $shape-pill: sass.fn-naming-prefix("shape-pill");
13
+ $shape-rounded: sass.fn-naming-prefix("shape-rounded");
14
+ $shape-flat: sass.fn-naming-prefix("shape-flat");
15
+ $property-border-radius: sass.fn-naming-property("border-radius");
16
+
17
+ // Property name
18
+ $spacing-x: sass.fn-naming-var("spacing", "x");
19
+ $border-width: sass.fn-naming-var("stroke", "width");
20
+ $radius-ratio: sass.fn-naming-var("radius-ratio");
21
+ $radius-size: sass.fn-naming-var("radius-size");
22
+
23
+ $slide-range-background-color: sass.fn-naming-var("slide-range", "background-color");
24
+ $slide-range-background-color-hover: sass.fn-naming-var("slide-range", "background-color", "hover");
25
+ $slide-range-background-color-focus: sass.fn-naming-var("slide-range", "background-color", "focus");
26
+ $slide-range-background-color-active: sass.fn-naming-var(
27
+ "slide-range",
28
+ "background-color",
29
+ "active"
30
+ );
31
+ $slide-range-border-color: sass.fn-naming-var("slide-range", "border-color");
32
+ $slide-range-border-color-hover: sass.fn-naming-var("slide-range", "border-color", "hover");
33
+ $slide-range-border-color-focus: sass.fn-naming-var("slide-range", "border-color", "focus");
34
+ $slide-range-border-color-active: sass.fn-naming-var("slide-range", "border-color", "active");
35
+ $slide-range-percent: sass.fn-naming-var("slide-range", "percent");
36
+ $slide-range-position: sass.fn-naming-var("slide-range", "position");
37
+ $slide-range-tooltip-background-color: sass.fn-naming-var(
38
+ "slide-range",
39
+ "tooltip-background-color"
40
+ );
41
+ $slide-range-tooltip-color: sass.fn-naming-var("slide-range", "tooltip-color");
42
+
43
+ .#{$slide-range} {
44
+ display: flex;
45
+ justify-content: center;
46
+ align-items: center;
47
+ width: 100%;
48
+
49
+ &.#{$shape-pill} {
50
+ #{$property-border-radius}: 50%;
51
+
52
+ .#{$slide-range-tooltip} {
53
+ border-radius: 3px;
54
+ }
55
+ }
56
+
57
+ &.#{$shape-rounded} {
58
+ #{$property-border-radius}: 2px;
59
+
60
+ .#{$slide-range-tooltip} {
61
+ border-radius: 2px;
62
+ }
63
+ }
64
+
65
+ &.#{$shape-flat} {
66
+ #{$property-border-radius}: 0;
67
+
68
+ .#{$slide-range-tooltip} {
69
+ border-radius: 0;
70
+ }
71
+ }
72
+
73
+ &.disabled,
74
+ &.readonly {
75
+ opacity: 0.6;
76
+ }
77
+
78
+ &.disabled {
79
+ cursor: not-allowed;
80
+ }
81
+
82
+ .#{$slide-range-track-container} {
83
+ position: relative;
84
+ margin: calc(#{$spacing-x} * 2) #{$spacing-x};
85
+ width: calc(100% - #{$spacing-x} * 2);
86
+ height: 1rem;
87
+ touch-action: none;
88
+ }
89
+
90
+ .#{$slide-range-track-body} {
91
+ position: relative;
92
+ width: 100%;
93
+ height: 1px;
94
+ top: 50%;
95
+ transform: translateY(-50%);
96
+ }
97
+
98
+ .#{$slide-range-track} {
99
+ position: absolute;
100
+ top: 0;
101
+ left: 0;
102
+ height: 1px;
103
+
104
+ &.back {
105
+ width: 100%;
106
+ background-color: rgba(#{$slide-range-border-color});
107
+ }
108
+
109
+ &.front {
110
+ width: #{$slide-range-percent};
111
+ background-color: rgba(#{$slide-range-border-color-active});
112
+ }
113
+ }
114
+
115
+ .#{$slide-range-node} {
116
+ position: absolute;
117
+ left: #{$slide-range-position};
118
+ top: -3.5px;
119
+ height: 8px;
120
+ width: 8px;
121
+ border-radius: calc(min(8px, #{$radius-size}) * #{$radius-ratio});
122
+ transform: rotate(45deg);
123
+ background-color: rgba(#{$slide-range-background-color});
124
+ border: 1px solid rgba(#{$slide-range-border-color});
125
+
126
+ &.active {
127
+ border-color: rgba(#{$slide-range-border-color-active});
128
+ background-color: rgba(#{$slide-range-background-color-active});
129
+ }
130
+ }
131
+
132
+ .#{$slide-range-thumb} {
133
+ position: absolute;
134
+ left: #{$slide-range-percent};
135
+ top: 2px;
136
+ height: 100%;
137
+
138
+ .#{$slide-range-thumb-inner} {
139
+ position: absolute;
140
+ width: 12px;
141
+ height: 12px;
142
+ border: 1px solid rgba(#{$slide-range-border-color-active});
143
+ border-radius: calc(12px * #{$radius-ratio});
144
+ background-color: rgba(#{$slide-range-background-color});
145
+ transform: translateX(calc(-50% + 2px)) rotate(45deg);
146
+ }
147
+
148
+ .#{$slide-range-tooltip} {
149
+ position: absolute;
150
+ top: -23px;
151
+ left: calc(50% + 0.15rem);
152
+ transform: translateX(-50%);
153
+ padding: 0 calc(#{$spacing-x} * 0.5);
154
+ font-size: 0.6rem;
155
+ line-height: 0.9rem;
156
+ white-space: nowrap;
157
+ color: rgba(#{$slide-range-tooltip-color});
158
+ background-color: rgba(#{$slide-range-tooltip-background-color});
159
+ }
160
+ }
161
+ }
@@ -0,0 +1,16 @@
1
+ export type SlideRangeProps = {
2
+ modelValue?: string | number;
3
+ min?: number;
4
+ max?: number;
5
+ step?: number;
6
+ unit?: string;
7
+ disabled?: boolean;
8
+ readonly?: boolean;
9
+ role?: string;
10
+ shape?: string;
11
+ };
12
+
13
+ export type SlideRangeEmit = {
14
+ (e: "update:modelValue", value: string): void;
15
+ (e: "change", value: string): void;
16
+ };
@@ -0,0 +1,229 @@
1
+ <style lang="scss" src="./slide-range.scss" scoped></style>
2
+ <template src="./slide-range.html"></template>
3
+ <script lang="ts" setup>
4
+ import { gesture } from "@toife/gesture";
5
+ import { computed, onMounted, onUnmounted, ref } from "vue";
6
+ import { cssProperty, cssPrefix } from "../../utils";
7
+ import type { SlideRangeProps, SlideRangeEmit } from "./slide-range.type";
8
+ import { type AppProviderState, APP_PROVIDER_STATE_KEY } from "../app";
9
+ import { inject } from "vue";
10
+
11
+ // Component setup (props, emits, injects)
12
+ // ----------------------------------------------------------------------------
13
+ const props = withDefaults(defineProps<SlideRangeProps>(), {
14
+ modelValue: "",
15
+ min: 0,
16
+ max: 100,
17
+ step: 25,
18
+ unit: "",
19
+ disabled: false,
20
+ readonly: false,
21
+ });
22
+ const emit = defineEmits<SlideRangeEmit>();
23
+ const appState = inject<AppProviderState>(APP_PROVIDER_STATE_KEY);
24
+
25
+ // Reactive state
26
+ // ----------------------------------------------------------------------------
27
+ const point = ref<HTMLElement | null>(null);
28
+ const container = ref<HTMLElement | null>(null);
29
+ const isShowTooltip = ref(false);
30
+ let tooltipTimeout: ReturnType<typeof setTimeout> | undefined;
31
+ let gestureCleanup: { destroy: () => void } | null = null;
32
+ let dragStartPercent = 0;
33
+
34
+ // Computed properties
35
+ // ----------------------------------------------------------------------------
36
+ const normalizedValue = computed(() => {
37
+ if (typeof props.modelValue === "number") return props.modelValue;
38
+ if (typeof props.modelValue !== "string") return props.min;
39
+ const parsed = Number.parseFloat(props.modelValue);
40
+ if (Number.isNaN(parsed)) return props.min;
41
+ return parsed;
42
+ });
43
+
44
+ const percent = computed(() => {
45
+ const delta = props.max - props.min;
46
+ if (delta <= 0) return 0;
47
+ const p = ((normalizedValue.value - props.min) / delta) * 100;
48
+ return Math.max(0, Math.min(100, Math.round(p)));
49
+ });
50
+
51
+ const ranges = computed(() => {
52
+ const { min, max, step } = props;
53
+ if (step <= 0 || max < min) return [min, max];
54
+
55
+ const result: number[] = [];
56
+ for (let value = min; value <= max; value += step) {
57
+ result.push(value);
58
+ }
59
+
60
+ if (result.at(-1) !== max) {
61
+ result.push(max);
62
+ }
63
+
64
+ return result;
65
+ });
66
+
67
+ const displayValue = computed(() => {
68
+ const value = getValueFromPercent(percent.value);
69
+ return formatValue(value);
70
+ });
71
+
72
+ const slideRangeAttrs = computed(() => {
73
+ const role = props.role || appState?.role.value || "";
74
+ const shape = props.shape || appState?.shape.value || "";
75
+ return {
76
+ class: [
77
+ cssPrefix("slide-range"),
78
+ cssPrefix(["layer", "slide-range"]),
79
+ cssPrefix(["role", role]),
80
+ cssPrefix(["shape", shape]),
81
+ {
82
+ disabled: props.disabled,
83
+ readonly: props.readonly,
84
+ },
85
+ ],
86
+ };
87
+ });
88
+
89
+ const trackContainerAttrs = {
90
+ class: [cssPrefix("slide-range-track-container")],
91
+ } as const;
92
+
93
+ const trackBodyAttrs = {
94
+ class: [cssPrefix("slide-range-track-body")],
95
+ } as const;
96
+
97
+ const trackBackAttrs = {
98
+ class: [cssPrefix("slide-range-track"), "back"],
99
+ } as const;
100
+
101
+ const trackFrontAttrs = computed(() => {
102
+ return {
103
+ class: [cssPrefix("slide-range-track"), "front"],
104
+ style: {
105
+ [cssProperty(["slide-range", "percent"])]: `${percent.value}%`,
106
+ },
107
+ };
108
+ });
109
+
110
+ const thumbAttrs = computed(() => {
111
+ return {
112
+ class: [cssPrefix("slide-range-thumb")],
113
+ style: {
114
+ [cssProperty(["slide-range", "percent"])]: `${percent.value}%`,
115
+ },
116
+ };
117
+ });
118
+
119
+ const thumbInnerAttrs = {
120
+ class: [cssPrefix("slide-range-thumb-inner")],
121
+ } as const;
122
+
123
+ const tooltipAttrs = {
124
+ class: [cssPrefix("slide-range-tooltip")],
125
+ } as const;
126
+
127
+ // Methods
128
+ // ----------------------------------------------------------------------------
129
+ const nodeAttrs = (value: number) => {
130
+ const nodePercent = getPercentFromValue(value);
131
+ return {
132
+ class: [cssPrefix("slide-range-node"), { active: percent.value > nodePercent }],
133
+ style: {
134
+ [cssProperty(["slide-range", "position"])]: `${nodePercent}%`,
135
+ },
136
+ };
137
+ };
138
+
139
+ const getValueFromPercent = (currentPercent: number) => {
140
+ const { min, max } = props;
141
+ return Math.round(min + (max - min) * (currentPercent / 100));
142
+ };
143
+
144
+ const getPercentFromValue = (value: number) => {
145
+ const { min, max } = props;
146
+ if (max <= min) return 0;
147
+ const p = ((value - min) / (max - min)) * 100;
148
+ return Math.max(0, Math.min(100, p));
149
+ };
150
+
151
+ const formatValue = (value: number) => {
152
+ if (value === 0) return "";
153
+ return `${value}${props.unit || ""}`;
154
+ };
155
+
156
+ const showTooltipTemporarily = () => {
157
+ if (tooltipTimeout) clearTimeout(tooltipTimeout);
158
+ isShowTooltip.value = true;
159
+ tooltipTimeout = setTimeout(() => {
160
+ isShowTooltip.value = false;
161
+ }, 300);
162
+ };
163
+
164
+ const emitValueFromPercent = (currentPercent: number) => {
165
+ if (props.disabled || props.readonly) return;
166
+ const value = getValueFromPercent(currentPercent);
167
+ const payload = formatValue(value);
168
+ emit("update:modelValue", payload);
169
+ emit("change", payload);
170
+ };
171
+
172
+ const onNodeSelect = (value: number) => {
173
+ const nodePercent = getPercentFromValue(value);
174
+ emitValueFromPercent(nodePercent);
175
+ showTooltipTemporarily();
176
+ };
177
+
178
+ const getClientX = (ev: MouseEvent | TouchEvent) => {
179
+ if ("changedTouches" in ev && ev.changedTouches.length > 0) {
180
+ return ev.changedTouches[0].clientX;
181
+ }
182
+ return (ev as MouseEvent).clientX;
183
+ };
184
+
185
+ const onClickPath = (ev: MouseEvent | TouchEvent) => {
186
+ if (props.disabled || props.readonly || !container.value) return;
187
+ const width = container.value.offsetWidth;
188
+ const rect = container.value.getBoundingClientRect();
189
+ const x = getClientX(ev) - rect.left;
190
+ const p = (x / width) * 100;
191
+ emitValueFromPercent(Math.max(0, Math.min(100, p)));
192
+ showTooltipTemporarily();
193
+ };
194
+
195
+ // Lifecycle
196
+ // ----------------------------------------------------------------------------
197
+ onMounted(() => {
198
+ if (!point.value || !container.value) return;
199
+ gestureCleanup = gesture(point.value, {
200
+ down() {
201
+ if (props.disabled || props.readonly) return;
202
+ dragStartPercent = percent.value;
203
+ },
204
+ up() {
205
+ isShowTooltip.value = false;
206
+ },
207
+ cancel() {
208
+ isShowTooltip.value = false;
209
+ },
210
+ move({ initialDirection, deltaX }: { initialDirection: string; deltaX: number }) {
211
+ if (props.disabled || props.readonly) return;
212
+ if (initialDirection !== "right" && initialDirection !== "left") return;
213
+ if (!container.value) return;
214
+
215
+ const width = container.value.offsetWidth;
216
+ const p = (deltaX / width) * 100;
217
+ const nextPercent = Math.max(0, Math.min(100, Math.round(dragStartPercent + p)));
218
+ emitValueFromPercent(nextPercent);
219
+ if (tooltipTimeout) clearTimeout(tooltipTimeout);
220
+ isShowTooltip.value = true;
221
+ },
222
+ });
223
+ });
224
+
225
+ onUnmounted(() => {
226
+ if (tooltipTimeout) clearTimeout(tooltipTimeout);
227
+ gestureCleanup?.destroy();
228
+ });
229
+ </script>
@@ -2,7 +2,7 @@
2
2
  v-bind="switchWrapperAttrs"
3
3
  :tabindex="disabled ? -1 : 0"
4
4
  :aria-checked="props.modelValue"
5
- @click="onSwitch"
5
+ @pointerup="onSwitch"
6
6
  @focus="onFocus"
7
7
  @blur="onBlur"
8
8
  @keydown="onKeydown"
@@ -3,8 +3,6 @@
3
3
  // Classes
4
4
  $switch: sass.fn-naming-prefix("switch");
5
5
  $switch-icon: sass.fn-naming-prefix("switch-icon");
6
- $shape-rounded: sass.fn-naming-prefix("shape-rounded");
7
- $shape-pill: sass.fn-naming-prefix("shape-pill");
8
6
  $switch-wrapper: sass.fn-naming-prefix("switch-wrapper");
9
7
 
10
8
  // Property name - layer: switch (track + thumb; states like button + inactive)
@@ -19,8 +17,12 @@ $switch-color-disabled: sass.fn-naming-var("switch", "color", "disabled");
19
17
  $switch-box-shadow-color-focus: sass.fn-naming-var("switch", "box-shadow-color", "focus");
20
18
 
21
19
  $transition-duration: sass.fn-naming-var("motion", "duration");
22
- $border-radius: sass.fn-naming-var("border-radius");
20
+ $radius-ratio: sass.fn-naming-var("radius-ratio");
23
21
  $spacing-x: sass.fn-naming-var("spacing", "x");
22
+ $size-control-mark-size: sass.fn-naming-var("control-mark-size");
23
+ $radius-size: sass.fn-naming-var("radius-size");
24
+ $icon-size: calc(#{$size-control-mark-size} - #{$size-control-mark-size} * 0.2);
25
+ $bounce-ratio: sass.fn-naming-var("switch", "bounce", "ratio");
24
26
 
25
27
  .#{$switch-wrapper} {
26
28
  display: inline-flex;
@@ -29,43 +31,32 @@ $spacing-x: sass.fn-naming-var("spacing", "x");
29
31
  cursor: pointer;
30
32
  box-shadow: none !important;
31
33
 
32
- .#{$switch} {
33
- height: 1.5rem;
34
- aspect-ratio: 11/6;
35
- position: relative;
36
- transition:
37
- background-color #{$transition-duration} ease,
38
- color #{$transition-duration} ease,
39
- border-color #{$transition-duration} ease,
40
- border-radius #{$transition-duration} ease,
41
- box-shadow #{$transition-duration} ease;
42
- border-radius: #{$border-radius};
43
-
34
+ &.transition {
35
+ .#{$switch},
44
36
  .#{$switch-icon} {
45
- height: calc(100% - 0.3rem);
46
- position: absolute;
47
- top: 0;
48
- border-radius: #{$border-radius};
49
- aspect-ratio: 1/1;
50
- margin: 0.15rem;
51
37
  transition:
52
38
  background-color #{$transition-duration} ease,
53
39
  color #{$transition-duration} ease,
54
40
  border-color #{$transition-duration} ease,
55
41
  border-radius #{$transition-duration} ease,
42
+ box-shadow #{$transition-duration} ease,
56
43
  transform #{$transition-duration} ease;
57
44
  }
45
+ }
58
46
 
59
- &.#{$shape-rounded} {
60
- .#{$switch-icon} {
61
- border-radius: calc(#{$border-radius} - 10%);
62
- }
63
- }
47
+ .#{$switch} {
48
+ height: #{$size-control-mark-size};
49
+ aspect-ratio: 14/8;
50
+ position: relative;
51
+ border-radius: calc(min(#{$size-control-mark-size}, #{$radius-size}) * #{$radius-ratio});
64
52
 
65
- &.#{$shape-pill} {
66
- .#{$switch-icon} {
67
- border-radius: 50%;
68
- }
53
+ .#{$switch-icon} {
54
+ width: #{$icon-size};
55
+ height: #{$icon-size};
56
+ position: absolute;
57
+ top: 0;
58
+ border-radius: calc(min(#{$icon-size}, #{$radius-size}) * #{$radius-ratio});
59
+ margin: calc(#{$size-control-mark-size} * 0.1);
69
60
  }
70
61
  }
71
62
 
@@ -73,12 +64,12 @@ $spacing-x: sass.fn-naming-var("spacing", "x");
73
64
  cursor: not-allowed;
74
65
 
75
66
  .#{$switch} {
76
- background-color: rgb(#{$switch-background-color-disabled});
67
+ background-color: rgba(#{$switch-background-color-disabled});
77
68
  cursor: not-allowed;
78
69
  opacity: 0.3;
79
70
 
80
71
  .#{$switch-icon} {
81
- background-color: rgb(#{$switch-color-disabled});
72
+ background-color: rgba(#{$switch-color-disabled});
82
73
  }
83
74
  }
84
75
  }
@@ -93,10 +84,10 @@ $spacing-x: sass.fn-naming-var("spacing", "x");
93
84
 
94
85
  &:not(.on) {
95
86
  .#{$switch} {
96
- background-color: rgb(#{$switch-background-color-off});
87
+ background-color: rgba(#{$switch-background-color-off});
97
88
 
98
89
  .#{$switch-icon} {
99
- background-color: rgb(#{$switch-color-off});
90
+ background-color: rgba(#{$switch-color-off});
100
91
  left: 0;
101
92
  }
102
93
  }
@@ -104,13 +95,13 @@ $spacing-x: sass.fn-naming-var("spacing", "x");
104
95
 
105
96
  &.on {
106
97
  .#{$switch} {
107
- background-color: rgb(#{$switch-background-color-on});
98
+ background-color: rgba(#{$switch-background-color-on});
108
99
 
109
100
  .#{$switch-icon} {
110
101
  right: 0;
111
102
  animation: shrink #{$transition-duration} linear;
112
103
  animation-fill-mode: forwards;
113
- background-color: rgb(#{$switch-color-on});
104
+ background-color: rgba(#{$switch-color-on});
114
105
  }
115
106
  }
116
107
  }
@@ -118,7 +109,7 @@ $spacing-x: sass.fn-naming-var("spacing", "x");
118
109
  &.focus {
119
110
  &.shadow {
120
111
  .#{$switch} {
121
- box-shadow: 0 0 0 0.25rem rgb(#{$switch-box-shadow-color-focus}, 0.25);
112
+ box-shadow: 0 0 0 0.25rem rgba(#{$switch-box-shadow-color-focus}, 0.25);
122
113
  }
123
114
  }
124
115
  }
@@ -129,7 +120,7 @@ $spacing-x: sass.fn-naming-var("spacing", "x");
129
120
  transform: scale(1);
130
121
  }
131
122
  50% {
132
- transform: scale(1.5);
123
+ transform: scale(#{$bounce-ratio});
133
124
  }
134
125
  100% {
135
126
  transform: scale(1);