create-nexwire 4.2.4 → 4.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/README.md +20 -8
  2. package/express/.env +1 -1
  3. package/express/.vscode/extensions.json +6 -0
  4. package/express/.vscode/settings.json +37 -26
  5. package/express/biome.json +99 -66
  6. package/express/package.json +31 -29
  7. package/express/src/config/database.ts +48 -29
  8. package/express/src/config/index.ts +83 -83
  9. package/express/src/config/openapi.ts +6 -24
  10. package/express/src/config/queue.ts +30 -30
  11. package/express/src/config/validate.ts +50 -40
  12. package/express/src/database/migrations/sqlite/0000_init.sql +47 -47
  13. package/express/src/database/migrations/sqlite/meta/0000_snapshot.json +1 -1
  14. package/express/src/database/migrations/sqlite/meta/_journal.json +1 -1
  15. package/express/src/database/schema.ts +2 -2
  16. package/express/src/framework/cache/cache.ts +40 -8
  17. package/express/src/framework/circuit-breaker/cb.ts +88 -60
  18. package/express/src/framework/database/connection.ts +52 -27
  19. package/express/src/framework/events/dispatcher.ts +61 -49
  20. package/express/src/framework/facade.ts +46 -3
  21. package/express/src/framework/http/app.ts +28 -22
  22. package/express/src/framework/http/logger.ts +27 -16
  23. package/express/src/framework/http/openapi.ts +9 -11
  24. package/express/src/framework/modules/routes.ts +26 -21
  25. package/express/src/framework/notification/channels/broadcast.ts +15 -0
  26. package/express/src/framework/notification/channels/mail.ts +40 -0
  27. package/express/src/framework/notification/contracts.ts +29 -0
  28. package/express/src/framework/notification/index.ts +19 -94
  29. package/express/src/framework/notification/registry.ts +15 -0
  30. package/express/src/framework/providers/adapter.ts +27 -17
  31. package/express/src/framework/queue/queue.ts +43 -44
  32. package/express/src/framework/realtime/broadcast.ts +29 -5
  33. package/express/src/framework/realtime/socket.ts +149 -119
  34. package/express/src/framework/redis/client.ts +148 -151
  35. package/express/src/framework/runtime/server-adapter.ts +39 -34
  36. package/express/src/framework/scheduler/lock.ts +128 -116
  37. package/express/src/framework/server.ts +33 -49
  38. package/express/src/framework/session/session.ts +69 -14
  39. package/express/src/framework/storage/storage.ts +744 -785
  40. package/express/src/framework/support/mail.ts +15 -7
  41. package/express/src/providers/notification.ts +4 -0
  42. package/express/src/resources/src/components/Checkbox.vue +32 -29
  43. package/express/src/resources/src/components/Input.vue +243 -223
  44. package/express/src/resources/src/components/InputGroup.vue +147 -145
  45. package/express/src/resources/src/components/InputPasswordToggle.vue +229 -213
  46. package/express/src/resources/src/components/Modal.vue +71 -55
  47. package/express/src/resources/src/components/Refresh.vue +19 -0
  48. package/express/src/resources/src/helpers/assets.ts +51 -0
  49. package/express/src/resources/src/layouts/Layout/Header.vue +124 -105
  50. package/express/src/resources/src/main.ts +3 -1
  51. package/express/src/resources/src/plugins/gum.ts +1 -1
  52. package/express/src/resources/src/plugins/pulse.ts +157 -147
  53. package/express/src/resources/src/stores/auth.ts +184 -164
  54. package/express/src/resources/vite.config.ts +54 -36
  55. package/express/src/storage/database/nexwire.sqlite +0 -0
  56. package/express/src/storage/logs/app.log +0 -0
  57. package/express/src/storage/logs/fatal.log +0 -0
  58. package/express/src/types/global.d.ts +32 -26
  59. package/hono/.env +1 -1
  60. package/hono/.vscode/extensions.json +6 -0
  61. package/hono/.vscode/settings.json +37 -26
  62. package/hono/biome.json +25 -2
  63. package/hono/package.json +25 -23
  64. package/hono/src/config/database.ts +48 -29
  65. package/hono/src/config/index.ts +83 -83
  66. package/hono/src/config/openapi.ts +6 -24
  67. package/hono/src/config/queue.ts +30 -30
  68. package/hono/src/config/validate.ts +50 -40
  69. package/hono/src/database/migrations/sqlite/0000_init.sql +47 -47
  70. package/hono/src/database/migrations/sqlite/meta/0000_snapshot.json +1 -1
  71. package/hono/src/database/migrations/sqlite/meta/_journal.json +1 -1
  72. package/hono/src/database/schema.ts +2 -2
  73. package/hono/src/framework/cache/cache.ts +40 -8
  74. package/hono/src/framework/circuit-breaker/cb.ts +88 -60
  75. package/hono/src/framework/database/connection.ts +52 -27
  76. package/hono/src/framework/events/dispatcher.ts +61 -49
  77. package/hono/src/framework/facade.ts +38 -1
  78. package/hono/src/framework/http/openapi.ts +7 -9
  79. package/hono/src/framework/notification/channels/broadcast.ts +15 -0
  80. package/hono/src/framework/notification/channels/mail.ts +40 -0
  81. package/hono/src/framework/notification/contracts.ts +29 -0
  82. package/hono/src/framework/notification/index.ts +19 -94
  83. package/hono/src/framework/notification/registry.ts +15 -0
  84. package/hono/src/framework/providers/adapter.ts +27 -17
  85. package/hono/src/framework/queue/queue.ts +43 -44
  86. package/hono/src/framework/realtime/broadcast.ts +29 -5
  87. package/hono/src/framework/realtime/socket.ts +217 -196
  88. package/hono/src/framework/redis/client.ts +148 -151
  89. package/hono/src/framework/scheduler/lock.ts +128 -116
  90. package/hono/src/framework/server.ts +27 -29
  91. package/hono/src/framework/session/session.ts +62 -14
  92. package/hono/src/framework/storage/storage.ts +744 -785
  93. package/hono/src/framework/support/mail.ts +56 -48
  94. package/hono/src/providers/notification.ts +4 -0
  95. package/hono/src/resources/src/components/Checkbox.vue +32 -29
  96. package/hono/src/resources/src/components/Input.vue +243 -234
  97. package/hono/src/resources/src/components/InputGroup.vue +147 -149
  98. package/hono/src/resources/src/components/InputPasswordToggle.vue +229 -222
  99. package/hono/src/resources/src/components/Modal.vue +71 -55
  100. package/hono/src/resources/src/components/Refresh.vue +19 -0
  101. package/hono/src/resources/src/helpers/assets.ts +51 -0
  102. package/hono/src/resources/src/layouts/Layout/Header.vue +124 -105
  103. package/hono/src/resources/src/main.ts +3 -1
  104. package/hono/src/resources/src/plugins/gum.ts +1 -1
  105. package/hono/src/resources/src/plugins/pulse.ts +157 -147
  106. package/hono/src/resources/src/stores/auth.ts +184 -164
  107. package/hono/src/resources/vite.config.ts +54 -36
  108. package/hono/src/storage/database/nexwire.sqlite +0 -0
  109. package/hono/src/storage/logs/app.log +0 -0
  110. package/hono/src/storage/logs/fatal.log +0 -0
  111. package/hono/src/types/global.d.ts +32 -26
  112. package/package.json +2 -2
  113. package/src/index.js +139 -14
