@conduction/nextcloud-vue 2.108.0 → 2.110.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 (161) hide show
  1. package/dist/esm/components/CnActionDialog/CnActionDialog.vue.js +1 -1
  2. package/dist/esm/components/CnActionDialog/CnActionDialog.vue2.js +115 -12
  3. package/dist/esm/components/CnActionDialog/CnActionDialog.vue2.js.map +1 -1
  4. package/dist/esm/components/CnActionDialog/CnActionDialog.vue3.js +36 -9
  5. package/dist/esm/components/CnActionDialog/CnActionDialog.vue3.js.map +1 -1
  6. package/dist/esm/components/CnAddressSearch/CnAddressSearch.vue.js +10 -0
  7. package/dist/esm/components/CnAddressSearch/CnAddressSearch.vue.js.map +1 -0
  8. package/dist/esm/components/CnAddressSearch/CnAddressSearch.vue2.js +305 -0
  9. package/dist/esm/components/CnAddressSearch/CnAddressSearch.vue2.js.map +1 -0
  10. package/dist/esm/components/CnAddressSearch/CnAddressSearch.vue3.js +38 -0
  11. package/dist/esm/components/CnAddressSearch/CnAddressSearch.vue3.js.map +1 -0
  12. package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue.js +10 -0
  13. package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue.js.map +1 -0
  14. package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue2.js +398 -0
  15. package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue2.js.map +1 -0
  16. package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue3.js +155 -0
  17. package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue3.js.map +1 -0
  18. package/dist/esm/components/CnDecisionListWidget/CnDecisionListWidget.vue.js +9 -0
  19. package/dist/esm/components/CnDecisionListWidget/CnDecisionListWidget.vue.js.map +1 -0
  20. package/dist/esm/components/CnDecisionListWidget/CnDecisionListWidget.vue2.js +281 -0
  21. package/dist/esm/components/CnDecisionListWidget/CnDecisionListWidget.vue2.js.map +1 -0
  22. package/dist/esm/components/CnDecisionListWidget/CnDecisionListWidget.vue3.js +137 -0
  23. package/dist/esm/components/CnDecisionListWidget/CnDecisionListWidget.vue3.js.map +1 -0
  24. package/dist/esm/components/CnDuplicateWarning/CnDuplicateWarning.vue.js +9 -0
  25. package/dist/esm/components/CnDuplicateWarning/CnDuplicateWarning.vue.js.map +1 -0
  26. package/dist/esm/components/CnDuplicateWarning/CnDuplicateWarning.vue2.js +225 -0
  27. package/dist/esm/components/CnDuplicateWarning/CnDuplicateWarning.vue2.js.map +1 -0
  28. package/dist/esm/components/CnDuplicateWarning/CnDuplicateWarning.vue3.js +110 -0
  29. package/dist/esm/components/CnDuplicateWarning/CnDuplicateWarning.vue3.js.map +1 -0
  30. package/dist/esm/components/CnFormDialog/CnFormDialog.vue.js +1 -1
  31. package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +19 -3
  32. package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
  33. package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
  34. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +30 -0
  35. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  36. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +513 -481
  37. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  38. package/dist/esm/components/CnNcDashboardList/CnNcDashboardList.vue.js +9 -0
  39. package/dist/esm/components/CnNcDashboardList/CnNcDashboardList.vue.js.map +1 -0
  40. package/dist/esm/components/CnNcDashboardList/CnNcDashboardList.vue2.js +181 -0
  41. package/dist/esm/components/CnNcDashboardList/CnNcDashboardList.vue2.js.map +1 -0
  42. package/dist/esm/components/CnNcDashboardList/CnNcDashboardList.vue3.js +50 -0
  43. package/dist/esm/components/CnNcDashboardList/CnNcDashboardList.vue3.js.map +1 -0
  44. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue.js +1 -1
  45. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +102 -1
  46. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
  47. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js +6 -6
  48. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
  49. package/dist/esm/components/CnPlanWidget/CnPlanWidget.vue.js +9 -0
  50. package/dist/esm/components/CnPlanWidget/CnPlanWidget.vue.js.map +1 -0
  51. package/dist/esm/components/CnPlanWidget/CnPlanWidget.vue2.js +190 -0
  52. package/dist/esm/components/CnPlanWidget/CnPlanWidget.vue2.js.map +1 -0
  53. package/dist/esm/components/CnPlanWidget/CnPlanWidget.vue3.js +91 -0
  54. package/dist/esm/components/CnPlanWidget/CnPlanWidget.vue3.js.map +1 -0
  55. package/dist/esm/components/CnPlanWidget/CnPlanWidgetRow.vue.js +9 -0
  56. package/dist/esm/components/CnPlanWidget/CnPlanWidgetRow.vue.js.map +1 -0
  57. package/dist/esm/components/CnPlanWidget/CnPlanWidgetRow.vue2.js +64 -0
  58. package/dist/esm/components/CnPlanWidget/CnPlanWidgetRow.vue2.js.map +1 -0
  59. package/dist/esm/components/CnPlanWidget/CnPlanWidgetRow.vue3.js +49 -0
  60. package/dist/esm/components/CnPlanWidget/CnPlanWidgetRow.vue3.js.map +1 -0
  61. package/dist/esm/components/CnRedactionEditor/CnRedactionEditor.vue.js +9 -0
  62. package/dist/esm/components/CnRedactionEditor/CnRedactionEditor.vue.js.map +1 -0
  63. package/dist/esm/components/CnRedactionEditor/CnRedactionEditor.vue2.js +382 -0
  64. package/dist/esm/components/CnRedactionEditor/CnRedactionEditor.vue2.js.map +1 -0
  65. package/dist/esm/components/CnRedactionEditor/CnRedactionEditor.vue3.js +285 -0
  66. package/dist/esm/components/CnRedactionEditor/CnRedactionEditor.vue3.js.map +1 -0
  67. package/dist/esm/components/CnRetentionWidget/CnRetentionWidget.vue.js +9 -0
  68. package/dist/esm/components/CnRetentionWidget/CnRetentionWidget.vue.js.map +1 -0
  69. package/dist/esm/components/CnRetentionWidget/CnRetentionWidget.vue2.js +277 -0
  70. package/dist/esm/components/CnRetentionWidget/CnRetentionWidget.vue2.js.map +1 -0
  71. package/dist/esm/components/CnRetentionWidget/CnRetentionWidget.vue3.js +183 -0
  72. package/dist/esm/components/CnRetentionWidget/CnRetentionWidget.vue3.js.map +1 -0
  73. package/dist/esm/components/CnWidgetGrid/builtInWidgets.js +16 -14
  74. package/dist/esm/components/CnWidgetGrid/builtInWidgets.js.map +1 -1
  75. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +53 -22
  76. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
  77. package/dist/esm/components/CnWorkQueue/CnWorkQueue.vue.js +9 -0
  78. package/dist/esm/components/CnWorkQueue/CnWorkQueue.vue.js.map +1 -0
  79. package/dist/esm/components/CnWorkQueue/CnWorkQueue.vue2.js +253 -0
  80. package/dist/esm/components/CnWorkQueue/CnWorkQueue.vue2.js.map +1 -0
  81. package/dist/esm/components/CnWorkQueue/CnWorkQueue.vue3.js +197 -0
  82. package/dist/esm/components/CnWorkQueue/CnWorkQueue.vue3.js.map +1 -0
  83. package/dist/esm/index.js +12 -0
  84. package/dist/esm/index.js.map +1 -1
  85. package/dist/esm/l10n/en.json.js +106 -1
  86. package/dist/esm/l10n/en.json.js.map +1 -1
  87. package/dist/esm/l10n/nl.json.js +105 -1
  88. package/dist/esm/l10n/nl.json.js.map +1 -1
  89. package/dist/esm/library-style.js +1 -1
  90. package/dist/esm/nextcloud-vue.css +510 -76
  91. package/dist/esm/utils/actionsDispatcher.js +11 -3
  92. package/dist/esm/utils/actionsDispatcher.js.map +1 -1
  93. package/dist/esm/utils/builtInModals.js +3 -1
  94. package/dist/esm/utils/builtInModals.js.map +1 -1
  95. package/dist/esm/utils/duplicateWarning.js +62 -0
  96. package/dist/esm/utils/duplicateWarning.js.map +1 -0
  97. package/dist/esm/utils/errors.js +4 -0
  98. package/dist/esm/utils/errors.js.map +1 -1
  99. package/dist/esm/utils/libraryWidgetKeys.js +4 -0
  100. package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
  101. package/dist/esm/utils/openDuplicateWarning.js +39 -0
  102. package/dist/esm/utils/openDuplicateWarning.js.map +1 -0
  103. package/dist/esm/utils/optionsFrom.js +69 -2
  104. package/dist/esm/utils/optionsFrom.js.map +1 -1
  105. package/dist/esm/utils/planTree.js +57 -0
  106. package/dist/esm/utils/planTree.js.map +1 -0
  107. package/dist/esm/utils/redactionSegments.js +53 -0
  108. package/dist/esm/utils/redactionSegments.js.map +1 -0
  109. package/dist/esm/utils/registerNcDashboardWidgets.js +61 -0
  110. package/dist/esm/utils/registerNcDashboardWidgets.js.map +1 -0
  111. package/dist/esm/utils/searchRefusal.js +62 -0
  112. package/dist/esm/utils/searchRefusal.js.map +1 -0
  113. package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
  114. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  115. package/dist/nextcloud-vue.cjs.js +17560 -12844
  116. package/dist/nextcloud-vue.cjs.js.map +2 -2
  117. package/dist/nextcloud-vue.css +510 -76
  118. package/l10n/en.json +106 -1
  119. package/l10n/nl.json +105 -1
  120. package/package.json +1 -1
  121. package/src/components/CnActionDialog/CnActionDialog.vue +134 -13
  122. package/src/components/CnAddressSearch/CnAddressSearch.vue +343 -0
  123. package/src/components/CnAddressSearch/index.js +12 -0
  124. package/src/components/CnAssignDialog/CnAssignDialog.vue +525 -0
  125. package/src/components/CnAssignDialog/index.js +12 -0
  126. package/src/components/CnDecisionListWidget/CnDecisionListWidget.vue +388 -0
  127. package/src/components/CnDecisionListWidget/index.js +12 -0
  128. package/src/components/CnDuplicateWarning/CnDuplicateWarning.vue +318 -0
  129. package/src/components/CnDuplicateWarning/index.js +12 -0
  130. package/src/components/CnFormDialog/CnFormDialog.vue +19 -3
  131. package/src/components/CnIndexPage/CnIndexPage.vue +52 -0
  132. package/src/components/CnNcDashboardList/CnNcDashboardList.vue +218 -0
  133. package/src/components/CnNcDashboardList/index.js +12 -0
  134. package/src/components/CnObjectListWidget/CnObjectListWidget.vue +107 -6
  135. package/src/components/CnPlanWidget/CnPlanWidget.vue +246 -0
  136. package/src/components/CnPlanWidget/CnPlanWidgetRow.vue +117 -0
  137. package/src/components/CnPlanWidget/index.js +12 -0
  138. package/src/components/CnRedactionEditor/CnRedactionEditor.vue +655 -0
  139. package/src/components/CnRedactionEditor/index.js +13 -0
  140. package/src/components/CnRetentionWidget/CnRetentionWidget.vue +413 -0
  141. package/src/components/CnRetentionWidget/index.js +12 -0
  142. package/src/components/CnWidgetGrid/builtInWidgets.js +2 -0
  143. package/src/components/CnWidgetGrid/registerDashboardWidgets.js +31 -0
  144. package/src/components/CnWorkQueue/CnWorkQueue.vue +463 -0
  145. package/src/components/CnWorkQueue/index.js +12 -0
  146. package/src/components/index.js +9 -0
  147. package/src/css/index-page.css +24 -0
  148. package/src/index.js +12 -0
  149. package/src/schemas/app-manifest-v2.schema.json +119 -3
  150. package/src/utils/actionsDispatcher.js +10 -2
  151. package/src/utils/builtInModals.js +3 -1
  152. package/src/utils/duplicateWarning.js +59 -0
  153. package/src/utils/errors.js +4 -0
  154. package/src/utils/libraryWidgetKeys.js +4 -0
  155. package/src/utils/openDuplicateWarning.js +35 -0
  156. package/src/utils/optionsFrom.js +68 -1
  157. package/src/utils/planTree.js +54 -0
  158. package/src/utils/redactionSegments.js +50 -0
  159. package/src/utils/registerNcDashboardWidgets.js +57 -0
  160. package/src/utils/searchRefusal.js +59 -0
  161. package/src/utils/validateManifestV2.compiled.js +3 -3
