@ramathibodi/nuxt-commons 0.1.73 → 0.1.75

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 (111) hide show
  1. package/README.md +115 -96
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +1 -0
  4. package/dist/runtime/components/Alert.vue +58 -54
  5. package/dist/runtime/components/BarcodeReader.vue +130 -122
  6. package/dist/runtime/components/ExportCSV.vue +110 -102
  7. package/dist/runtime/components/FileBtn.vue +79 -67
  8. package/dist/runtime/components/ImportCSV.vue +151 -139
  9. package/dist/runtime/components/MrzReader.vue +168 -0
  10. package/dist/runtime/components/SplitterPanel.vue +67 -59
  11. package/dist/runtime/components/TabsGroup.vue +39 -31
  12. package/dist/runtime/components/TextBarcode.vue +66 -54
  13. package/dist/runtime/components/device/IdCardButton.vue +95 -83
  14. package/dist/runtime/components/device/IdCardWebSocket.vue +207 -195
  15. package/dist/runtime/components/device/Scanner.vue +350 -338
  16. package/dist/runtime/components/dialog/Confirm.vue +112 -100
  17. package/dist/runtime/components/dialog/Host.vue +88 -84
  18. package/dist/runtime/components/dialog/Index.vue +84 -72
  19. package/dist/runtime/components/dialog/Loading.vue +51 -39
  20. package/dist/runtime/components/dialog/default/Confirm.vue +112 -100
  21. package/dist/runtime/components/dialog/default/Loading.vue +60 -48
  22. package/dist/runtime/components/dialog/default/Notify.vue +82 -70
  23. package/dist/runtime/components/dialog/default/Printing.vue +46 -34
  24. package/dist/runtime/components/dialog/default/VerifyUser.vue +144 -132
  25. package/dist/runtime/components/document/Form.vue +50 -42
  26. package/dist/runtime/components/document/TemplateBuilder.vue +536 -524
  27. package/dist/runtime/components/form/ActionPad.vue +156 -144
  28. package/dist/runtime/components/form/Birthdate.vue +116 -104
  29. package/dist/runtime/components/form/CheckboxGroup.vue +99 -87
  30. package/dist/runtime/components/form/CodeEditor.vue +45 -37
  31. package/dist/runtime/components/form/Date.vue +270 -258
  32. package/dist/runtime/components/form/DateTime.vue +220 -208
  33. package/dist/runtime/components/form/Dialog.vue +178 -166
  34. package/dist/runtime/components/form/EditPad.vue +157 -145
  35. package/dist/runtime/components/form/File.vue +295 -283
  36. package/dist/runtime/components/form/Hidden.vue +44 -32
  37. package/dist/runtime/components/form/Iterator.vue +538 -526
  38. package/dist/runtime/components/form/Login.vue +143 -131
  39. package/dist/runtime/components/form/Pad.vue +399 -387
  40. package/dist/runtime/components/form/SignPad.vue +226 -218
  41. package/dist/runtime/components/form/System.vue +34 -26
  42. package/dist/runtime/components/form/Table.vue +391 -379
  43. package/dist/runtime/components/form/TableData.vue +236 -224
  44. package/dist/runtime/components/form/Time.vue +177 -165
  45. package/dist/runtime/components/form/images/Capture.vue +245 -237
  46. package/dist/runtime/components/form/images/Edit.vue +133 -121
  47. package/dist/runtime/components/form/images/Field.vue +331 -320
  48. package/dist/runtime/components/form/images/Pad.vue +54 -42
  49. package/dist/runtime/components/label/Date.vue +37 -29
  50. package/dist/runtime/components/label/DateAgo.vue +102 -94
  51. package/dist/runtime/components/label/DateCount.vue +152 -144
  52. package/dist/runtime/components/label/Field.vue +111 -103
  53. package/dist/runtime/components/label/FormatMoney.vue +37 -29
  54. package/dist/runtime/components/label/Mask.vue +46 -38
  55. package/dist/runtime/components/label/Object.vue +21 -13
  56. package/dist/runtime/components/master/Autocomplete.vue +89 -81
  57. package/dist/runtime/components/master/Combobox.vue +88 -80
  58. package/dist/runtime/components/master/RadioGroup.vue +90 -78
  59. package/dist/runtime/components/master/Select.vue +70 -62
  60. package/dist/runtime/components/master/label.vue +55 -47
  61. package/dist/runtime/components/model/Autocomplete.vue +91 -79
  62. package/dist/runtime/components/model/Combobox.vue +90 -78
  63. package/dist/runtime/components/model/Pad.vue +114 -102
  64. package/dist/runtime/components/model/Select.vue +78 -72
  65. package/dist/runtime/components/model/Table.vue +370 -358
  66. package/dist/runtime/components/model/iterator.vue +497 -489
  67. package/dist/runtime/components/model/label.vue +58 -50
  68. package/dist/runtime/components/pdf/Print.vue +75 -63
  69. package/dist/runtime/components/pdf/View.vue +146 -134
  70. package/dist/runtime/composables/alert.d.ts +4 -0
  71. package/dist/runtime/composables/api.d.ts +4 -0
  72. package/dist/runtime/composables/dialog.d.ts +1 -1
  73. package/dist/runtime/composables/document/templateFormHidden.d.ts +4 -0
  74. package/dist/runtime/composables/graphql.d.ts +1 -1
  75. package/dist/runtime/composables/graphqlModel.d.ts +9 -9
  76. package/dist/runtime/composables/graphqlModelItem.d.ts +7 -7
  77. package/dist/runtime/composables/graphqlModelOperation.d.ts +6 -6
  78. package/dist/runtime/composables/localStorageModel.d.ts +4 -0
  79. package/dist/runtime/composables/lookupList.d.ts +4 -0
  80. package/dist/runtime/composables/menu.d.ts +4 -0
  81. package/dist/runtime/composables/useMrzReader.d.ts +48 -0
  82. package/dist/runtime/composables/useMrzReader.js +423 -0
  83. package/dist/runtime/composables/useTesseract.d.ts +16 -0
  84. package/dist/runtime/composables/useTesseract.js +45 -0
  85. package/dist/runtime/composables/userPermission.d.ts +1 -1
  86. package/dist/runtime/labs/Calendar.vue +99 -99
  87. package/dist/runtime/labs/form/EditMobile.vue +152 -152
  88. package/dist/runtime/labs/form/TextFieldMask.vue +43 -43
  89. package/dist/runtime/plugins/clientConfig.d.ts +1 -1
  90. package/dist/runtime/plugins/default.d.ts +1 -1
  91. package/dist/runtime/plugins/dialogManager.d.ts +1 -1
  92. package/dist/runtime/plugins/permission.d.ts +1 -1
  93. package/dist/runtime/types/alert.d.ts +11 -11
  94. package/dist/runtime/types/clientConfig.d.ts +13 -13
  95. package/dist/runtime/types/dialogManager.d.ts +35 -35
  96. package/dist/runtime/types/formDialog.d.ts +5 -5
  97. package/dist/runtime/types/graphqlOperation.d.ts +23 -23
  98. package/dist/runtime/types/menu.d.ts +31 -31
  99. package/dist/runtime/types/modules.d.ts +7 -7
  100. package/dist/runtime/types/permission.d.ts +13 -13
  101. package/dist/runtime/utils/asset.d.ts +2 -0
  102. package/dist/runtime/utils/asset.js +49 -0
  103. package/package.json +131 -122
  104. package/scripts/enrich-vue-docs-from-ai.mjs +197 -0
  105. package/scripts/generate-ai-summary.mjs +321 -0
  106. package/scripts/generate-composables-md.mjs +129 -0
  107. package/scripts/postInstall.cjs +70 -70
  108. package/templates/.codegen/codegen.ts +32 -32
  109. package/templates/.codegen/plugin-schema-object.js +161 -161
  110. package/templates/public/tesseract/mrz.traineddata.gz +0 -0
  111. package/templates/public/tesseract/ocrb.traineddata.gz +0 -0
