@skyservice-developers/vue-dev-kit 1.0.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.
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "@skyservice-developers/vue-dev-kit",
3
+ "version": "1.0.0",
4
+ "description": "Vue 3 developer toolkit - components and helpers",
5
+ "type": "module",
6
+ "main": "./dist/vue-dev-kit.cjs",
7
+ "module": "./dist/vue-dev-kit.js",
8
+ "types": "./src/types/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": "./dist/vue-dev-kit.js",
12
+ "require": "./dist/vue-dev-kit.cjs",
13
+ "types": "./src/types/index.d.ts"
14
+ },
15
+ "./style.css": "./dist/style.css"
16
+ },
17
+ "files": [
18
+ "dist",
19
+ "src"
20
+ ],
21
+ "scripts": {
22
+ "dev": "vite",
23
+ "build": "vite build",
24
+ "preview": "vite preview",
25
+ "prepublishOnly": "npm run build"
26
+ },
27
+ "peerDependencies": {
28
+ "vue": "^3.4.0"
29
+ },
30
+ "dependencies": {
31
+ "ua-parser-js": "^1.0.37"
32
+ },
33
+ "devDependencies": {
34
+ "@vitejs/plugin-vue": "^5.0.4",
35
+ "vite": "^5.1.4",
36
+ "vue": "^3.4.21"
37
+ },
38
+ "keywords": [
39
+ "vue",
40
+ "vue3",
41
+ "components",
42
+ "toolkit",
43
+ "ui"
44
+ ],
45
+ "author": "Skyservice-POS",
46
+ "license": "MIT",
47
+ "repository": {
48
+ "type": "git",
49
+ "url": "git+https://github.com/Skyservice-POS/vue-dev-kit.git"
50
+ },
51
+ "bugs": {
52
+ "url": "https://github.com/Skyservice-POS/vue-dev-kit/issues"
53
+ },
54
+ "homepage": "https://github.com/Skyservice-POS/vue-dev-kit#readme"
55
+ }
@@ -0,0 +1,101 @@
1
+ <template>
2
+ <component
3
+ :is="dialogComponent"
4
+ v-model="isOpen"
5
+ :title="title"
6
+ :subtitle="subtitle"
7
+ :z-index="zIndex"
8
+ :close-text="closeText"
9
+ :enable-animation="enableAnimation"
10
+ :close-on-esc="closeOnEsc"
11
+ @close="$emit('close')"
12
+ @save="$emit('save')"
13
+ >
14
+ <slot></slot>
15
+ <template #buttons>
16
+ <slot name="buttons"></slot>
17
+ </template>
18
+ </component>
19
+ </template>
20
+
21
+ <script setup>
22
+ import { computed, ref, onMounted } from 'vue'
23
+ import DialogModal from './DialogModal.vue'
24
+ import DialogNext from './DialogNext.vue'
25
+
26
+ const props = defineProps({
27
+ modelValue: {
28
+ type: Boolean,
29
+ default: false
30
+ },
31
+ title: {
32
+ type: String,
33
+ default: ''
34
+ },
35
+ subtitle: {
36
+ type: String,
37
+ default: ''
38
+ },
39
+ zIndex: {
40
+ type: [Number, String],
41
+ default: null
42
+ },
43
+ closeText: {
44
+ type: String,
45
+ default: ''
46
+ },
47
+ enableAnimation: {
48
+ type: Boolean,
49
+ default: true
50
+ },
51
+ closeOnEsc: {
52
+ type: Boolean,
53
+ default: true
54
+ },
55
+ // Force specific mode (overrides URL parameter)
56
+ mode: {
57
+ type: String,
58
+ default: null, // 'next' | 'classic' | null (auto-detect)
59
+ validator: (value) => [null, 'next', 'classic'].includes(value)
60
+ }
61
+ })
62
+
63
+ const emit = defineEmits(['update:modelValue', 'close', 'save'])
64
+
65
+ const isOpen = computed({
66
+ get: () => props.modelValue,
67
+ set: (value) => emit('update:modelValue', value)
68
+ })
69
+
70
+ // Detect rocketMode from URL
71
+ const rocketMode = ref(true) // default to DialogNext
72
+
73
+ onMounted(() => {
74
+ try {
75
+ const urlParams = new URLSearchParams(window.location.search)
76
+ const rocketParam = urlParams.get('rocketMode')
77
+
78
+ if (rocketParam === 'false') {
79
+ rocketMode.value = false
80
+ } else {
81
+ // true or not specified = DialogNext
82
+ rocketMode.value = true
83
+ }
84
+ } catch {
85
+ rocketMode.value = true
86
+ }
87
+ })
88
+
89
+ const dialogComponent = computed(() => {
90
+ // If mode is explicitly set, use it
91
+ if (props.mode === 'next') {
92
+ return DialogNext
93
+ }
94
+ if (props.mode === 'classic') {
95
+ return DialogModal
96
+ }
97
+
98
+ // Auto-detect based on URL parameter
99
+ return rocketMode.value ? DialogNext : DialogModal
100
+ })
101
+ </script>
@@ -0,0 +1,411 @@
1
+ <template>
2
+ <Teleport to="body">
3
+ <transition :name="enableAnimation ? 'dialog-slide' : ''">
4
+ <div
5
+ v-if="modelValue"
6
+ class="sky-dialogbox sky-dialogbox-classic"
7
+ :style="[zIndex ? { 'z-index': zIndex } : null]"
8
+ >
9
+ <div class="sky-dialog-overlay" :class="{ 'sky-dialog-animate': enableAnimation }">
10
+ <div ref="dialogContent" class="sky-dialog-content">
11
+ <!-- Header -->
12
+ <div class="sky-dialog-title" :class="{ 'sky-dialog-title-with-subtitle': subtitle }">
13
+ {{ title }}
14
+ <span v-if="subtitle" class="sky-dialog-subtitle">{{ subtitle }}</span>
15
+ </div>
16
+
17
+ <button class="sky-dialog-close" :title="closeText" @click="close">
18
+ <svg viewBox="0 0 16 16" width="16" height="16">
19
+ <line x1="1" y1="15" x2="15" y2="1" stroke="currentColor" stroke-width="2" />
20
+ <line x1="1" y1="1" x2="15" y2="15" stroke="currentColor" stroke-width="2" />
21
+ </svg>
22
+ </button>
23
+
24
+ <div class="sky-dialog-clearfix" />
25
+
26
+ <!-- Body -->
27
+ <div
28
+ ref="dialogPaper"
29
+ class="sky-dialog-paper"
30
+ @touchstart="handleTouchStart"
31
+ @touchend="handleTouchEnd"
32
+ >
33
+ <!-- iOS swipe back area -->
34
+ <div v-if="isIos" class="sky-dialog-swipe-area" />
35
+ <slot></slot>
36
+ </div>
37
+
38
+ <!-- Footer -->
39
+ <div v-if="$slots.buttons" class="sky-dialog-footer" :class="{ 'sky-dialog-footer-animate': enableAnimation }">
40
+ <slot name="buttons"></slot>
41
+ </div>
42
+ </div>
43
+ </div>
44
+ </div>
45
+ </transition>
46
+ </Teleport>
47
+ </template>
48
+
49
+ <script setup>
50
+ import { ref, computed, watch, onMounted, onUnmounted, nextTick } from 'vue'
51
+ import { isIosWebview, isAndroidWebview } from '../utils/webviewCheck'
52
+
53
+ const props = defineProps({
54
+ modelValue: {
55
+ type: Boolean,
56
+ default: false
57
+ },
58
+ title: {
59
+ type: String,
60
+ default: ''
61
+ },
62
+ subtitle: {
63
+ type: String,
64
+ default: ''
65
+ },
66
+ zIndex: {
67
+ type: [Number, String],
68
+ default: null
69
+ },
70
+ closeText: {
71
+ type: String,
72
+ default: 'Закрити'
73
+ },
74
+ enableAnimation: {
75
+ type: Boolean,
76
+ default: true
77
+ },
78
+ closeOnEsc: {
79
+ type: Boolean,
80
+ default: true
81
+ }
82
+ })
83
+
84
+ const emit = defineEmits(['update:modelValue', 'close', 'save'])
85
+
86
+ const dialogContent = ref(null)
87
+ const dialogPaper = ref(null)
88
+ const touchStartX = ref(0)
89
+
90
+ const isIos = computed(() => {
91
+ try {
92
+ return isIosWebview()
93
+ } catch {
94
+ return false
95
+ }
96
+ })
97
+
98
+ const isAndroid = computed(() => {
99
+ try {
100
+ return isAndroidWebview()
101
+ } catch {
102
+ return false
103
+ }
104
+ })
105
+
106
+ const close = () => {
107
+ emit('update:modelValue', false)
108
+ emit('close')
109
+ }
110
+
111
+ const handleKeydown = (e) => {
112
+ if (e.key === 'Escape' && props.closeOnEsc && props.modelValue) {
113
+ close()
114
+ }
115
+ if (e.key === 'Enter' && props.modelValue) {
116
+ emit('save')
117
+ }
118
+ }
119
+
120
+ // Touch handling for iOS swipe back
121
+ const handleTouchStart = (e) => {
122
+ if (e.touches[0].clientX < 35) {
123
+ touchStartX.value = e.touches[0].clientX
124
+ }
125
+ }
126
+
127
+ const handleTouchEnd = (e) => {
128
+ if (touchStartX.value > 0 && touchStartX.value < 35) {
129
+ const touchEndX = e.changedTouches[0].clientX
130
+ if (touchEndX - touchStartX.value > 50) {
131
+ close()
132
+ }
133
+ }
134
+ touchStartX.value = 0
135
+ }
136
+
137
+ // Android notch fix
138
+ const androidFix = () => {
139
+ if (!isAndroid.value || !dialogContent.value) return
140
+
141
+ try {
142
+ if (typeof Android !== 'undefined' && Android.getDisplayCutoutTop) {
143
+ const cutoutTop = Android.getDisplayCutoutTop()
144
+ if (cutoutTop && window.devicePixelRatio > 1.0) {
145
+ const paddingTop = cutoutTop / window.devicePixelRatio
146
+ dialogContent.value.style.paddingTop = paddingTop + 'px'
147
+ }
148
+ }
149
+ } catch (err) {
150
+ // Android interface not available
151
+ }
152
+ }
153
+
154
+ // Body scroll lock
155
+ watch(() => props.modelValue, (value) => {
156
+ if (value) {
157
+ document.body.style.overflow = 'hidden'
158
+ nextTick(() => {
159
+ androidFix()
160
+ })
161
+ } else {
162
+ document.body.style.overflow = ''
163
+ }
164
+ })
165
+
166
+ onMounted(() => {
167
+ document.addEventListener('keydown', handleKeydown)
168
+ window.addEventListener('resize', androidFix)
169
+ })
170
+
171
+ onUnmounted(() => {
172
+ document.removeEventListener('keydown', handleKeydown)
173
+ window.removeEventListener('resize', androidFix)
174
+ document.body.style.overflow = ''
175
+ })
176
+ </script>
177
+
178
+ <style>
179
+ /* Global styles (не scoped через баг з Teleport) */
180
+ .sky-dialogbox-classic {
181
+ display: block;
182
+ position: fixed;
183
+ padding: 0;
184
+ top: 0;
185
+ left: 0;
186
+ width: 100%;
187
+ height: 100%;
188
+ z-index: var(--sky-dialog-z-index, 9998);
189
+ background: rgba(0, 0, 0, 0.6);
190
+ backdrop-filter: blur(2px);
191
+ }
192
+ </style>
193
+
194
+ <style scoped>
195
+ .sky-dialog-overlay {
196
+ display: flex;
197
+ justify-content: center;
198
+ align-items: center;
199
+ position: fixed;
200
+ padding: 0;
201
+ top: 0;
202
+ left: 0;
203
+ width: 100%;
204
+ height: 100%;
205
+ z-index: 9999;
206
+ }
207
+
208
+ .sky-dialog-content {
209
+ background: var(--sky-dialog-bg, white);
210
+ width: 100%;
211
+ height: 100%;
212
+ border-radius: var(--sky-dialog-radius, 5px);
213
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3), 0 1px 2px rgba(0, 0, 0, 0.24);
214
+ }
215
+
216
+ .sky-dialog-title {
217
+ max-width: calc(100% - 80px);
218
+ font-size: var(--sky-dialog-title-size, 13pt);
219
+ padding: 24px;
220
+ padding-right: 0;
221
+ float: left;
222
+ white-space: nowrap;
223
+ overflow: hidden;
224
+ text-overflow: ellipsis;
225
+ color: var(--sky-dialog-title-color, #252525);
226
+ }
227
+
228
+ .sky-dialog-subtitle {
229
+ display: block;
230
+ font-size: var(--sky-dialog-subtitle-size, 12pt);
231
+ line-height: 24px;
232
+ color: var(--sky-dialog-subtitle-color, #6c757d);
233
+ white-space: nowrap;
234
+ overflow: hidden;
235
+ text-overflow: ellipsis;
236
+ }
237
+
238
+ .sky-dialog-close {
239
+ cursor: pointer;
240
+ font-size: 16pt;
241
+ margin: 15px;
242
+ padding: 17px;
243
+ float: right;
244
+ border-radius: 50%;
245
+ width: 50px;
246
+ height: 50px;
247
+ background: transparent;
248
+ border: none;
249
+ display: flex;
250
+ align-items: center;
251
+ justify-content: center;
252
+ color: var(--sky-dialog-close-color, #333);
253
+ transition: background-color 0.2s;
254
+ }
255
+
256
+ .sky-dialog-close:hover {
257
+ background-color: var(--sky-dialog-close-hover-bg, #f0f0f0);
258
+ }
259
+
260
+ .sky-dialog-clearfix {
261
+ clear: both;
262
+ }
263
+
264
+ .sky-dialog-paper {
265
+ height: 100%;
266
+ overflow-y: auto;
267
+ overflow-x: hidden;
268
+ -webkit-overflow-scrolling: touch;
269
+ position: relative;
270
+ }
271
+
272
+ .sky-dialog-swipe-area {
273
+ position: absolute;
274
+ width: 35px;
275
+ height: 100%;
276
+ left: 0;
277
+ }
278
+
279
+ .sky-dialog-footer {
280
+ padding: 5px 10px;
281
+ display: flex;
282
+ justify-content: center;
283
+ width: 100%;
284
+ transform: translateY(-52px);
285
+ gap: 10px;
286
+ }
287
+
288
+ /* Кнопки в футері: 1 = 100%, 2 = по 50% */
289
+ .sky-dialog-footer > :deep(*) {
290
+ flex: 1;
291
+ min-width: 0;
292
+ }
293
+
294
+ .sky-dialog-footer:has(> :deep(*:only-child)) > :deep(*) {
295
+ max-width: 100%;
296
+ }
297
+
298
+ .sky-dialog-footer:has(> :deep(*:nth-child(2)):not(:has(> :deep(*:nth-child(3))))) > :deep(*) {
299
+ flex: 1 1 50%;
300
+ }
301
+
302
+ /* Desktop */
303
+ @media only screen and (min-width: 1400px) {
304
+ .sky-dialog-content {
305
+ width: 75%;
306
+ margin: 0 auto;
307
+ }
308
+ }
309
+
310
+ /* Tablet and Desktop */
311
+ @media screen and (min-width: 710px) {
312
+ .sky-dialog-paper {
313
+ height: calc(100% - 150px);
314
+ max-height: calc(100% - 150px);
315
+ background-color: #fff;
316
+ margin: 0 10px 60px 10px;
317
+ border-radius: 5px;
318
+ }
319
+
320
+ .sky-dialogbox,
321
+ .sky-dialog-overlay {
322
+ padding: 10px;
323
+ }
324
+ }
325
+
326
+ /* Mobile */
327
+ @media screen and (max-width: 709px) {
328
+ .sky-dialog-paper {
329
+ height: calc(100% - 142px);
330
+ max-height: calc(100% - 142px);
331
+ background-color: #fff;
332
+ margin: 0 10px 10px 10px;
333
+ border-radius: 5px;
334
+ max-width: 100vw !important;
335
+ }
336
+
337
+ .sky-dialog-footer {
338
+ transform: translateY(-6px);
339
+ }
340
+ }
341
+
342
+ @media screen and (max-width: 500px) {
343
+ .sky-dialog-subtitle {
344
+ display: block;
345
+ white-space: nowrap;
346
+ overflow: hidden;
347
+ text-overflow: ellipsis;
348
+ }
349
+
350
+ .sky-dialog-title-with-subtitle {
351
+ padding: 12px 24px !important;
352
+ }
353
+ }
354
+
355
+ @media screen and (max-width: 374px) {
356
+ .sky-dialog-subtitle {
357
+ font-size: 9pt;
358
+ }
359
+ }
360
+
361
+ /* iPhone safe area support */
362
+ @supports (padding-top: env(safe-area-inset-top)) {
363
+ .sky-dialog-paper {
364
+ height: calc(100% - 150px - env(safe-area-inset-top));
365
+ }
366
+
367
+ .sky-dialog-footer {
368
+ padding-bottom: calc(env(safe-area-inset-bottom) + 8px);
369
+ }
370
+ }
371
+
372
+ /* Animations */
373
+ .sky-dialog-animate {
374
+ animation: sky-dialog-slide-in 0.4s ease-in-out;
375
+ }
376
+
377
+ .sky-dialog-footer-animate {
378
+ animation: sky-dialog-footer-in 0.4s ease-in-out;
379
+ }
380
+
381
+ @keyframes sky-dialog-slide-in {
382
+ 0% {
383
+ opacity: 0;
384
+ margin-top: -1600px;
385
+ }
386
+ 100% {
387
+ opacity: 1;
388
+ margin-top: 0;
389
+ }
390
+ }
391
+
392
+ @keyframes sky-dialog-footer-in {
393
+ 0% {
394
+ opacity: 0;
395
+ bottom: -100px;
396
+ }
397
+ 50% {
398
+ opacity: 0.25;
399
+ bottom: -50px;
400
+ }
401
+ 100% {
402
+ opacity: 1;
403
+ bottom: 15px;
404
+ }
405
+ }
406
+
407
+ /* Transition */
408
+ .dialog-slide-leave-active {
409
+ animation: sky-dialog-slide-in 0.4s reverse;
410
+ }
411
+ </style>