@toife/vue 3.1.4 → 3.1.6

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 (104) hide show
  1. package/README.md +1 -1
  2. package/package.json +4 -4
  3. package/src/components/action/action.scss +2 -2
  4. package/src/components/action/action.vue +5 -5
  5. package/src/components/app/app.scss +3 -3
  6. package/src/components/app/app.type.ts +0 -6
  7. package/src/components/app/app.vue +2 -8
  8. package/src/components/avatar/avatar.scss +4 -3
  9. package/src/components/avatar/avatar.vue +6 -6
  10. package/src/components/button/button.scss +29 -21
  11. package/src/components/button/button.type.ts +2 -4
  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 +3 -3
  15. package/src/components/card/card/card.vue +5 -5
  16. package/src/components/card/card-body/card-body.scss +3 -3
  17. package/src/components/card/card-body/card-body.vue +2 -2
  18. package/src/components/card/card-footer/card-footer.scss +4 -4
  19. package/src/components/card/card-footer/card-footer.vue +2 -2
  20. package/src/components/card/card-header/card-header.scss +4 -4
  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 +19 -14
  24. package/src/components/checkbox/checkbox.type.ts +1 -3
  25. package/src/components/checkbox/checkbox.vue +7 -7
  26. package/src/components/collapse/collapse.html +1 -1
  27. package/src/components/collapse/collapse.scss +4 -7
  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 +11 -11
  31. package/src/components/decision-modal/decision-modal.vue +8 -8
  32. package/src/components/divider/divider.scss +3 -3
  33. package/src/components/divider/divider.vue +4 -4
  34. package/src/components/dropdown/dropdown.scss +4 -4
  35. package/src/components/dropdown/dropdown.type.ts +2 -2
  36. package/src/components/dropdown/dropdown.vue +7 -9
  37. package/src/components/field/field.html +28 -9
  38. package/src/components/field/{outline/outline.scss → field.scss} +29 -125
  39. package/src/components/field/field.type.ts +4 -4
  40. package/src/components/field/field.vue +83 -46
  41. package/src/components/field/index.ts +1 -1
  42. package/src/components/form-group/form-group.vue +2 -2
  43. package/src/components/gesture-indicator/gesture-indicator.scss +2 -2
  44. package/src/components/gesture-indicator/gesture-indicator.vue +4 -4
  45. package/src/components/image/image.vue +12 -5
  46. package/src/components/layout/flex/flex.vue +8 -8
  47. package/src/components/layout/flex-item/flex-item.vue +6 -6
  48. package/src/components/layout/grid/grid.vue +6 -6
  49. package/src/components/layout/grid-item/grid-item.vue +6 -6
  50. package/src/components/modal/modal.scss +2 -2
  51. package/src/components/modal/modal.vue +68 -5
  52. package/src/components/page/page.vue +2 -2
  53. package/src/components/present/present.scss +4 -4
  54. package/src/components/present/present.vue +14 -14
  55. package/src/components/radio/radio/radio.html +1 -1
  56. package/src/components/radio/radio/radio.scss +19 -14
  57. package/src/components/radio/radio/radio.type.ts +1 -3
  58. package/src/components/radio/radio/radio.vue +6 -6
  59. package/src/components/radio/radio-group/radio-group.vue +2 -2
  60. package/src/components/refresher/refresher.html +0 -3
  61. package/src/components/refresher/refresher.scss +1 -25
  62. package/src/components/refresher/refresher.vue +2 -16
  63. package/src/components/route/route-navigator/route-navigator.html +1 -1
  64. package/src/components/route/route-navigator/route-navigator.scss +3 -3
  65. package/src/components/route/route-navigator/route-navigator.vue +11 -14
  66. package/src/components/route/route-wrapper/route-wrapper.composable.ts +5 -15
  67. package/src/components/route/route-wrapper/route-wrapper.type.ts +0 -4
  68. package/src/components/route/route-wrapper/route-wrapper.vue +4 -12
  69. package/src/components/route/route.type.ts +0 -1
  70. package/src/components/segmented-field/segmented-field.html +1 -1
  71. package/src/components/segmented-field/segmented-field.scss +3 -3
  72. package/src/components/segmented-field/segmented-field.vue +8 -8
  73. package/src/components/select/select.html +2 -2
  74. package/src/components/select/select.scss +11 -11
  75. package/src/components/select/select.vue +10 -10
  76. package/src/components/skeleton/skeleton.scss +1 -1
  77. package/src/components/skeleton/skeleton.vue +7 -7
  78. package/src/components/slide-range/slide-range.html +3 -4
  79. package/src/components/slide-range/slide-range.scss +17 -14
  80. package/src/components/slide-range/slide-range.vue +17 -16
  81. package/src/components/switch/switch.html +1 -1
  82. package/src/components/switch/switch.scss +23 -21
  83. package/src/components/switch/switch.type.ts +2 -3
  84. package/src/components/switch/switch.vue +23 -9
  85. package/src/components/tabs/tab/tab.html +1 -1
  86. package/src/components/tabs/tab/tab.scss +13 -0
  87. package/src/components/tabs/tab/tab.vue +4 -2
  88. package/src/components/tabs/tabs/index.ts +1 -0
  89. package/src/components/tabs/tabs/tabs.scss +82 -54
  90. package/src/components/tabs/tabs/tabs.type.ts +4 -1
  91. package/src/components/tabs/tabs/tabs.vue +47 -23
  92. package/src/components/toast/toast/toast.scss +1 -1
  93. package/src/components/toast/toast/toast.vue +2 -2
  94. package/src/components/toast/toast-content/toast-content.scss +4 -4
  95. package/src/components/toast/toast-content/toast-content.vue +5 -5
  96. package/src/components/toolbar/toolbar.scss +4 -4
  97. package/src/components/toolbar/toolbar.vue +5 -5
  98. package/src/factory.ts +105 -51
  99. package/src/type.ts +2 -1
  100. package/src/utils/style/index.ts +9 -9
  101. package/src/utils/style.md +9 -9
  102. package/src/components/field/outline/index.ts +0 -1
  103. package/src/components/field/outline/outline.html +0 -36
  104. package/src/components/field/outline/outline.vue +0 -286