@@ -1,83 +1,95 @@
1
- <script setup lang="ts">
2
- import { computed, ref, useAttrs } from 'vue'
3
- import { VBtn } from 'vuetify/components/VBtn'
4
- import { useAlert } from '../../composables/alert'
5
- import { useHostAgent, type PatientRegisterPayload } from '../../composables/hostAgent'
6
-
7
- interface Props extends /* @vue-ignore */ InstanceType<typeof VBtn['$props']> {
8
- /** If true -> call /idcard/infoAndPhoto, else /idcard/info */
9
- withPhoto?: boolean
10
- /** Optional reader name/query string */
11
- reader?: string | null
12
- /** Auto-disable button while reading */
13
- disableWhileLoading?: boolean
14
- }
15
-
16
- const props = withDefaults(defineProps<Props>(), {
17
- withPhoto: false,
18
- reader: null,
19
- disableWhileLoading: true,
20
- })
21
-
22
- const emit = defineEmits<{
23
- (e: 'read', payload: PatientRegisterPayload): void
24
- (e: 'error', err: unknown): void
25
- (e: 'loading', loading: boolean): void
26
- }>()
27
-
28
- const attrs = useAttrs()
29
- const host = useHostAgent()
30
-
31
- const loading = ref(false)
32
- const disabled = computed(() => (props.disableWhileLoading ? loading.value : false))
33
-
34
- async function onClick(ev: MouseEvent) {
35
- // allow parent to prevent if they attach their own click handler and call preventDefault
36
- if (ev.defaultPrevented) return
37
-
38
- try {
39
- loading.value = true
40
- emit('loading', true)
41
-
42
- const payload = props.withPhoto
43
- ? await host.getIdCardInfoAndPhoto(props.reader ?? undefined)
44
- : await host.getIdCardInfo(props.reader ?? undefined)
45
-
46
- emit('read', payload)
47
- } catch (e: any) {
48
- emit('error', e)
49
-
50
- const msg = e?.data?.detail || e?.data?.title || e?.message || 'ID card read failed'
51
- useAlert()?.addAlert({ message: msg, alertType: 'error' })
52
- } finally {
53
- loading.value = false
54
- emit('loading', false)
55
- }
56
- }
57
- </script>
58
-
59
- <template>
60
- <!--
61
- Extends Vuetify VBtn:
62
- - All unknown attrs (size, class, etc.) pass through via v-bind="attrs"
63
- - You can override content via default slot; label+icon is fallback
64
- -->
65
- <v-btn
66
- v-bind="attrs"
67
- class="idcard-btn"
68
- :loading="loading"
69
- :disabled="disabled || (attrs as any).disabled"
70
- @click="onClick"
71
- >
72
- <template
73
- v-for="(_, name, index) in ($slots as {})"
74
- :key="index"
75
- #[name]="slotData"
76
- >
77
- <slot
78
- :name="name"
79
- v-bind="((slotData || {}) as object)"
80
- />
81
- </template>
82
- </v-btn>
83
- </template>
1
+ <script setup lang="ts">
2
+ /**
3
+ * DeviceIdCardButton bridges UI actions with host-agent hardware/device operations and emits runtime scan/read results.
4
+ * This doc block is consumed by vue-docgen for generated API documentation.
5
+ */
6
+ import { computed, ref, useAttrs } from 'vue'
7
+ import { VBtn } from 'vuetify/components/VBtn'
8
+ import { useAlert } from '../../composables/alert'
9
+ import { useHostAgent, type PatientRegisterPayload } from '../../composables/hostAgent'
10
+
11
+ interface Props extends /* @vue-ignore */ InstanceType<typeof VBtn['$props']> {
12
+ /** If true -> call /idcard/infoAndPhoto, else /idcard/info */
13
+ withPhoto?: boolean // Includes portrait/photo data in ID card read results.
14
+ /** Optional reader name/query string */
15
+ reader?: string | null // Preferred hardware reader identifier to connect or switch to.
16
+ /** Auto-disable button while reading */
17
+ disableWhileLoading?: boolean // Disables the trigger button while a read operation is in progress.
18
+ }
19
+
20
+ /**
21
+ * Public props accepted by DeviceIdCardButton.
22
+ * Document each prop field with intent, defaults, and side effects for clear generated docs.
23
+ */
24
+ const props = withDefaults(defineProps<Props>(), {
25
+ withPhoto: false,
26
+ reader: null,
27
+ disableWhileLoading: true,
28
+ })
29
+
30
+ /**
31
+ * Custom events emitted by DeviceIdCardButton.
32
+ * Parents can listen to these events to react to user actions and internal state changes.
33
+ */
34
+ const emit = defineEmits<{
35
+ (e: 'read', payload: PatientRegisterPayload): void
36
+ (e: 'error', err: unknown): void
37
+ (e: 'loading', loading: boolean): void
38
+ }>()
39
+
40
+ const attrs = useAttrs()
41
+ const host = useHostAgent()
42
+
43
+ const loading = ref(false)
44
+ const disabled = computed(() => (props.disableWhileLoading ? loading.value : false))
45
+
46
+ async function onClick(ev: MouseEvent) {
47
+ // allow parent to prevent if they attach their own click handler and call preventDefault
48
+ if (ev.defaultPrevented) return
49
+
50
+ try {
51
+ loading.value = true
52
+ emit('loading', true)
53
+
54
+ const payload = props.withPhoto
55
+ ? await host.getIdCardInfoAndPhoto(props.reader ?? undefined)
56
+ : await host.getIdCardInfo(props.reader ?? undefined)
57
+
58
+ emit('read', payload)
59
+ } catch (e: any) {
60
+ emit('error', e)
61
+
62
+ const msg = e?.data?.detail || e?.data?.title || e?.message || 'ID card read failed'
63
+ useAlert()?.addAlert({ message: msg, alertType: 'error' })
64
+ } finally {
65
+ loading.value = false
66
+ emit('loading', false)
67
+ }
68
+ }
69
+ </script>
70
+
71
+ <template>
72
+ <!--
73
+ Extends Vuetify VBtn:
74
+ - All unknown attrs (size, class, etc.) pass through via v-bind="attrs"
75
+ - You can override content via default slot; label+icon is fallback
76
+ -->
77
+ <v-btn
78
+ v-bind="attrs"
79
+ class="idcard-btn"
80
+ :loading="loading"
81
+ :disabled="disabled || (attrs as any).disabled"
82
+ @click="onClick"
83
+ >
84
+ <template
85
+ v-for="(_, name, index) in ($slots as {})"
86
+ :key="index"
87
+ #[name]="slotData"
88
+ >
89
+ <slot
90
+ :name="name"
91
+ v-bind="((slotData || {}) as object)"
92
+ />
93
+ </template>
94
+ </v-btn>
95
+ </template>
@@ -1,195 +1,207 @@
1
- <script setup lang="ts">
2
- import { onBeforeUnmount, onMounted, ref, watch } from 'vue'
3
- import { type IdCardWsServerMessage, type PatientRegisterPayload } from '../../composables/hostAgent'
4
- import { useHostAgentWs } from "../../composables/hostAgentWs";
5
-
6
- interface Props {
7
- /** Subscribe withPhoto=true => server will push payload that includes photo when available */
8
- withPhoto?: boolean
9
- /** Optional reader name; null/undefined means "any/default" */
10
- reader?: string | null
11
- /** Auto-connect on mount (default true) */
12
- autoConnect?: boolean
13
- /** Auto-subscribe after connect (default true) */
14
- autoSubscribe?: boolean
15
- /** If true, try to reconnect when socket closes unexpectedly */
16
- autoReconnect?: boolean
17
- /** Reconnect delay in ms */
18
- reconnectDelayMs?: number
19
- }
20
-
21
- const props = withDefaults(defineProps<Props>(), {
22
- withPhoto: false,
23
- reader: null,
24
- autoConnect: true,
25
- autoSubscribe: true,
26
- autoReconnect: false,
27
- reconnectDelayMs: 1000,
28
- })
29
-
30
- const emit = defineEmits<{
31
- (e: 'connected'): void
32
- (e: 'disconnected', info?: { code?: number; reason?: string }): void
33
- (e: 'subscribed', info?: { reader?: string | null; withPhoto?: boolean }): void
34
- (e: 'unsubscribed'): void
35
- (e: 'inserted', payload: PatientRegisterPayload): void
36
- (e: 'removed'): void
37
- (e: 'error', err: unknown): void
38
- (e: 'message', msg: IdCardWsServerMessage): void
39
- }>()
40
-
41
- const host = useHostAgentWs()
42
-
43
- const reconnectTimer = ref<ReturnType<typeof setTimeout> | null>(null)
44
- const mounted = ref(false)
45
- const manuallyDisconnected = ref(false)
46
-
47
- function clearReconnect() {
48
- if (reconnectTimer.value) {
49
- clearTimeout(reconnectTimer.value)
50
- reconnectTimer.value = null
51
- }
52
- }
53
-
54
- function connectAndSubscribe() {
55
- manuallyDisconnected.value = false
56
- host.connectIdCardWs()
57
-
58
- // subscribe immediately; safe because connectIdCardWs sets handlers then WS opens soon
59
- // but we must wait until OPEN to send; so we subscribe on "open" event in handler below.
60
- }
61
-
62
- function safeDisconnect() {
63
- manuallyDisconnected.value = true
64
- clearReconnect()
65
- try {
66
- // best-effort unsubscribe (only if connected)
67
- if (host.wsConnected.value) {
68
- try { host.unsubscribeIdCardWs() } catch { /* ignore */ }
69
- }
70
- } catch { /* ignore */ }
71
- host.disconnectIdCardWs()
72
- }
73
-
74
- const off = host.onIdCardWsEvent((ev) => {
75
- if (ev.type === 'open') {
76
- emit('connected')
77
-
78
- if (props.autoSubscribe) {
79
- try {
80
- host.subscribeIdCardWs({ reader: props.reader ?? null, withPhoto: props.withPhoto })
81
- } catch (e) {
82
- emit('error', e)
83
- }
84
- }
85
- return
86
- }
87
-
88
- if (ev.type === 'close') {
89
- emit('disconnected', { code: ev.code, reason: ev.reason })
90
-
91
- if (props.autoReconnect && mounted.value && !manuallyDisconnected.value) {
92
- clearReconnect()
93
- reconnectTimer.value = setTimeout(() => {
94
- try {
95
- connectAndSubscribe()
96
- } catch (e) {
97
- emit('error', e)
98
- }
99
- }, props.reconnectDelayMs)
100
- }
101
- return
102
- }
103
-
104
- if (ev.type === 'error') {
105
- emit('error', ev.error)
106
- return
107
- }
108
-
109
- if (ev.type === 'message') {
110
- const msg = ev.data
111
- emit('message', msg)
112
-
113
- switch (msg.type) {
114
- case 'monitor.subscribed':
115
- if (msg.ok) emit('subscribed', { reader: msg.reader ?? null, withPhoto: !!msg.withPhoto })
116
- break
117
- case 'monitor.unsubscribed':
118
- emit('unsubscribed')
119
- break
120
- case 'card.inserted':
121
- emit('inserted', msg.payload)
122
- break
123
- case 'card.removed':
124
- emit('removed')
125
- break
126
- case 'card.error':
127
- case 'error':
128
- emit('error', msg)
129
- break
130
- }
131
- }
132
- })
133
-
134
- onMounted(() => {
135
- mounted.value = true
136
- if (props.autoConnect) connectAndSubscribe()
137
- })
138
-
139
- onBeforeUnmount(() => {
140
- mounted.value = false
141
- try { off?.() } catch { /* ignore */ }
142
- safeDisconnect()
143
- })
144
-
145
- /**
146
- * If props change while connected, switch subscription behavior.
147
- * - withPhoto changes => re-subscribe (server treats subscribe twice as error), so do unsubscribe -> subscribe
148
- * - reader changes => use switchReader action when possible; if not connected, just store for next subscribe
149
- */
150
- watch(
151
- () => props.reader,
152
- (r) => {
153
- if (!mounted.value) return
154
- if (!host.wsConnected.value) return
155
- if (!host.wsSubscribed.value) return
156
-
157
- try {
158
- host.switchReaderIdCardWs(r ?? null)
159
- } catch (e) {
160
- emit('error', e)
161
- }
162
- }
163
- )
164
-
165
- watch(
166
- () => props.withPhoto,
167
- (withPhoto) => {
168
- if (!mounted.value) return
169
- if (!host.wsConnected.value) return
170
-
171
- // Server disallows re-subscribe without unsubscribe first.
172
- // We do a clean unsubscribe -> resubscribe sequence.
173
- try {
174
- if (host.wsSubscribed.value) {
175
- host.unsubscribeIdCardWs()
176
- }
177
- } catch { /* ignore */ }
178
-
179
- // resubscribe after a tick; give server a moment to process unsubscribe
180
- setTimeout(() => {
181
- try {
182
- if (host.wsConnected.value) {
183
- host.subscribeIdCardWs({ reader: props.reader ?? null, withPhoto })
184
- }
185
- } catch (e) {
186
- emit('error', e)
187
- }
188
- }, 0)
189
- }
190
- )
191
- </script>
192
-
193
- <template>
194
- <!-- Intentionally blank (headless component) -->
195
- </template>
1
+ <script setup lang="ts">
2
+ /**
3
+ * DeviceIdCardWebSocket bridges UI actions with host-agent hardware/device operations and emits runtime scan/read results.
4
+ * This doc block is consumed by vue-docgen for generated API documentation.
5
+ */
6
+ import { onBeforeUnmount, onMounted, ref, watch } from 'vue'
7
+ import { type IdCardWsServerMessage, type PatientRegisterPayload } from '../../composables/hostAgent'
8
+ import { useHostAgentWs } from "../../composables/hostAgentWs";
9
+
10
+ interface Props {
11
+ /** Subscribe withPhoto=true => server will push payload that includes photo when available */
12
+ withPhoto?: boolean // Includes portrait/photo data in ID card read results.
13
+ /** Optional reader name; null/undefined means "any/default" */
14
+ reader?: string | null // Preferred hardware reader identifier to connect or switch to.
15
+ /** Auto-connect on mount (default true) */
16
+ autoConnect?: boolean // Connects to the device WebSocket automatically on mount.
17
+ /** Auto-subscribe after connect (default true) */
18
+ autoSubscribe?: boolean // Subscribes to live reader events automatically after connect.
19
+ /** If true, try to reconnect when socket closes unexpectedly */
20
+ autoReconnect?: boolean // Retries connection automatically when the socket closes unexpectedly.
21
+ /** Reconnect delay in ms */
22
+ reconnectDelayMs?: number // Delay in milliseconds before each reconnect attempt.
23
+ }
24
+
25
+ /**
26
+ * Public props accepted by DeviceIdCardWebSocket.
27
+ * Document each prop field with intent, defaults, and side effects for clear generated docs.
28
+ */
29
+ const props = withDefaults(defineProps<Props>(), {
30
+ withPhoto: false,
31
+ reader: null,
32
+ autoConnect: true,
33
+ autoSubscribe: true,
34
+ autoReconnect: false,
35
+ reconnectDelayMs: 1000,
36
+ })
37
+
38
+ /**
39
+ * Custom events emitted by DeviceIdCardWebSocket.
40
+ * Parents can listen to these events to react to user actions and internal state changes.
41
+ */
42
+ const emit = defineEmits<{
43
+ (e: 'connected'): void
44
+ (e: 'disconnected', info?: { code?: number; reason?: string }): void
45
+ (e: 'subscribed', info?: { reader?: string | null; withPhoto?: boolean }): void
46
+ (e: 'unsubscribed'): void
47
+ (e: 'inserted', payload: PatientRegisterPayload): void
48
+ (e: 'removed'): void
49
+ (e: 'error', err: unknown): void
50
+ (e: 'message', msg: IdCardWsServerMessage): void
51
+ }>()
52
+
53
+ const host = useHostAgentWs()
54
+
55
+ const reconnectTimer = ref<ReturnType<typeof setTimeout> | null>(null)
56
+ const mounted = ref(false)
57
+ const manuallyDisconnected = ref(false)
58
+
59
+ function clearReconnect() {
60
+ if (reconnectTimer.value) {
61
+ clearTimeout(reconnectTimer.value)
62
+ reconnectTimer.value = null
63
+ }
64
+ }
65
+
66
+ function connectAndSubscribe() {
67
+ manuallyDisconnected.value = false
68
+ host.connectIdCardWs()
69
+
70
+ // subscribe immediately; safe because connectIdCardWs sets handlers then WS opens soon
71
+ // but we must wait until OPEN to send; so we subscribe on "open" event in handler below.
72
+ }
73
+
74
+ function safeDisconnect() {
75
+ manuallyDisconnected.value = true
76
+ clearReconnect()
77
+ try {
78
+ // best-effort unsubscribe (only if connected)
79
+ if (host.wsConnected.value) {
80
+ try { host.unsubscribeIdCardWs() } catch { /* ignore */ }
81
+ }
82
+ } catch { /* ignore */ }
83
+ host.disconnectIdCardWs()
84
+ }
85
+
86
+ const off = host.onIdCardWsEvent((ev) => {
87
+ if (ev.type === 'open') {
88
+ emit('connected')
89
+
90
+ if (props.autoSubscribe) {
91
+ try {
92
+ host.subscribeIdCardWs({ reader: props.reader ?? null, withPhoto: props.withPhoto })
93
+ } catch (e) {
94
+ emit('error', e)
95
+ }
96
+ }
97
+ return
98
+ }
99
+
100
+ if (ev.type === 'close') {
101
+ emit('disconnected', { code: ev.code, reason: ev.reason })
102
+
103
+ if (props.autoReconnect && mounted.value && !manuallyDisconnected.value) {
104
+ clearReconnect()
105
+ reconnectTimer.value = setTimeout(() => {
106
+ try {
107
+ connectAndSubscribe()
108
+ } catch (e) {
109
+ emit('error', e)
110
+ }
111
+ }, props.reconnectDelayMs)
112
+ }
113
+ return
114
+ }
115
+
116
+ if (ev.type === 'error') {
117
+ emit('error', ev.error)
118
+ return
119
+ }
120
+
121
+ if (ev.type === 'message') {
122
+ const msg = ev.data
123
+ emit('message', msg)
124
+
125
+ switch (msg.type) {
126
+ case 'monitor.subscribed':
127
+ if (msg.ok) emit('subscribed', { reader: msg.reader ?? null, withPhoto: !!msg.withPhoto })
128
+ break
129
+ case 'monitor.unsubscribed':
130
+ emit('unsubscribed')
131
+ break
132
+ case 'card.inserted':
133
+ emit('inserted', msg.payload)
134
+ break
135
+ case 'card.removed':
136
+ emit('removed')
137
+ break
138
+ case 'card.error':
139
+ case 'error':
140
+ emit('error', msg)
141
+ break
142
+ }
143
+ }
144
+ })
145
+
146
+ onMounted(() => {
147
+ mounted.value = true
148
+ if (props.autoConnect) connectAndSubscribe()
149
+ })
150
+
151
+ onBeforeUnmount(() => {
152
+ mounted.value = false
153
+ try { off?.() } catch { /* ignore */ }
154
+ safeDisconnect()
155
+ })
156
+
157
+ /**
158
+ * If props change while connected, switch subscription behavior.
159
+ * - withPhoto changes => re-subscribe (server treats subscribe twice as error), so do unsubscribe -> subscribe
160
+ * - reader changes => use switchReader action when possible; if not connected, just store for next subscribe
161
+ */
162
+ watch(
163
+ () => props.reader,
164
+ (r) => {
165
+ if (!mounted.value) return
166
+ if (!host.wsConnected.value) return
167
+ if (!host.wsSubscribed.value) return
168
+
169
+ try {
170
+ host.switchReaderIdCardWs(r ?? null)
171
+ } catch (e) {
172
+ emit('error', e)
173
+ }
174
+ }
175
+ )
176
+
177
+ watch(
178
+ () => props.withPhoto,
179
+ (withPhoto) => {
180
+ if (!mounted.value) return
181
+ if (!host.wsConnected.value) return
182
+
183
+ // Server disallows re-subscribe without unsubscribe first.
184
+ // We do a clean unsubscribe -> resubscribe sequence.
185
+ try {
186
+ if (host.wsSubscribed.value) {
187
+ host.unsubscribeIdCardWs()
188
+ }
189
+ } catch { /* ignore */ }
190
+
191
+ // resubscribe after a tick; give server a moment to process unsubscribe
192
+ setTimeout(() => {
193
+ try {
194
+ if (host.wsConnected.value) {
195
+ host.subscribeIdCardWs({ reader: props.reader ?? null, withPhoto })
196
+ }
197
+ } catch (e) {
198
+ emit('error', e)
199
+ }
200
+ }, 0)
201
+ }
202
+ )
203
+ </script>
204
+
205
+ <template>
206
+ <!-- Intentionally blank (headless component) -->
207
+ </template>