@toife/vue 3.1.3 → 3.1.4

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 (100) hide show
  1. package/package.json +1 -1
  2. package/src/components/app/app.type.ts +6 -0
  3. package/src/components/app/app.vue +7 -1
  4. package/src/components/avatar/avatar.scss +3 -2
  5. package/src/components/button/button.scss +9 -6
  6. package/src/components/button/button.type.ts +3 -1
  7. package/src/components/card/card/card.scss +3 -1
  8. package/src/components/checkbox/checkbox.scss +9 -14
  9. package/src/components/checkbox/checkbox.type.ts +4 -0
  10. package/src/components/checkbox/checkbox.vue +2 -0
  11. package/src/components/decision-modal/decision-modal.scss +3 -1
  12. package/src/components/dropdown/dropdown.scss +5 -2
  13. package/src/components/dropdown/dropdown.type.ts +4 -1
  14. package/src/components/dropdown/dropdown.vue +4 -0
  15. package/src/components/field/field.type.ts +2 -2
  16. package/src/components/field/outline/outline.scss +9 -8
  17. package/src/components/gesture-indicator/gesture-indicator.scss +3 -1
  18. package/src/components/index.ts +1 -0
  19. package/src/components/layout/flex/flex.scss +0 -2
  20. package/src/components/layout/flex-item/flex-item.html +1 -0
  21. package/src/components/layout/flex-item/flex-item.scss +48 -0
  22. package/src/components/layout/flex-item/flex-item.type.ts +11 -0
  23. package/src/components/layout/flex-item/flex-item.vue +27 -0
  24. package/src/components/layout/flex-item/index.ts +2 -0
  25. package/src/components/layout/grid/grid.scss +0 -2
  26. package/src/components/layout/grid-item/grid-item.html +1 -0
  27. package/src/components/layout/grid-item/grid-item.scss +49 -0
  28. package/src/components/layout/grid-item/grid-item.type.ts +14 -0
  29. package/src/components/layout/grid-item/grid-item.vue +27 -0
  30. package/src/components/layout/grid-item/index.ts +2 -0
  31. package/src/components/layout/index.ts +2 -1
  32. package/src/components/modal/modal.scss +3 -1
  33. package/src/components/radio/radio/radio.scss +7 -5
  34. package/src/components/radio/radio/radio.type.ts +4 -0
  35. package/src/components/radio/radio/radio.vue +2 -0
  36. package/src/components/refresher/refresher.html +4 -4
  37. package/src/components/refresher/refresher.scss +10 -4
  38. package/src/components/refresher/refresher.vue +3 -3
  39. package/src/components/skeleton/skeleton.scss +3 -1
  40. package/src/components/slide-range/index.ts +2 -0
  41. package/src/components/slide-range/slide-range.html +20 -0
  42. package/src/components/slide-range/slide-range.scss +161 -0
  43. package/src/components/slide-range/slide-range.type.ts +16 -0
  44. package/src/components/slide-range/slide-range.vue +229 -0
  45. package/src/components/switch/switch.scss +11 -22
  46. package/src/components/switch/switch.type.ts +5 -0
  47. package/src/components/switch/switch.vue +2 -0
  48. package/src/components/tabs/tabs/tabs.scss +135 -95
  49. package/src/components/tabs/tabs/tabs.type.ts +0 -1
  50. package/src/components/tabs/tabs/tabs.vue +6 -10
  51. package/src/components/toast/toast-content/toast-content.scss +3 -2
  52. package/src/components/toolbar/toolbar.scss +1 -1
  53. package/src/components/toolbar/toolbar.type.ts +1 -1
  54. package/src/components/toolbar/toolbar.vue +2 -2
  55. package/src/factory.ts +6 -2
  56. package/src/index.ts +1 -1
  57. package/src/components/action/action.md +0 -115
  58. package/src/components/app/app.md +0 -77
  59. package/src/components/avatar/avatar.md +0 -64
  60. package/src/components/button/button.md +0 -66
  61. package/src/components/cable/cable.md +0 -57
  62. package/src/components/card/card/card.md +0 -57
  63. package/src/components/card/card-body/card-body.md +0 -34
  64. package/src/components/card/card-footer/card-footer.md +0 -42
  65. package/src/components/card/card-header/card-header.md +0 -44
  66. package/src/components/checkbox/checkbox.md +0 -60
  67. package/src/components/collapse/collapse.md +0 -59
  68. package/src/components/container/container.md +0 -38
  69. package/src/components/decision-modal/decision-modal.md +0 -79
  70. package/src/components/divider/divider.md +0 -42
  71. package/src/components/field/field.md +0 -68
  72. package/src/components/field/outline/outline-field.md +0 -44
  73. package/src/components/form-group/form-group.md +0 -41
  74. package/src/components/gesture-indicator/gesture-indicator.md +0 -42
  75. package/src/components/image/image.md +0 -41
  76. package/src/components/layout/cell/cell.html +0 -1
  77. package/src/components/layout/cell/cell.md +0 -47
  78. package/src/components/layout/cell/cell.scss +0 -54
  79. package/src/components/layout/cell/cell.type.ts +0 -19
  80. package/src/components/layout/cell/cell.vue +0 -35
  81. package/src/components/layout/cell/index.ts +0 -2
  82. package/src/components/layout/grid/grid.md +0 -50
  83. package/src/components/modal/modal.md +0 -65
  84. package/src/components/page/page.md +0 -39
  85. package/src/components/present/present.md +0 -60
  86. package/src/components/radio/radio/radio.md +0 -53
  87. package/src/components/radio/radio-group/radio-group.md +0 -62
  88. package/src/components/refresher/refresher.md +0 -53
  89. package/src/components/route/route-navigator/route-navigator.md +0 -50
  90. package/src/components/route/route-outlet/route-outlet.md +0 -30
  91. package/src/components/route/route-provider/route-provider.md +0 -46
  92. package/src/components/route/route-wrapper/route-wrapper.md +0 -45
  93. package/src/components/segmented-field/segmented-field.md +0 -58
  94. package/src/components/skeleton/skeleton.md +0 -47
  95. package/src/components/switch/switch.md +0 -57
  96. package/src/components/tabs/tab/tab.md +0 -52
  97. package/src/components/tabs/tabs/tabs.md +0 -59
  98. package/src/components/toast/toast/toast.md +0 -56
  99. package/src/components/toast/toast-content/toast-content.md +0 -41
  100. package/src/components/toolbar/toolbar.md +0 -54