@@ -1,13 +1,13 @@
1
- import type { AppDirection, AppSize } from "../app";
1
+ import type { AppDirection } from "../app";
2
2
 
3
3
  export type FieldVariant = "outline" | "fill" | "underline";
4
- export type FieldSize = AppSize;
4
+ export type FieldSize = string;
5
5
  export type FieldType = "text" | "number" | "email" | "password" | "tel" | "url" | "paragraph";
6
6
 
7
7
  // Type definitions
8
8
  export type FieldProps = {
9
9
  // Wrapper
10
- modelValue?: string;
10
+ modelValue?: string | number;
11
11
  name?: string;
12
12
  variant?: FieldVariant;
13
13
  role?: string;
@@ -18,7 +18,7 @@ export type FieldProps = {
18
18
 
19
19
  // Input
20
20
  id?: string;
21
- value?: string;
21
+ value?: string | number;
22
22
  placeholder?: string;
23
23
  disabled?: boolean;
24
24
  readonly?: boolean;
@@ -1,9 +1,9 @@
1
+ <style lang="scss" src="./field.scss" scoped></style>
1
2
  <template src="./field.html"></template>
2
3
  <script lang="ts" setup>
3
- import { computed, inject } from "vue";
4
- import type { FieldProps } from "./field.type";
5
- import type { FieldEmit } from "./field.type";
6
- import { OutlineField } from "./outline";
4
+ import { computed, inject, nextTick, onMounted, ref, watch } from "vue";
5
+ import type { FieldProps, FieldEmit } from "./field.type";
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)
@@ -12,6 +12,8 @@ const props = withDefaults(defineProps<FieldProps>(), {
12
12
  modelValue: "",
13
13
  type: "text",
14
14
  size: "standard",
15
+ role: undefined,
16
+ shape: undefined,
15
17
  disabled: false,
16
18
  readonly: false,
17
19
  message: "",
@@ -21,66 +23,101 @@ const props = withDefaults(defineProps<FieldProps>(), {
21
23
  shadow: undefined,
22
24
  direction: undefined,
23
25
  line: 1,
26
+ maxLine: 1,
24
27
  });
25
28
  const emit = defineEmits<FieldEmit>();
26
29
  const appState = inject<AppProviderState>(APP_PROVIDER_STATE_KEY);
27
30
 
31
+ // Reactive state
32
+ // ----------------------------------------------------------------------------
33
+ const isFocus = ref(false);
34
+ const caret = ref(0);
35
+ const content = ref("");
36
+
28
37
  // Computed properties
29
38
  // ----------------------------------------------------------------------------
30
- const role = computed(() => {
31
- return props.role || appState?.role.value || "";
32
- });
39
+ const fieldAttrs = computed(() => {
40
+ const role = props.role || appState?.role?.value || "";
41
+ const shape = props.shape || appState?.shape?.value || "";
42
+ const shadow = (props?.shadow !== undefined ? props.shadow : appState?.shadow?.value) ?? false;
43
+ const direction = props.direction || appState?.direction?.value || "left";
33
44
 
34
- const shape = computed(() => {
35
- return props.shape || appState?.shape.value || "";
45
+ return {
46
+ class: [
47
+ cssPrefix(["layer", "field"]),
48
+ cssPrefix(["role", role]),
49
+ cssPrefix(["shape", shape]),
50
+ cssPrefix("field"),
51
+ cssPrefix(["size", props.size]),
52
+ cssPrefix(["direction", direction]),
53
+ props.variant,
54
+ props.type,
55
+ {
56
+ disabled: props.disabled,
57
+ focus: isFocus.value,
58
+ shadow,
59
+ readonly: props.readonly,
60
+ },
61
+ ],
62
+ style: {
63
+ [cssProperty(["field", "line"])]: props.line,
64
+ [cssProperty(["field", "max-line"])]: props.maxLine || props.line,
65
+ },
66
+ };
36
67
  });
37
68
 
38
- const shadow = computed(() => {
39
- return (props?.shadow !== undefined ? props.shadow : appState?.shadow.value) ?? false;
69
+ const rawContent = computed(() => {
70
+ return props.modelValue ?? props.value ?? "";
40
71
  });
41
72
 
42
- const direction = computed(() => {
43
- return props.direction || appState?.direction?.value || "left";
44
- });
73
+ const fieldContentAttrs = computed(() => ({
74
+ class: [cssPrefix("field-content")],
75
+ }));
45
76
 
46
- const fieldAttrs = computed(() => {
47
- return {
48
- ...props,
49
- direction: direction.value,
50
- role: role.value,
51
- shape: shape.value,
52
- shadow: shadow.value,
53
- onInput,
54
- onFocus,
55
- onBlur,
56
- onBeforeinput,
57
- };
58
- });
77
+ const fieldInputAttrs = computed(() => ({
78
+ class: [cssPrefix("field-input")],
79
+ name: props.name,
80
+ id: props.id,
81
+ placeholder: props.placeholder,
82
+ autocomplete: props.autocomplete,
83
+ type: props.type,
84
+ readonly: props.readonly,
85
+ disabled: props.disabled,
86
+ }));
87
+
88
+ const fieldMessageAttrs = computed(() => ({
89
+ class: [cssPrefix("field-message")],
90
+ }));
91
+
92
+ const fieldHelpAttrs = computed(() => ({
93
+ class: [cssPrefix("field-help")],
94
+ }));
59
95
 
60
96
  // Methods
61
97
  // ----------------------------------------------------------------------------
62
- const onInput = (event: Event) => {
63
- let raw = "";
64
- // Outline variant uses contenteditable for non-password types; password uses a real input
65
- if (props.type !== "password") {
66
- raw = (event.target as HTMLElement).innerText;
67
- } else {
68
- raw = (event.target as HTMLInputElement).value;
69
- }
70
- const value = raw.trim() === "" ? "" : raw;
71
- emit("update:modelValue", value as string);
72
- emit("input", event);
98
+ const onFocus = (ev: FocusEvent) => {
99
+ if (props.disabled) return;
100
+ isFocus.value = true;
101
+ emit("focus", ev);
73
102
  };
74
103
 
75
- const onFocus = (event: FocusEvent) => {
76
- emit("focus", event);
104
+ const onBlur = (ev: FocusEvent) => {
105
+ if (props.disabled) return;
106
+ isFocus.value = false;
107
+ emit("blur", ev);
77
108
  };
78
109
 
79
- const onBlur = (event: FocusEvent) => {
80
- emit("blur", event);
81
- };
110
+ watch(
111
+ () => rawContent.value,
112
+ (value) => {
113
+ content.value = value.toString();
114
+ }
115
+ );
82
116
 
83
- const onBeforeinput = (event: Event) => {
84
- emit("beforeinput", event);
85
- };
117
+ watch(
118
+ () => content.value,
119
+ (value) => {
120
+ emit("update:modelValue", value.toString());
121
+ }
122
+ );
86
123
  </script>
@@ -1,2 +1,2 @@
1
1
  export { default as Field } from "./field.vue";
2
- export type { FieldVariant, FieldSize, FieldProps, FieldEmit } from "./field.type";
2
+ export * from "./field.type";
@@ -1,7 +1,7 @@
1
1
  <style lang="scss" src="./form-group.scss" scoped></style>
2
2
  <template src="./form-group.html"></template>
3
3
  <script lang="ts" setup>
4
- import { withPrefix } from "../../utils";
4
+ import { cssPrefix } from "../../utils";
5
5
  import { computed } from "vue";
6
6
  import { type FormGroupProps } from "./form-group.type";
7
7
 
@@ -15,7 +15,7 @@ const props = withDefaults(defineProps<FormGroupProps>(), {
15
15
  // ----------------------------------------------------------------------------
16
16
  const formGroupAttrs = computed(() => {
17
17
  return {
18
- class: [withPrefix("form-group"), props.direction],
18
+ class: [cssPrefix("form-group"), props.direction],
19
19
  };
20
20
  });
21
21
  </script>
@@ -7,10 +7,10 @@ $gesture-indicator: sass.fn-naming-prefix("gesture-indicator");
7
7
  $gesture-indicator-border-color: sass.fn-naming-var("gesture-indicator", "border-color");
8
8
  $radius-ratio: sass.fn-naming-var("radius-ratio");
9
9
  $radius-size: sass.fn-naming-var("radius-size");
10
- $transition-duration: sass.fn-naming-var("motion", "duration");
10
+ $transition-duration: sass.fn-naming-var("duration");
11
11
 
12
12
  .#{$gesture-indicator} {
13
- background-color: rgb(#{$gesture-indicator-border-color});
13
+ background-color: rgba(#{$gesture-indicator-border-color});
14
14
  transition:
15
15
  box-shadow #{$transition-duration} ease,
16
16
  border-color #{$transition-duration} ease,
@@ -1,7 +1,7 @@
1
1
  <style lang="scss" src="./gesture-indicator.scss" scoped></style>
2
2
  <template src="./gesture-indicator.html"></template>
3
3
  <script lang="ts" setup>
4
- import { withPrefix } from "../../utils";
4
+ import { cssPrefix } from "../../utils";
5
5
  import { type GestureIndicatorProps } from "./gesture-indicator.type";
6
6
  import { computed, inject } from "vue";
7
7
  import { type AppProviderState, APP_PROVIDER_STATE_KEY } from "../app";
@@ -21,9 +21,9 @@ const gestureIndicatorAttrs = computed(() => {
21
21
 
22
22
  return {
23
23
  class: [
24
- withPrefix(["layer", "gesture-indicator"]),
25
- withPrefix(["role", role]),
26
- withPrefix("gesture-indicator"),
24
+ cssPrefix(["layer", "gesture-indicator"]),
25
+ cssPrefix(["role", role]),
26
+ cssPrefix("gesture-indicator"),
27
27
  props.placement,
28
28
  ],
29
29
  };
@@ -1,22 +1,29 @@
1
1
  <template src="./image.html"></template>
2
2
  <script setup lang="ts">
3
- import { ref } from "vue";
3
+ import { ref, watch } from "vue";
4
4
  import { type ImageProps } from "./image.type";
5
5
 
6
6
  // Component setup (props, emits, injects)
7
7
  // ----------------------------------------------------------------------------
8
- const { defaultSrc, src } = defineProps<ImageProps>();
8
+ const props = defineProps<ImageProps>();
9
9
 
10
10
  // Reactive state
11
11
  // ----------------------------------------------------------------------------
12
12
  // Initialize from src to avoid an empty src flash before mount
13
- const dataSrc = ref<string>(src ?? "");
13
+ const dataSrc = ref<string>(props.src ?? "");
14
14
 
15
15
  // Methods
16
16
  // ----------------------------------------------------------------------------
17
17
  const handleError = () => {
18
- if (defaultSrc) {
19
- dataSrc.value = defaultSrc;
18
+ if (props.defaultSrc) {
19
+ dataSrc.value = props.defaultSrc;
20
20
  }
21
21
  };
22
+
23
+ watch(
24
+ () => props.src,
25
+ (newVal) => {
26
+ dataSrc.value = newVal ?? "";
27
+ }
28
+ );
22
29
  </script>
@@ -2,7 +2,7 @@
2
2
  <template src="./flex.html"></template>
3
3
  <script lang="ts" setup>
4
4
  import { computed } from "vue";
5
- import { withPrefix, property } from "../../../utils";
5
+ import { cssPrefix, cssProperty } from "../../../utils";
6
6
  import type { FlexProps } from "./flex.type";
7
7
 
8
8
  // Component setup (props, emits, injects)
@@ -16,18 +16,18 @@ const props = withDefaults(defineProps<FlexProps>(), {
16
16
  const flexAttrs = computed(() => {
17
17
  const style = props.options.map((option) => {
18
18
  return {
19
- [property(["flex", "gap", option?.breakpoint || ""])]:
19
+ [cssProperty(["flex", "gap", option?.breakpoint || ""])]:
20
20
  typeof option.gap === "number" ? `${option.gap}px` : option.gap,
21
- [property(["flex", "direction", option?.breakpoint || ""])]: option.direction,
22
- [property(["flex", "wrap", option?.breakpoint || ""])]: option.wrap,
23
- [property(["flex", "justify", option?.breakpoint || ""])]: option.justify,
24
- [property(["flex", "align", option?.breakpoint || ""])]: option.align,
25
- [property(["flex", "align-content", option?.breakpoint || ""])]: option.alignContent,
21
+ [cssProperty(["flex", "direction", option?.breakpoint || ""])]: option.direction,
22
+ [cssProperty(["flex", "wrap", option?.breakpoint || ""])]: option.wrap,
23
+ [cssProperty(["flex", "justify", option?.breakpoint || ""])]: option.justify,
24
+ [cssProperty(["flex", "align", option?.breakpoint || ""])]: option.align,
25
+ [cssProperty(["flex", "align-content", option?.breakpoint || ""])]: option.alignContent,
26
26
  };
27
27
  });
28
28
 
29
29
  return {
30
- class: [withPrefix("flex")],
30
+ class: [cssPrefix("flex")],
31
31
  style,
32
32
  };
33
33
  });
@@ -2,7 +2,7 @@
2
2
  <template src="./flex-item.html"></template>
3
3
  <script lang="ts" setup>
4
4
  import { computed } from "vue";
5
- import { withPrefix, property } from "../../../utils";
5
+ import { cssPrefix, cssProperty } from "../../../utils";
6
6
  import type { FlexItemProps } from "./flex-item.type";
7
7
 
8
8
  const props = withDefaults(defineProps<FlexItemProps>(), {
@@ -12,15 +12,15 @@ const props = withDefaults(defineProps<FlexItemProps>(), {
12
12
  const flexItemAttrs = computed(() => {
13
13
  const style = props.options.map((option) => {
14
14
  return {
15
- [property(["flex-item", "grow", option?.breakpoint || ""])]: option.grow,
16
- [property(["flex-item", "shrink", option?.breakpoint || ""])]: option.shrink,
17
- [property(["flex-item", "basis", option?.breakpoint || ""])]: option.basis,
18
- [property(["flex-item", "order", option?.breakpoint || ""])]: option.order,
15
+ [cssProperty(["flex-item", "grow", option?.breakpoint || ""])]: option.grow,
16
+ [cssProperty(["flex-item", "shrink", option?.breakpoint || ""])]: option.shrink,
17
+ [cssProperty(["flex-item", "basis", option?.breakpoint || ""])]: option.basis,
18
+ [cssProperty(["flex-item", "order", option?.breakpoint || ""])]: option.order,
19
19
  };
20
20
  });
21
21
 
22
22
  return {
23
- class: [withPrefix("flex-item")],
23
+ class: [cssPrefix("flex-item")],
24
24
  style,
25
25
  };
26
26
  });
@@ -2,7 +2,7 @@
2
2
  <template src="./grid.html"></template>
3
3
  <script lang="ts" setup>
4
4
  import { computed } from "vue";
5
- import { withPrefix, property } from "../../../utils";
5
+ import { cssPrefix, cssProperty } from "../../../utils";
6
6
  import type { GridProps } from "./grid.type";
7
7
 
8
8
  // Component setup (props, emits, injects)
@@ -16,16 +16,16 @@ const props = withDefaults(defineProps<GridProps>(), {
16
16
  const gridAttrs = computed(() => {
17
17
  const style = props.options.map((option) => {
18
18
  return {
19
- [property(["grid", "gap", option?.breakpoint || ""])]:
19
+ [cssProperty(["grid", "gap", option?.breakpoint || ""])]:
20
20
  typeof option.gap === "number" ? `${option.gap}px` : option.gap,
21
- [property(["grid", "columns", option?.breakpoint || ""])]: option.columns,
22
- [property(["grid", "rows", option?.breakpoint || ""])]: option.rows,
23
- [property(["grid", "auto-flow", option?.breakpoint || ""])]: option.autoFlow,
21
+ [cssProperty(["grid", "columns", option?.breakpoint || ""])]: option.columns,
22
+ [cssProperty(["grid", "rows", option?.breakpoint || ""])]: option.rows,
23
+ [cssProperty(["grid", "auto-flow", option?.breakpoint || ""])]: option.autoFlow,
24
24
  };
25
25
  });
26
26
 
27
27
  return {
28
- class: [withPrefix("grid")],
28
+ class: [cssPrefix("grid")],
29
29
  style,
30
30
  };
31
31
  });
@@ -2,7 +2,7 @@
2
2
  <template src="./grid-item.html"></template>
3
3
  <script lang="ts" setup>
4
4
  import { computed } from "vue";
5
- import { withPrefix, property } from "../../../utils";
5
+ import { cssPrefix, cssProperty } from "../../../utils";
6
6
  import type { GridItemProps } from "./grid-item.type";
7
7
 
8
8
  const props = withDefaults(defineProps<GridItemProps>(), {
@@ -12,15 +12,15 @@ const props = withDefaults(defineProps<GridItemProps>(), {
12
12
  const gridItemAttrs = computed(() => {
13
13
  const style = props.options.map((option) => {
14
14
  return {
15
- [property(["grid-item", "row", option?.breakpoint || ""])]: option.row,
16
- [property(["grid-item", "column", option?.breakpoint || ""])]: option.column,
17
- [property(["grid-item", "justify", option?.breakpoint || ""])]: option.justify,
18
- [property(["grid-item", "align", option?.breakpoint || ""])]: option.align,
15
+ [cssProperty(["grid-item", "row", option?.breakpoint || ""])]: option.row,
16
+ [cssProperty(["grid-item", "column", option?.breakpoint || ""])]: option.column,
17
+ [cssProperty(["grid-item", "justify", option?.breakpoint || ""])]: option.justify,
18
+ [cssProperty(["grid-item", "align", option?.breakpoint || ""])]: option.align,
19
19
  };
20
20
  });
21
21
 
22
22
  return {
23
- class: [withPrefix("grid-item")],
23
+ class: [cssPrefix("grid-item")],
24
24
  style,
25
25
  };
26
26
  });
@@ -15,11 +15,11 @@ $safe-area-top: sass.fn-naming-var("safe-area", "top");
15
15
  $safe-area-bottom: sass.fn-naming-var("safe-area", "bottom");
16
16
  $safe-area-left: sass.fn-naming-var("safe-area", "left");
17
17
  $safe-area-right: sass.fn-naming-var("safe-area", "right");
18
- $transition-duration: sass.fn-naming-var("motion", "duration");
18
+ $transition-duration: sass.fn-naming-var("duration");
19
19
  $border-radius: calc(min(#{$viewport-width}, #{$radius-size}) * #{$radius-ratio});
20
20
 
21
21
  .#{$modal} {
22
- background: rgb(#{$modal-background-color});
22
+ background: rgba(#{$modal-background-color});
23
23
  transition:
24
24
  box-shadow #{$transition-duration} ease,
25
25
  border-color #{$transition-duration} ease,
@@ -6,7 +6,7 @@ import { gesture as toifeGesture } from "@toife/gesture";
6
6
  import { Present } from "../present";
7
7
  import { GestureIndicator } from "../gesture-indicator";
8
8
  import { type ModalProps, type ModalEmit } from "./modal.type";
9
- import { withPrefix } from "../../utils";
9
+ import { cssPrefix } from "../../utils";
10
10
  import { type AppProviderState, APP_PROVIDER_STATE_KEY } from "../app";
11
11
 
12
12
  // Component setup (props, emits, injects)
@@ -47,10 +47,10 @@ const modalAttrs = computed(() => {
47
47
 
48
48
  return {
49
49
  class: [
50
- withPrefix(["layer", "modal"]),
51
- withPrefix(["role", role]),
52
- withPrefix(["shape", shape]),
53
- withPrefix("modal"),
50
+ cssPrefix(["layer", "modal"]),
51
+ cssPrefix(["role", role]),
52
+ cssPrefix(["shape", shape]),
53
+ cssPrefix("modal"),
54
54
  {
55
55
  fullscreen: props.fullscreen,
56
56
  [props.placement]: true,
@@ -67,6 +67,65 @@ const close = (e: string) => {
67
67
  emit("close", e);
68
68
  };
69
69
 
70
+ const SCROLLABLE_OVERFLOW_VALUES = ["auto", "scroll", "overlay"];
71
+
72
+ /**
73
+ * Check if the element is scrollable
74
+ * @param el - The element
75
+ * @param axis - The axis of the scroll
76
+ * @returns True if the element is scrollable, false otherwise
77
+ */
78
+ const isScrollable = (el: HTMLElement, axis: "x" | "y") => {
79
+ const style = getComputedStyle(el);
80
+ if (axis == "y") {
81
+ return (
82
+ el.scrollHeight > el.clientHeight && SCROLLABLE_OVERFLOW_VALUES.includes(style.overflowY)
83
+ );
84
+ }
85
+
86
+ return el.scrollWidth > el.clientWidth && SCROLLABLE_OVERFLOW_VALUES.includes(style.overflowX);
87
+ };
88
+
89
+ /**
90
+ * Check if the element has remaining scroll
91
+ * @param el - The element
92
+ * @param direction - The direction of the scroll
93
+ * @returns True if the element has remaining scroll, false otherwise
94
+ */
95
+ const hasRemainingScroll = (el: HTMLElement, direction: "up" | "down" | "left" | "right") => {
96
+ if (direction == "down") return el.scrollTop > 0;
97
+ if (direction == "up") return el.scrollTop < el.scrollHeight - el.clientHeight;
98
+ if (direction == "right") return el.scrollLeft > 0;
99
+
100
+ return el.scrollLeft < el.scrollWidth - el.clientWidth;
101
+ };
102
+
103
+ /**
104
+ * Check if the gesture can start
105
+ * @param e - The event
106
+ * @returns True if the gesture can start, false otherwise
107
+ */
108
+ const canStartGesture = (e: unknown) => {
109
+ if (!modal.value || !gestureDir.value) return true;
110
+
111
+ const target = (e as { target?: EventTarget }).target;
112
+ if (!(target instanceof Element)) return true;
113
+
114
+ const axis = gestureDir.value == "left" || gestureDir.value == "right" ? "x" : "y";
115
+ let current: Element | null = target;
116
+
117
+ while (current && current !== modal.value) {
118
+ if (current instanceof HTMLElement && isScrollable(current, axis)) {
119
+ if (hasRemainingScroll(current, gestureDir.value)) {
120
+ return false;
121
+ }
122
+ }
123
+ current = current.parentElement;
124
+ }
125
+
126
+ return true;
127
+ };
128
+
70
129
  // Cooldown so rapid gesture events do not fight the sheet animation
71
130
  const busy = () => {
72
131
  isBusy.value = true;
@@ -94,6 +153,10 @@ watch(
94
153
  return false;
95
154
  }
96
155
 
156
+ if (!canStartGesture(e)) {
157
+ return false;
158
+ }
159
+
97
160
  return true;
98
161
  },
99
162
 
@@ -1,10 +1,10 @@
1
1
  <template src="./page.html"></template>
2
2
  <style lang="scss" src="./page.scss" scoped></style>
3
3
  <script lang="ts" setup>
4
- import { withPrefix } from "../../utils";
4
+ import { cssPrefix } from "../../utils";
5
5
 
6
6
  // Top-level page surface inside the app shell
7
7
  const pageAttrs = {
8
- class: [withPrefix("page")],
8
+ class: [cssPrefix("page")],
9
9
  } as const;
10
10
  </script>
@@ -7,7 +7,7 @@ $backdrop: sass.fn-naming-prefix("present-backdrop");
7
7
  // Property name
8
8
  $transition-duration: sass.fn-naming-dvar(
9
9
  ("transition", "duration"),
10
- sass.fn-naming-var("motion", "duration")
10
+ sass.fn-naming-var("duration")
11
11
  );
12
12
 
13
13
  $backdrop-background-color: sass.fn-naming-var("backdrop", "background-color");
@@ -18,7 +18,7 @@ $present-translate-minus: sass.fn-naming-dvar(("translate"), "-100%");
18
18
  $present-opacity: sass.fn-naming-dvar(("present", "opacity"), 1);
19
19
 
20
20
  .#{$backdrop} {
21
- position: absolute;
21
+ position: fixed;
22
22
  width: 100%;
23
23
  height: 100%;
24
24
  top: 0;
@@ -30,7 +30,7 @@ $present-opacity: sass.fn-naming-dvar(("present", "opacity"), 1);
30
30
  color #{$transition-duration} ease,
31
31
  border-radius #{$transition-duration} ease,
32
32
  opacity #{$transition-duration} ease;
33
- background-color: rgb(#{$backdrop-background-color});
33
+ background-color: rgba(#{$backdrop-background-color});
34
34
  opacity: #{$backdrop-opacity};
35
35
  }
36
36
 
@@ -38,7 +38,7 @@ $present-opacity: sass.fn-naming-dvar(("present", "opacity"), 1);
38
38
  display: flex;
39
39
  justify-content: center;
40
40
  align-items: center;
41
- position: absolute;
41
+ position: fixed;
42
42
  width: fit-content;
43
43
  height: fit-content;
44
44
  transition: transform #{$transition-duration} ease;
@@ -3,7 +3,7 @@
3
3
  <script lang="ts" setup>
4
4
  import type { PresentEmit, PresentProps, RenderOptions } from "./present.type";
5
5
  import { computed, onMounted, reactive, ref, watch } from "vue";
6
- import { withPrefix, property } from "../../utils";
6
+ import { cssPrefix, cssProperty } from "../../utils";
7
7
  import { usePresent } from "./present.composable";
8
8
 
9
9
  // Component setup (props, emits, injects)
@@ -28,7 +28,7 @@ const zIndex = ref(0);
28
28
  const isShow = ref(false);
29
29
  const styles = reactive({
30
30
  backdropTransitionDuration: "0.2s",
31
- backdropOpacity: 0.4,
31
+ backdropOpacity: undefined as number | undefined,
32
32
  presentTransitionDuration: "0.2s",
33
33
  presentTranslate: "0px",
34
34
  presentOpacity: 1,
@@ -38,11 +38,11 @@ const styles = reactive({
38
38
  // ----------------------------------------------------------------------------
39
39
  const backdropAttrs = computed(() => {
40
40
  return {
41
- class: [withPrefix(["layer", "backdrop"]), withPrefix("present-backdrop")],
41
+ class: [cssPrefix(["layer", "backdrop"]), cssPrefix("present-backdrop")],
42
42
  style: {
43
43
  zIndex: zIndex.value - 1,
44
- [property("transition-duration")]: styles.backdropTransitionDuration,
45
- [property(["backdrop", "opacity"])]:
44
+ [cssProperty("transition-duration")]: styles.backdropTransitionDuration,
45
+ [cssProperty(["backdrop", "opacity"])]:
46
46
  props.backdrop === "transparent" ? 0 : styles.backdropOpacity,
47
47
  },
48
48
  };
@@ -51,13 +51,13 @@ const backdropAttrs = computed(() => {
51
51
  // Present attributes
52
52
  const presentAttrs = computed(() => {
53
53
  return {
54
- class: [withPrefix("present"), props.class, props.placement],
54
+ class: [cssPrefix("present"), props.class, props.placement],
55
55
  style: [
56
56
  {
57
57
  zIndex: zIndex.value,
58
- [property("transition-duration")]: styles.presentTransitionDuration,
59
- [property("translate")]: styles.presentTranslate,
60
- [property(["present", "opacity"])]: styles.presentOpacity,
58
+ [cssProperty("transition-duration")]: styles.presentTransitionDuration,
59
+ [cssProperty("translate")]: styles.presentTranslate,
60
+ [cssProperty(["present", "opacity"])]: styles.presentOpacity,
61
61
  },
62
62
  props.style,
63
63
  ],
@@ -87,9 +87,9 @@ const render = (data: RenderOptions) => {
87
87
  if (data.presentTransitionDuration !== undefined) {
88
88
  styles.presentTransitionDuration = data.presentTransitionDuration;
89
89
  }
90
- if (data.backdropOpacity !== undefined) {
91
- styles.backdropOpacity = data.backdropOpacity;
92
- }
90
+
91
+ styles.backdropOpacity = data.backdropOpacity;
92
+
93
93
  if (data.presentTranslate !== undefined) {
94
94
  styles.presentTranslate = data.presentTranslate;
95
95
  }
@@ -115,7 +115,7 @@ const open = () => {
115
115
 
116
116
  render({
117
117
  backdropTransitionDuration: time.value,
118
- backdropOpacity: 0.4,
118
+ backdropOpacity: undefined,
119
119
  presentTranslate: String(presentTranslate),
120
120
  presentTransitionDuration: time.value,
121
121
  presentOpacity: 1,
@@ -128,7 +128,7 @@ const open = () => {
128
128
  }, props.duration);
129
129
  } else {
130
130
  render({
131
- backdropOpacity: 0.4,
131
+ backdropOpacity: undefined,
132
132
  backdropTransitionDuration: time.value,
133
133
  presentTranslate: "0px",
134
134
  presentTransitionDuration: time.value,