@@ -1,222 +1,229 @@
1
- <template>
2
- <div :class="topclass">
3
- <div class="position-relative">
4
- <label :for="inputId" class="d-flex align-items-center" :class="{ 'd-none': !inputLabel }">
5
- <div class="position-relative">
6
- <span class="text-capitalize mb-1" v-html="inputLabel"></span>
7
- <span
8
- class="position-absolute text-danger rounded-circle bg-danger must"
9
- :class="{ 'd-none': !props.must }"></span>
10
- </div>
11
- <div
12
- class="text-end ms-auto text-primary align-self-center"
13
- :class="{ 'd-none': !props.hood }"
14
- style="font-size: 12px; margin-top: 0.15rem"
15
- v-html="hoodHtml"></div>
16
- </label>
17
-
18
- <div class="input-group mb-3">
19
- <input
20
- ref="inputRef"
21
- class="form-control border-0"
22
- :type="showPassword ? 'text' : 'password'"
23
- :value="props.modelValue"
24
- :maxlength="maxLengthAttr"
25
- :readonly="props.readonly"
26
- v-bind="$attrs"
27
- @input="updateModel" />
28
- <button
29
- class="input-group-text p-0 border-0 border-start border-1 position-relative z-1"
30
- type="button"
31
- @click="togglePassword">
32
- <i
33
- class="py-2 px-3"
34
- :class="showPassword ? 'bi bi-eye' : 'bi bi-eye-slash'"
35
- style="font-size: 15px"></i>
36
- </button>
37
- </div>
38
- <!-- <input class="form-control" v-model="model" :maxlength="max" v-bind="$attrs"> vue3.3 (2nd way)/3.4 -->
39
- <div
40
- class="position-absolute bottom-0 end-0 pe-1 text-secondary max"
41
- :class="{ 'd-none': !max }">
42
- {{ maxCounter }}
43
- </div>
44
- </div>
45
- <div
46
- :id="`${inputId}Help`"
47
- class="form-text mt-1 text-danger"
48
- :class="{ 'd-none': !props.err }">
49
- {{ props.err }}
50
- </div>
51
- </div>
52
- </template>
53
-
54
- <script setup lang="ts">
55
- import { computed, nextTick, onMounted, ref, useAttrs } from "vue";
56
- import { browserDetect } from "@/plugins/browserDetect";
57
-
58
- defineOptions({ name: "InputPasswordToggle", inheritAttrs: false });
59
- const { isFirefox } = browserDetect;
60
-
61
- const $attrs = useAttrs();
62
- const inputRef = ref<HTMLInputElement | null>(null);
63
- type InputPasswordCategory =
64
- | "number"
65
- | "decimal"
66
- | "mobile"
67
- | "tax-period"
68
- | "bin"
69
- | "challan-number"
70
- | string;
71
-
72
- interface InputPasswordProps {
73
- focus?: boolean;
74
- must?: boolean;
75
- err?: string | boolean;
76
- modelValue?: string | number;
77
- category?: InputPasswordCategory;
78
- hood?: string | boolean | number;
79
- readonly?: boolean;
80
- }
81
-
82
- const props = withDefaults(defineProps<InputPasswordProps>(), {
83
- err: false,
84
- modelValue: ""
85
- });
86
-
87
- const topclass = computed(() => ($attrs.topclass as string | undefined) || "mb-3");
88
- const inputId = computed(() => ($attrs.id as string | undefined) || "");
89
- const inputLabel = computed(() => ($attrs.label as string | undefined) || "");
90
- const inputType = computed(() => ($attrs.type as string | undefined) || "text");
91
-
92
- const max = computed<number | null>(() => {
93
- const raw = $attrs.maxlength as string | number | undefined;
94
- return raw != null ? Number(raw) : null;
95
- });
96
- const maxLengthAttr = computed<number | undefined>(() => max.value ?? undefined);
97
-
98
- const hoodHtml = computed(() =>
99
- props.hood === false || props.hood == null ? "" : String(props.hood)
100
- );
101
-
102
- const maxCounter = computed(() => {
103
- if (max.value == null) {
104
- return "";
105
- }
106
-
107
- if (!isFirefox && inputType.value === "month") {
108
- return "";
109
- }
110
-
111
- return max.value - String(props.modelValue).length;
112
- });
113
-
114
- const emit = defineEmits<(event: "update:modelValue", value: string | number) => void>();
115
- const updateModel = (e: Event) => {
116
- const target = e.target as HTMLInputElement;
117
-
118
- if (props.category === "number") {
119
- emit("update:modelValue", (target.value = target.value.replace(/[^\d]/gi, "")));
120
- } else if (props.category === "decimal") {
121
- let val = target.value.replace(/[^\d.]/g, "");
122
- const parts = val.split(".");
123
- val = parts[0];
124
- if (parts.length > 1) {
125
- val += `.${parts.slice(1).join("")}`;
126
- }
127
- emit("update:modelValue", (target.value = val));
128
- } else if (props.category === "mobile") {
129
- let digits = target.value.replace(/[^\d]/g, ""); // only digits
130
- let val = digits;
131
- if (target.value.includes("+")) {
132
- val = `+${digits}`;
133
- }
134
- emit("update:modelValue", (target.value = val));
135
- } else if (props.category === "tax-period") {
136
- let val = target.value.replace(/[^\d-]/g, ""); // Only numbers and hyphen
137
- val = val.replace(/-/g, ""); // Remove all hyphens
138
- val = val.slice(0, 6); // Only 6 digits (YYYYMM)
139
-
140
- if (val.length > 4) {
141
- let year = val.slice(0, 4);
142
- let month = val.slice(4, 6);
143
-
144
- // Only correct if both month digits entered
145
- if (month.length === 2) {
146
- let m = parseInt(month, 10);
147
- // Clamp month between 1 and 12
148
- if (m < 1) m = 1;
149
- if (m > 12) m = 12;
150
- // Always pad to 2 digits
151
- month = m.toString().padStart(2, "0");
152
- }
153
-
154
- val = `${year}-${month}`;
155
- }
156
-
157
- emit("update:modelValue", (target.value = val));
158
- } else if (props.category === "bin") {
159
- let digits = target.value.replace(/[^\d]/g, "");
160
- digits = digits.slice(0, 14);
161
- let formatted = digits;
162
- if (digits.length > 9) {
163
- formatted = `${digits.slice(0, 9)}-${digits.slice(9, 14)}`;
164
- }
165
- emit("update:modelValue", (target.value = formatted));
166
- } else if (props.category === "challan-number") {
167
- let digits = target.value.replace(/[^\d]/g, "");
168
- digits = digits.slice(0, 16);
169
- let formatted = digits;
170
- if (digits.length > 4) {
171
- formatted = `${digits.slice(0, 4)}-${digits.slice(4, 16)}`;
172
- }
173
- emit("update:modelValue", (target.value = formatted));
174
- } else {
175
- emit("update:modelValue", target.value);
176
- }
177
- };
178
-
179
- const showPassword = ref(false);
180
- const togglePassword = () => (showPassword.value = !showPassword.value);
181
-
182
- onMounted(() => {
183
- const shouldFocus = props.focus !== false && props.focus !== undefined;
184
- if (!shouldFocus) return;
185
-
186
- nextTick(() => inputRef.value?.focus());
187
- });
188
- </script>
189
-
190
- <style lang="scss" scoped>
191
- input {
192
- border-color: #cccccc;
193
-
194
- &:read-only {
195
- background-color: #f8f8f8;
196
- }
197
-
198
- &::placeholder {
199
- color: #bfbfbf;
200
- }
201
- }
202
-
203
- button:focus-visible {
204
- border: none;
205
- box-shadow: none;
206
- }
207
-
208
- .form-text {
209
- font-size: 0.75rem;
210
- }
211
-
212
- .must {
213
- width: 4px;
214
- height: 4px;
215
- margin-left: 2px;
216
- margin-top: 5px;
217
- }
218
-
219
- .max {
220
- font-size: 0.6rem;
221
- }
222
- </style>
1
+ <template>
2
+ <div :class="topclass">
3
+ <div class="position-relative">
4
+ <label :for="inputId" class="d-flex align-items-center" :class="{ 'd-none': !inputLabel }">
5
+ <div class="position-relative">
6
+ <span class="text-capitalize mb-1" v-html="inputLabel"></span>
7
+ <span
8
+ class="position-absolute text-danger rounded-circle bg-danger must"
9
+ :class="{ 'd-none': !props.must }"
10
+ ></span>
11
+ </div>
12
+ <div
13
+ class="text-end ms-auto text-primary align-self-center"
14
+ :class="{ 'd-none': !props.hood }"
15
+ style="font-size: 12px; margin-top: 0.15rem"
16
+ v-html="hoodHtml"
17
+ ></div>
18
+ </label>
19
+
20
+ <div class="input-group border-0 mb-3">
21
+ <input
22
+ ref="inputRef"
23
+ class="form-control shadow-none"
24
+ :type="showPassword ? 'text' : 'password'"
25
+ :value="props.modelValue"
26
+ :maxlength="maxLengthAttr"
27
+ :readonly="props.readonly"
28
+ v-bind="$attrs"
29
+ @input="updateModel"
30
+ />
31
+ <button
32
+ class="input-group-text p-0 position-relative z-1"
33
+ type="button"
34
+ @click="togglePassword"
35
+ >
36
+ <i
37
+ class="py-2 px-3"
38
+ :class="showPassword ? 'bi bi-eye' : 'bi bi-eye-slash'"
39
+ style="font-size: 15px"
40
+ ></i>
41
+ </button>
42
+ </div>
43
+ <!-- <input class="form-control" v-model="model" :maxlength="max" v-bind="$attrs"> vue3.3 (2nd way)/3.4 -->
44
+ <div
45
+ class="position-absolute bottom-0 end-0 pe-1 text-secondary max"
46
+ :class="{ 'd-none': !max }"
47
+ >
48
+ {{ maxCounter }}
49
+ </div>
50
+ </div>
51
+ <div
52
+ :id="`${inputId}Help`"
53
+ class="form-text mt-1 text-danger"
54
+ :class="{ 'd-none': !props.err }"
55
+ >
56
+ {{ props.err }}
57
+ </div>
58
+ </div>
59
+ </template>
60
+
61
+ <script setup lang="ts">
62
+ import { computed, nextTick, onMounted, ref, useAttrs } from "vue";
63
+ import { browserDetect } from "@/plugins/browserDetect";
64
+
65
+ defineOptions({ name: "InputPasswordToggle", inheritAttrs: false });
66
+ const { isFirefox } = browserDetect;
67
+
68
+ const $attrs = useAttrs();
69
+ const inputRef = ref<HTMLInputElement | null>(null);
70
+ type InputPasswordCategory =
71
+ | "number"
72
+ | "decimal"
73
+ | "mobile"
74
+ | "tax-period"
75
+ | "bin"
76
+ | "challan-number"
77
+ | string;
78
+
79
+ interface InputPasswordProps {
80
+ focus?: boolean;
81
+ must?: boolean;
82
+ err?: string | boolean;
83
+ modelValue?: string | number;
84
+ category?: InputPasswordCategory;
85
+ hood?: string | boolean | number;
86
+ readonly?: boolean;
87
+ }
88
+
89
+ const props = withDefaults(defineProps<InputPasswordProps>(), {
90
+ err: false,
91
+ modelValue: "",
92
+ });
93
+
94
+ const topclass = computed(() => ($attrs.topclass as string | undefined) || "mb-3");
95
+ const inputId = computed(() => ($attrs.id as string | undefined) || "");
96
+ const inputLabel = computed(() => ($attrs.label as string | undefined) || "");
97
+ const inputType = computed(() => ($attrs.type as string | undefined) || "text");
98
+
99
+ const max = computed<number | null>(() => {
100
+ const raw = $attrs.maxlength as string | number | undefined;
101
+ return raw != null ? Number(raw) : null;
102
+ });
103
+ const maxLengthAttr = computed<number | undefined>(() => max.value ?? undefined);
104
+
105
+ const hoodHtml = computed(() =>
106
+ props.hood === false || props.hood == null ? "" : String(props.hood),
107
+ );
108
+
109
+ const maxCounter = computed(() => {
110
+ if (max.value == null) {
111
+ return "";
112
+ }
113
+
114
+ if (!isFirefox && inputType.value === "month") {
115
+ return "";
116
+ }
117
+
118
+ return max.value - String(props.modelValue).length;
119
+ });
120
+
121
+ const emit = defineEmits<(event: "update:modelValue", value: string | number) => void>();
122
+ const updateModel = (e: Event) => {
123
+ const target = e.target as HTMLInputElement;
124
+
125
+ if (props.category === "number") {
126
+ emit("update:modelValue", (target.value = target.value.replace(/[^\d]/gi, "")));
127
+ } else if (props.category === "decimal") {
128
+ let val = target.value.replace(/[^\d.]/g, "");
129
+ const parts = val.split(".");
130
+ val = parts[0];
131
+ if (parts.length > 1) {
132
+ val += `.${parts.slice(1).join("")}`;
133
+ }
134
+ emit("update:modelValue", (target.value = val));
135
+ } else if (props.category === "mobile") {
136
+ let digits = target.value.replace(/[^\d]/g, ""); // only digits
137
+ let val = digits;
138
+ if (target.value.includes("+")) {
139
+ val = `+${digits}`;
140
+ }
141
+ emit("update:modelValue", (target.value = val));
142
+ } else if (props.category === "tax-period") {
143
+ let val = target.value.replace(/[^\d-]/g, ""); // Only numbers and hyphen
144
+ val = val.replace(/-/g, ""); // Remove all hyphens
145
+ val = val.slice(0, 6); // Only 6 digits (YYYYMM)
146
+
147
+ if (val.length > 4) {
148
+ let year = val.slice(0, 4);
149
+ let month = val.slice(4, 6);
150
+
151
+ // Only correct if both month digits entered
152
+ if (month.length === 2) {
153
+ let m = parseInt(month, 10);
154
+ // Clamp month between 1 and 12
155
+ if (m < 1) m = 1;
156
+ if (m > 12) m = 12;
157
+ // Always pad to 2 digits
158
+ month = m.toString().padStart(2, "0");
159
+ }
160
+
161
+ val = `${year}-${month}`;
162
+ }
163
+
164
+ emit("update:modelValue", (target.value = val));
165
+ } else if (props.category === "bin") {
166
+ let digits = target.value.replace(/[^\d]/g, "");
167
+ digits = digits.slice(0, 14);
168
+ let formatted = digits;
169
+ if (digits.length > 9) {
170
+ formatted = `${digits.slice(0, 9)}-${digits.slice(9, 14)}`;
171
+ }
172
+ emit("update:modelValue", (target.value = formatted));
173
+ } else if (props.category === "challan-number") {
174
+ let digits = target.value.replace(/[^\d]/g, "");
175
+ digits = digits.slice(0, 16);
176
+ let formatted = digits;
177
+ if (digits.length > 4) {
178
+ formatted = `${digits.slice(0, 4)}-${digits.slice(4, 16)}`;
179
+ }
180
+ emit("update:modelValue", (target.value = formatted));
181
+ } else {
182
+ emit("update:modelValue", target.value);
183
+ }
184
+ };
185
+
186
+ const showPassword = ref(false);
187
+ const togglePassword = () => (showPassword.value = !showPassword.value);
188
+
189
+ onMounted(async () => {
190
+ const shouldFocus = props.focus !== false && props.focus !== undefined;
191
+ if (!shouldFocus) return;
192
+
193
+ await nextTick();
194
+ // preventScroll: focusing inside a modal must not scroll the modal/page
195
+ inputRef.value?.focus({ preventScroll: true });
196
+ });
197
+ </script>
198
+
199
+ <style lang="scss" scoped>
200
+ input {
201
+ &:read-only {
202
+ background-color: #f8f8f8;
203
+ }
204
+
205
+ &::placeholder {
206
+ color: #bfbfbf;
207
+ }
208
+ }
209
+
210
+ button:focus-visible {
211
+ border: none;
212
+ box-shadow: none;
213
+ }
214
+
215
+ .form-text {
216
+ font-size: 0.75rem;
217
+ }
218
+
219
+ .must {
220
+ width: 4px;
221
+ height: 4px;
222
+ margin-left: 2px;
223
+ margin-top: 5px;
224
+ }
225
+
226
+ .max {
227
+ font-size: 0.6rem;
228
+ }
229
+ </style>
@@ -1,55 +1,71 @@
1
- <template>
2
- <Teleport to="#modal-show">
3
- <div :id="props.id" ref="modal" class="modal fade" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1"
4
- :aria-labelledby="`${props.id}Label`">
5
- <div :class="`modal-dialog modal-dialog-centered modal-dialog-scrollable modal-${props.size}`">
6
- <div class="modal-content border">
7
- <div class="modal-header">
8
- <div class="d-flex w-100 justify-content-between align-items-center">
9
- <h5 id="modalScrollableTitle" class="modal-title me-3">{{ props.title }}</h5>
10
- <button ref="closeButton" type="button" class="btn-close shadow-none" data-bs-dismiss="modal"
11
- aria-label="Close"></button>
12
- <slot name="modal-header-extra"></slot>
13
- </div>
14
- </div>
15
- <div class="modal-body">
16
- <slot name="modalbody"></slot>
17
- </div>
18
- <div class="modal-footer py-2">
19
- <slot name="modalfooter"></slot>
20
- </div>
21
- </div>
22
- </div>
23
- </div>
24
- </Teleport>
25
- </template>
26
-
27
- <script setup lang="ts">
28
- import { ref } from "vue";
29
-
30
- type ModalSize = "sm" | "md" | "lg" | "xl" | string;
31
-
32
- interface ModalProps {
33
- id: string;
34
- title?: string;
35
- size?: ModalSize;
36
- }
37
-
38
- defineOptions({ name: "Modal", inheritAttrs: false });
39
-
40
- const props = withDefaults(defineProps<ModalProps>(), {
41
- title: "",
42
- size: "md"
43
- });
44
-
45
- const modal = ref<HTMLElement | null>(null);
46
- const closeButton = ref<HTMLButtonElement | null>(null);
47
-
48
- const close = () => {
49
- closeButton.value?.click();
50
- };
51
-
52
- defineExpose({ close });
53
- </script>
54
-
55
- <style lang="scss" scoped></style>
1
+ <template>
2
+ <Teleport to="#modal-show">
3
+ <div
4
+ :id="props.id"
5
+ ref="modal"
6
+ class="modal fade"
7
+ data-bs-backdrop="static"
8
+ data-bs-keyboard="false"
9
+ tabindex="-1"
10
+ :aria-labelledby="`${props.id}Label`"
11
+ >
12
+ <div
13
+ :class="`modal-dialog modal-dialog-centered modal-dialog-scrollable modal-${props.size}`"
14
+ >
15
+ <div class="modal-content border">
16
+ <div class="modal-header">
17
+ <div class="d-flex w-100 justify-content-between align-items-center">
18
+ <h5 id="modalScrollableTitle" class="modal-title me-3">{{ props.title }}</h5>
19
+ <button
20
+ ref="closeButton"
21
+ type="button"
22
+ class="btn-close shadow-none"
23
+ data-bs-dismiss="modal"
24
+ aria-label="Close"
25
+ ></button>
26
+ <slot name="modal-header-extra"></slot>
27
+ </div>
28
+ </div>
29
+ <div class="modal-body">
30
+ <slot name="modalbody"></slot>
31
+ </div>
32
+ <div class="modal-footer py-2">
33
+ <slot name="modalfooter"></slot>
34
+ </div>
35
+ </div>
36
+ </div>
37
+ </div>
38
+ </Teleport>
39
+ </template>
40
+
41
+ <script setup lang="ts">
42
+ import { Modal } from "bootstrap";
43
+ import { onMounted, ref } from "vue";
44
+
45
+ interface ModalProps {
46
+ id: string;
47
+ title?: string;
48
+ size?: string;
49
+ }
50
+
51
+ const props = withDefaults(defineProps<ModalProps>(), {
52
+ title: "",
53
+ size: "md"
54
+ });
55
+
56
+ const modal = ref<HTMLElement | null>(null);
57
+ let bsModal: Modal | null = null;
58
+
59
+ onMounted(() => {
60
+ if (modal.value) {
61
+ bsModal = new Modal(modal.value);
62
+ }
63
+ });
64
+
65
+ const open = () => bsModal?.show();
66
+ const close = () => bsModal?.hide();
67
+
68
+ defineExpose({ open, close });
69
+ </script>
70
+
71
+ <style lang="scss" scoped></style>
@@ -0,0 +1,19 @@
1
+ <template>
2
+ <Teleport v-if="target" :to="target">
3
+ <slot />
4
+ </Teleport>
5
+ </template>
6
+
7
+ <script setup>
8
+ import { onMounted, ref } from "vue";
9
+
10
+ defineOptions({ name: "Refresh", inheritAttrs: false });
11
+
12
+ const target = ref(null);
13
+ onMounted(() => {
14
+ // grab the header container once it's in the DOM
15
+ target.value = document.getElementById("refresh");
16
+ });
17
+ </script>
18
+
19
+ <style lang="scss" scoped></style>