@@ -0,0 +1,343 @@
1
+ <!--
2
+ - SPDX-FileCopyrightText: 2026 Conduction B.V.
3
+ - SPDX-License-Identifier: EUPL-1.2
4
+ -->
5
+ <template>
6
+ <div class="cn-address-search" data-testid="cn-address-search">
7
+ <CnSearchSelect
8
+ :modelValue="selectedOption ? selectedOption.id : null"
9
+ :options="shownOptions"
10
+ :inputLabel="inputLabel || t('nextcloud-vue', 'Search an address')"
11
+ :labelOutside="labelOutside"
12
+ :placeholder="placeholder || t('nextcloud-vue', 'Street, postcode or place')"
13
+ :loading="searching"
14
+ :filterable="false"
15
+ :clearable="clearable"
16
+ :disabled="disabled"
17
+ @search="onSearch"
18
+ @update:modelValue="onPick" />
19
+ <p v-if="unavailable"
20
+ class="cn-address-search__note"
21
+ role="status"
22
+ data-testid="cn-address-search-unavailable">
23
+ {{ t('nextcloud-vue', 'Address search is not available right now.') }}
24
+ </p>
25
+ </div>
26
+ </template>
27
+
28
+ <script>
29
+ import axios from '@nextcloud/axios'
30
+ import { t } from '@nextcloud/l10n'
31
+ import { generateUrl } from '@nextcloud/router'
32
+ import { prefixUrl } from '../../utils/headers.js'
33
+ import { CnSearchSelect } from '../CnSearchSelect/index.js'
34
+
35
+ /** How long typing rests before a search runs, in ms. */
36
+ const SEARCH_DELAY = 250
37
+
38
+ /** The id the current value carries in the option list. */
39
+ const CURRENT_ID = '__cn-address-current'
40
+
41
+ /**
42
+ * WGS84 coordinates from a Locatieserver document: a normalised
43
+ * `location.coordinates` (`[lng, lat]`) or a raw `centroide_ll` WKT point.
44
+ *
45
+ * @param {object|null} doc The document.
46
+ * @return {{lat: number, lng: number}|null} The coordinates, or null.
47
+ */
48
+ export function addressCoordinates(doc) {
49
+ if (!doc) {
50
+ return null
51
+ }
52
+ if (Array.isArray(doc.location?.coordinates)) {
53
+ const [lng, lat] = doc.location.coordinates
54
+ return { lat, lng }
55
+ }
56
+ const match = typeof doc.centroide_ll === 'string' ? doc.centroide_ll.match(/POINT\(([^ ]+) ([^ ]+)\)/) : null
57
+ return match ? { lng: parseFloat(match[1]), lat: parseFloat(match[2]) } : null
58
+ }
59
+
60
+ /**
61
+ * A Locatieserver document's display text.
62
+ *
63
+ * @param {object|null} doc The document.
64
+ * @return {string} The address text.
65
+ */
66
+ export function addressText(doc) {
67
+ return doc ? doc.displayName || doc.weergavenaam || doc.display || '' : ''
68
+ }
69
+
70
+ /**
71
+ * CnAddressSearch — an address field over the PDOK Locatieserver (BAG
72
+ * addresses, streets, places and postcodes): suggestions as the user types,
73
+ * a free-text search when nothing is suggested, and on a pick the full
74
+ * document with its coordinates. Built on CnSearchSelect.
75
+ *
76
+ * Browsers never call api.pdok.nl directly (ADR-022): requests go to a
77
+ * PDOK proxy with `suggest`, `free` and `lookup/{id}` routes. By default
78
+ * that is the `/api/pdok` route of the first installed app among
79
+ * `appCandidates` (integriq, then openconnector); `endpoint` names another
80
+ * base URL. When the proxy answers 404 or 503 the field says address search
81
+ * is unavailable and stays usable, so a form around it can still be saved.
82
+ *
83
+ * `select` emits `{ result, coordinates, address }`: the looked-up document,
84
+ * `{ lat, lng }` or null, and the display text. `update:modelValue` emits
85
+ * the display text, or null when cleared.
86
+ *
87
+ * ```vue
88
+ * <CnAddressSearch v-model="caseObject.address" @select="onAddress" />
89
+ * ```
90
+ */
91
+ export default {
92
+ name: 'CnAddressSearch',
93
+
94
+ components: { CnSearchSelect },
95
+
96
+ props: {
97
+ /** The chosen address text. */
98
+ modelValue: {
99
+ type: String,
100
+ default: null,
101
+ },
102
+
103
+ /** Base URL of the PDOK proxy; empty resolves it from `appCandidates`. */
104
+ endpoint: {
105
+ type: String,
106
+ default: '',
107
+ },
108
+
109
+ /** App ids that serve `/api/pdok`, newest first; the first installed one wins. */
110
+ appCandidates: {
111
+ type: Array,
112
+ default: () => ['integriq', 'openconnector'],
113
+ },
114
+
115
+ /** The field's label; defaults to "Search an address". */
116
+ inputLabel: {
117
+ type: String,
118
+ default: '',
119
+ },
120
+
121
+ /** Draw the label above the field. */
122
+ labelOutside: {
123
+ type: Boolean,
124
+ default: false,
125
+ },
126
+
127
+ /** Placeholder; defaults to "Street, postcode or place". */
128
+ placeholder: {
129
+ type: String,
130
+ default: '',
131
+ },
132
+
133
+ /** Offer a clear button. */
134
+ clearable: {
135
+ type: Boolean,
136
+ default: true,
137
+ },
138
+
139
+ /** Disable the field. */
140
+ disabled: {
141
+ type: Boolean,
142
+ default: false,
143
+ },
144
+
145
+ /** The fewest characters a search needs. */
146
+ minLength: {
147
+ type: Number,
148
+ default: 3,
149
+ },
150
+
151
+ /** The most results a free-text search returns. */
152
+ rows: {
153
+ type: Number,
154
+ default: 10,
155
+ },
156
+ },
157
+
158
+ emits: ['update:modelValue', 'select'],
159
+
160
+ data() {
161
+ return {
162
+ options: [],
163
+ searching: false,
164
+ unavailable: false,
165
+ timer: null,
166
+ searchSeq: 0,
167
+ }
168
+ },
169
+
170
+ computed: {
171
+ /**
172
+ * The current value as an option, so the field shows it.
173
+ *
174
+ * @return {object|null}
175
+ */
176
+ selectedOption() {
177
+ return this.modelValue ? { id: CURRENT_ID, label: this.modelValue, icon: 'MapMarkerOutline' } : null
178
+ },
179
+
180
+ /**
181
+ * The suggestions, with the current value kept in the list.
182
+ *
183
+ * @return {Array<object>}
184
+ */
185
+ shownOptions() {
186
+ return this.selectedOption ? [this.selectedOption, ...this.options] : this.options
187
+ },
188
+
189
+ /**
190
+ * The proxy's base URL.
191
+ *
192
+ * @spec openspec/changes/address-search/specs/address-search/spec.md#requirement-an-address-field-over-the-pdok-locatieserver
193
+ * @return {string}
194
+ */
195
+ baseUrl() {
196
+ if (this.endpoint) {
197
+ return this.endpoint.replace(/\/+$/, '')
198
+ }
199
+ const roots = (typeof window !== 'undefined' && window.OC && window.OC.appswebroots) || {}
200
+ const app = this.appCandidates.find((id) => Object.hasOwn(roots, id)) || this.appCandidates[0] || 'integriq'
201
+ return generateUrl(`/apps/${app}/api/pdok`)
202
+ },
203
+ },
204
+
205
+ beforeUnmount() {
206
+ clearTimeout(this.timer)
207
+ },
208
+
209
+ methods: {
210
+ t,
211
+
212
+ /**
213
+ * A document as a picker option.
214
+ *
215
+ * @param {object} doc The Locatieserver document.
216
+ * @param {number} index Its place in the list, for documents without an id.
217
+ * @return {object|null} The option, or null without a text.
218
+ */
219
+ optionOf(doc, index) {
220
+ const label = addressText(doc)
221
+ if (!label) {
222
+ return null
223
+ }
224
+ const kinds = {
225
+ adres: t('nextcloud-vue', 'Address'),
226
+ address: t('nextcloud-vue', 'Address'),
227
+ weg: t('nextcloud-vue', 'Street'),
228
+ woonplaats: t('nextcloud-vue', 'Place'),
229
+ city: t('nextcloud-vue', 'Place'),
230
+ postcode: t('nextcloud-vue', 'Postcode'),
231
+ }
232
+ const municipality = doc.gemeentenaam && !label.includes(doc.gemeentenaam) ? doc.gemeentenaam : ''
233
+ return {
234
+ id: doc.id || `${CURRENT_ID}-${index}`,
235
+ label,
236
+ secondary: [kinds[doc.type] || '', municipality].filter(Boolean).join(' · '),
237
+ icon: 'MapMarkerOutline',
238
+ doc,
239
+ }
240
+ },
241
+
242
+ /**
243
+ * Call the proxy; 404 or 503 marks search unavailable and yields `fallback`.
244
+ *
245
+ * @param {string} path The route under the base URL.
246
+ * @param {object} params The query parameters.
247
+ * @param {Array<object>} fallback What a degraded answer yields.
248
+ * @return {Promise<Array<object>>} The documents.
249
+ */
250
+ async request(path, params, fallback) {
251
+ try {
252
+ const { data } = await axios.get(prefixUrl(`${this.baseUrl}/${path}`), { params })
253
+ this.unavailable = false
254
+ return Array.isArray(data?.docs) ? data.docs : fallback
255
+ } catch (error) {
256
+ const status = error?.response?.status
257
+ if (status === 404 || status === 503) {
258
+ this.unavailable = true
259
+ }
260
+ return fallback
261
+ }
262
+ },
263
+
264
+ /**
265
+ * Search after typing rests.
266
+ *
267
+ * @param {string} query The typed text.
268
+ */
269
+ onSearch(query) {
270
+ clearTimeout(this.timer)
271
+ const term = String(query || '').trim()
272
+ if (term.length < this.minLength) {
273
+ this.options = []
274
+ return
275
+ }
276
+ this.timer = setTimeout(() => this.search(term), SEARCH_DELAY)
277
+ },
278
+
279
+ /**
280
+ * Suggest addresses for the term; without suggestions, search free text.
281
+ *
282
+ * @spec openspec/changes/address-search/specs/address-search/spec.md#requirement-an-address-field-over-the-pdok-locatieserver
283
+ * @param {string} term The search term.
284
+ * @return {Promise<void>}
285
+ */
286
+ async search(term) {
287
+ const seq = ++this.searchSeq
288
+ this.searching = true
289
+ let docs = await this.request('suggest', { q: term }, [])
290
+ if (!docs.length && !this.unavailable && seq === this.searchSeq) {
291
+ docs = await this.request('free', { q: term, rows: this.rows }, [])
292
+ }
293
+ if (seq !== this.searchSeq) {
294
+ return
295
+ }
296
+ this.options = docs.map((doc, index) => this.optionOf(doc, index)).filter(Boolean)
297
+ this.searching = false
298
+ },
299
+
300
+ /**
301
+ * Take the picked suggestion: look up the full document, then emit it.
302
+ *
303
+ * @spec openspec/changes/address-search/specs/address-search/spec.md#requirement-an-address-field-over-the-pdok-locatieserver
304
+ * @param {string|null} id The picked option's id.
305
+ * @return {Promise<void>}
306
+ */
307
+ async onPick(id) {
308
+ if (id === CURRENT_ID) {
309
+ return
310
+ }
311
+ const option = id === null ? null : this.options.find((o) => String(o.id) === String(id))
312
+ if (!option) {
313
+ /** @event update:modelValue The chosen address text, or null when cleared. */
314
+ this.$emit('update:modelValue', null)
315
+ /** @event select `{ result, coordinates, address }` for the chosen address, or null when cleared. */
316
+ this.$emit('select', null)
317
+ return
318
+ }
319
+ let result = option.doc
320
+ if (option.doc.id) {
321
+ const [full] = await this.request(`lookup/${encodeURIComponent(option.doc.id)}`, {}, [])
322
+ result = full || option.doc
323
+ }
324
+ const address = addressText(result) || option.label
325
+ this.options = []
326
+ this.$emit('update:modelValue', address)
327
+ this.$emit('select', { result, coordinates: addressCoordinates(result), address })
328
+ },
329
+ },
330
+ }
331
+ </script>
332
+
333
+ <style scoped>
334
+ .cn-address-search {
335
+ width: 100%;
336
+ }
337
+
338
+ .cn-address-search__note {
339
+ margin: 4px 0 0;
340
+ color: var(--color-text-maxcontrast);
341
+ font-size: var(--font-size-small, 13px);
342
+ }
343
+ </style>
@@ -0,0 +1,12 @@
1
+ /**
2
+ * SPDX-FileCopyrightText: 2026 Conduction B.V.
3
+ * SPDX-License-Identifier: EUPL-1.2
4
+ *
5
+ * CnAddressSearch — an address field over the PDOK Locatieserver, through
6
+ * a PDOK proxy app, that emits the chosen address with its coordinates.
7
+ */
8
+
9
+ import CnAddressSearch from './CnAddressSearch.vue'
10
+
11
+ export { CnAddressSearch }
12
+ export default CnAddressSearch