@@ -9,12 +9,14 @@ $viewport-width: sass.fn-naming-var("viewport", "width");
9
9
  $viewport-max-width: sass.fn-naming-var("viewport", "max-width");
10
10
  $viewport-height: sass.fn-naming-var("viewport", "height");
11
11
  $viewport-max-height: sass.fn-naming-var("viewport", "max-height");
12
- $border-radius: sass.fn-naming-var("border-radius");
12
+ $radius-ratio: sass.fn-naming-var("radius-ratio");
13
+ $radius-size: sass.fn-naming-var("radius-size");
13
14
  $safe-area-top: sass.fn-naming-var("safe-area", "top");
14
15
  $safe-area-bottom: sass.fn-naming-var("safe-area", "bottom");
15
16
  $safe-area-left: sass.fn-naming-var("safe-area", "left");
16
17
  $safe-area-right: sass.fn-naming-var("safe-area", "right");
17
18
  $transition-duration: sass.fn-naming-var("motion", "duration");
19
+ $border-radius: calc(min(#{$viewport-width}, #{$radius-size}) * #{$radius-ratio});
18
20
 
19
21
  .#{$modal} {
20
22
  background: rgb(#{$modal-background-color});
@@ -22,7 +22,9 @@ $radio-border-color-disabled: sass.fn-naming-var("radio", "border-color", "disab
22
22
  $radio-box-shadow-color-focus: sass.fn-naming-var("radio", "box-shadow-color", "focus");
23
23
 
24
24
  $transition-duration: sass.fn-naming-var("motion", "duration");
25
- $border-radius: sass.fn-naming-var("border-radius");
25
+ $radius-ratio: sass.fn-naming-var("radius-ratio");
26
+
27
+ $size-control-mark-size: sass.fn-naming-var("control-mark-size");
26
28
 
27
29
  .#{$radio} {
28
30
  display: inline-flex;
@@ -35,8 +37,8 @@ $border-radius: sass.fn-naming-var("border-radius");
35
37
 
36
38
  .#{$radio-icon} {
37
39
  flex-shrink: 0;
38
- width: 1.25rem;
39
- height: 1.25rem;
40
+ width: #{$size-control-mark-size};
41
+ height: #{$size-control-mark-size};
40
42
  display: flex;
41
43
  align-items: center;
42
44
  justify-content: center;
@@ -54,8 +56,8 @@ $border-radius: sass.fn-naming-var("border-radius");
54
56
 
55
57
  &::after {
56
58
  content: "";
57
- width: 0.5rem;
58
- height: 0.5rem;
59
+ width: calc(#{$size-control-mark-size} * 0.5);
60
+ height: calc(#{$size-control-mark-size} * 0.5);
59
61
  border-radius: 50%;
60
62
  background-color: rgb(#{$radio-color});
61
63
  transform: scale(0);
@@ -1,9 +1,13 @@
1
+ import { AppSize } from "@/components/app";
2
+
1
3
  export type RadioVariant = "fill" | "outline";
4
+ export type RadioSize = AppSize;
2
5
 
3
6
  // Type definitions
4
7
  export type RadioProps = {
5
8
  value: string | number;
6
9
  role?: string;
10
+ size?: RadioSize;
7
11
  variant?: RadioVariant;
8
12
  disabled?: boolean;
9
13
  readonly?: boolean;
@@ -11,6 +11,7 @@ import { type RadioGroupProviderState, RADIO_GROUP_PROVIDER_STATE_KEY } from "..
11
11
  // ----------------------------------------------------------------------------
12
12
  const props = withDefaults(defineProps<RadioProps>(), {
13
13
  disabled: false,
14
+ size: "standard",
14
15
  shadow: undefined,
15
16
  });
16
17
  const appState = inject<AppProviderState>(APP_PROVIDER_STATE_KEY);
@@ -43,6 +44,7 @@ const radioAttrs = computed(() => {
43
44
  class: [
44
45
  withPrefix(["layer", "radio"]),
45
46
  withPrefix(["role", role]),
47
+ withPrefix(["size", props.size]),
46
48
  withPrefix("radio"),
47
49
  variant,
48
50
  {
@@ -1,6 +1,6 @@
1
- <div v-bind="refresherAttrs" v-show="calculateOffset > 0">
2
- <slot name="icon" :offset="calculateOffset" :refreshing="refreshing"></slot>
3
- </div>
4
1
  <div v-bind="containerAttrs" ref="container">
5
- <slot></slot>
2
+ <div v-bind="refresherIconAttrs" v-show="calculateOffset > 0">
3
+ <slot name="icon" :offset="calculateOffset" :refreshing="refreshing"></slot>
4
+ </div>
5
+ <slot :offset="calculateOffset" :refreshing="refreshing"></slot>
6
6
  </div>
@@ -1,8 +1,8 @@
1
1
  @use "@toife/sass-layer" as sass;
2
2
 
3
3
  // Class
4
- $refresher: sass.fn-naming-prefix("refresher");
5
- $container: sass.fn-naming-prefix("refresher-container");
4
+ $refresher-icon: sass.fn-naming-prefix("refresher-icon");
5
+ $container: sass.fn-naming-prefix("refresher");
6
6
 
7
7
  // Variables
8
8
  $safe-area-top: sass.fn-naming-var("safe-area", "top");
@@ -12,12 +12,18 @@ $offset: sass.fn-naming-var("refresher-offset");
12
12
  .#{$container} {
13
13
  width: 100%;
14
14
  height: 100%;
15
- position: relative;
15
+ max-width: 100%;
16
+ max-height: 100%;
17
+ position: absolute;
18
+ top: 0;
19
+ left: 0;
20
+ right: 0;
21
+ bottom: 0;
16
22
  overflow: auto;
17
23
  }
18
24
 
19
25
  // Refresher
20
- .#{$refresher} {
26
+ .#{$refresher-icon} {
21
27
  display: flex;
22
28
  justify-content: center;
23
29
  align-items: center;
@@ -29,13 +29,13 @@ const calculateOffset = computed(() => {
29
29
  return props.offset !== undefined ? props.offset : moveOffset.value / 2;
30
30
  });
31
31
 
32
- const refresherAttrs = computed(() => {
32
+ const refresherIconAttrs = computed(() => {
33
33
  return {
34
34
  style: {
35
35
  [property("refresher-offset")]: `${calculateOffset.value}px`,
36
36
  },
37
37
  class: [
38
- withPrefix("refresher"),
38
+ withPrefix("refresher-icon"),
39
39
  {
40
40
  moving: calculateOffset.value > 0,
41
41
  },
@@ -45,7 +45,7 @@ const refresherAttrs = computed(() => {
45
45
 
46
46
  const containerAttrs = computed(() => {
47
47
  return {
48
- class: [withPrefix("refresher-container")],
48
+ class: [withPrefix("refresher")],
49
49
  };
50
50
  });
51
51
 
@@ -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};
@@ -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,20 @@
1
+ <div v-bind="slideRangeAttrs">
2
+ <div v-bind="trackContainerAttrs" ref="container">
3
+ <div v-bind="trackBodyAttrs">
4
+ <div v-bind="trackBackAttrs" @click="onClickPath" @touchend="onClickPath"></div>
5
+ <div v-bind="trackFrontAttrs" @click="onClickPath" @touchend="onClickPath"></div>
6
+ <div
7
+ v-for="value in ranges"
8
+ :key="value"
9
+ v-bind="nodeAttrs(value)"
10
+ @click="onNodeSelect(value)"
11
+ @touchend="onNodeSelect(value)"
12
+ ></div>
13
+ </div>
14
+
15
+ <div v-bind="thumbAttrs" ref="point">
16
+ <div v-bind="thumbInnerAttrs"></div>
17
+ <span v-if="percent > 0 && isShowTooltip" v-bind="tooltipAttrs">{{ displayValue }}</span>
18
+ </div>
19
+ </div>
20
+ </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: rgb(#{$slide-range-border-color});
107
+ }
108
+
109
+ &.front {
110
+ width: #{$slide-range-percent};
111
+ background-color: rgb(#{$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: rgb(#{$slide-range-background-color});
124
+ border: 1px solid rgb(#{$slide-range-border-color});
125
+
126
+ &.active {
127
+ border-color: rgb(#{$slide-range-border-color-active});
128
+ background-color: rgb(#{$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 rgb(#{$slide-range-border-color-active});
143
+ border-radius: calc(12px * #{$radius-ratio});
144
+ background-color: rgb(#{$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: rgb(#{$slide-range-tooltip-color});
158
+ background-color: rgb(#{$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 { property, withPrefix } 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
+ withPrefix("slide-range"),
78
+ withPrefix(["layer", "slide-range"]),
79
+ withPrefix(["role", role]),
80
+ withPrefix(["shape", shape]),
81
+ {
82
+ disabled: props.disabled,
83
+ readonly: props.readonly,
84
+ },
85
+ ],
86
+ };
87
+ });
88
+
89
+ const trackContainerAttrs = {
90
+ class: [withPrefix("slide-range-track-container")],
91
+ } as const;
92
+
93
+ const trackBodyAttrs = {
94
+ class: [withPrefix("slide-range-track-body")],
95
+ } as const;
96
+
97
+ const trackBackAttrs = {
98
+ class: [withPrefix("slide-range-track"), "back"],
99
+ } as const;
100
+
101
+ const trackFrontAttrs = computed(() => {
102
+ return {
103
+ class: [withPrefix("slide-range-track"), "front"],
104
+ style: {
105
+ [property(["slide-range", "percent"])]: `${percent.value}%`,
106
+ },
107
+ };
108
+ });
109
+
110
+ const thumbAttrs = computed(() => {
111
+ return {
112
+ class: [withPrefix("slide-range-thumb")],
113
+ style: {
114
+ [property(["slide-range", "percent"])]: `${percent.value}%`,
115
+ },
116
+ };
117
+ });
118
+
119
+ const thumbInnerAttrs = {
120
+ class: [withPrefix("slide-range-thumb-inner")],
121
+ } as const;
122
+
123
+ const tooltipAttrs = {
124
+ class: [withPrefix("slide-range-tooltip")],
125
+ } as const;
126
+
127
+ // Methods
128
+ // ----------------------------------------------------------------------------
129
+ const nodeAttrs = (value: number) => {
130
+ const nodePercent = getPercentFromValue(value);
131
+ return {
132
+ class: [withPrefix("slide-range-node"), { active: percent.value > nodePercent }],
133
+ style: {
134
+ [property(["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>
@@ -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,11 @@ $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);
24
25
 
25
26
  .#{$switch-wrapper} {
26
27
  display: inline-flex;
@@ -30,8 +31,8 @@ $spacing-x: sass.fn-naming-var("spacing", "x");
30
31
  box-shadow: none !important;
31
32
 
32
33
  .#{$switch} {
33
- height: 1.5rem;
34
- aspect-ratio: 11/6;
34
+ height: #{$size-control-mark-size};
35
+ aspect-ratio: 14/8;
35
36
  position: relative;
36
37
  transition:
37
38
  background-color #{$transition-duration} ease,
@@ -39,15 +40,15 @@ $spacing-x: sass.fn-naming-var("spacing", "x");
39
40
  border-color #{$transition-duration} ease,
40
41
  border-radius #{$transition-duration} ease,
41
42
  box-shadow #{$transition-duration} ease;
42
- border-radius: #{$border-radius};
43
+ border-radius: calc(min(#{$size-control-mark-size}, #{$radius-size}) * #{$radius-ratio});
43
44
 
44
45
  .#{$switch-icon} {
45
- height: calc(100% - 0.3rem);
46
+ width: #{$icon-size};
47
+ height: #{$icon-size};
46
48
  position: absolute;
47
49
  top: 0;
48
- border-radius: #{$border-radius};
49
- aspect-ratio: 1/1;
50
- margin: 0.15rem;
50
+ border-radius: calc(min(#{$icon-size}, #{$radius-size}) * #{$radius-ratio});
51
+ margin: calc(#{$size-control-mark-size} * 0.1);
51
52
  transition:
52
53
  background-color #{$transition-duration} ease,
53
54
  color #{$transition-duration} ease,
@@ -55,18 +56,6 @@ $spacing-x: sass.fn-naming-var("spacing", "x");
55
56
  border-radius #{$transition-duration} ease,
56
57
  transform #{$transition-duration} ease;
57
58
  }
58
-
59
- &.#{$shape-rounded} {
60
- .#{$switch-icon} {
61
- border-radius: calc(#{$border-radius} - 10%);
62
- }
63
- }
64
-
65
- &.#{$shape-pill} {
66
- .#{$switch-icon} {
67
- border-radius: 50%;
68
- }
69
- }
70
59
  }
71
60
 
72
61
  &.disabled {
@@ -1,7 +1,12 @@
1
+ import { AppSize } from "../app";
2
+
3
+ export type SwitchSize = AppSize;
4
+
1
5
  // Type definitions
2
6
  export type SwitchProps = {
3
7
  modelValue?: boolean;
4
8
  role?: string;
9
+ size?: SwitchSize;
5
10
  shape?: string;
6
11
  disabled?: boolean;
7
12
  readonly?: boolean;