@arbetsformedlingen/yrkesvaljaren 2.0.5

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 (146) hide show
  1. package/LICENSE +190 -0
  2. package/README.md +369 -0
  3. package/dist/cjs/af-validation-message-BzY5H0xU.js +16 -0
  4. package/dist/cjs/core-selector.cjs.entry.js +476 -0
  5. package/dist/cjs/digi-service-job-selector.cjs.entry.js +1619 -0
  6. package/dist/cjs/fuse-BTLNKxuL.js +1631 -0
  7. package/dist/cjs/geo-valjaren.cjs.entry.js +14910 -0
  8. package/dist/cjs/index-BOuTGlEp.js +1817 -0
  9. package/dist/cjs/index.cjs.js +14 -0
  10. package/dist/cjs/job-selector-demo.cjs.entry.js +692 -0
  11. package/dist/cjs/loader.cjs.js +12 -0
  12. package/dist/cjs/neobrutal-search.cjs.entry.js +171 -0
  13. package/dist/cjs/search.engine-D0cMelRi.js +489 -0
  14. package/dist/cjs/taxonomy-registry-DVmuDUpM.js +435 -0
  15. package/dist/cjs/types-CtHBl6Ra.js +14 -0
  16. package/dist/cjs/yrkesvaljaren.cjs.js +24 -0
  17. package/dist/collection/collection-manifest.json +15 -0
  18. package/dist/collection/components/core-selector/core-selector.css +803 -0
  19. package/dist/collection/components/core-selector/core-selector.js +761 -0
  20. package/dist/collection/components/core-selector/types.js +1 -0
  21. package/dist/collection/components/design-compat/af-icon.js +13 -0
  22. package/dist/collection/components/design-compat/af-label.js +2 -0
  23. package/dist/collection/components/design-compat/af-tag.js +6 -0
  24. package/dist/collection/components/design-compat/af-validation-message.js +11 -0
  25. package/dist/collection/components/design-compat/index.js +4 -0
  26. package/dist/collection/components/geovaljaren/geovaljaren.css +21 -0
  27. package/dist/collection/components/geovaljaren/geovaljaren.js +304 -0
  28. package/dist/collection/components/geovaljaren/logic/adapter.utils.js +20 -0
  29. package/dist/collection/components/geovaljaren/logic/data.utils.js +12 -0
  30. package/dist/collection/components/geovaljaren/logic/history-service.js +25 -0
  31. package/dist/collection/components/geovaljaren/logic/search.engine.js +28 -0
  32. package/dist/collection/components/job-selector/constants.js +26 -0
  33. package/dist/collection/components/job-selector/history-service.js +41 -0
  34. package/dist/collection/components/job-selector/job-selector-controller.js +835 -0
  35. package/dist/collection/components/job-selector/job-selector.css +318 -0
  36. package/dist/collection/components/job-selector/job-selector.js +1378 -0
  37. package/dist/collection/components/job-selector/keyboard-handlers.js +84 -0
  38. package/dist/collection/components/job-selector/logic/adapter.utils.js +75 -0
  39. package/dist/collection/components/job-selector/logic/label-formatter.js +118 -0
  40. package/dist/collection/components/job-selector/logic/search.engine.js +50 -0
  41. package/dist/collection/components/job-selector/search-logic.js +35 -0
  42. package/dist/collection/components/job-selector/search.utils.js +224 -0
  43. package/dist/collection/components/job-selector/suggestions-list.js +31 -0
  44. package/dist/collection/components/job-selector/taxonomy-registry.js +60 -0
  45. package/dist/collection/components/job-selector/test-data.mock.js +33 -0
  46. package/dist/collection/components/job-selector/types.js +10 -0
  47. package/dist/collection/components/job-selector/validation-message.js +24 -0
  48. package/dist/collection/demo-data.js +20 -0
  49. package/dist/collection/index.js +7 -0
  50. package/dist/collection/verkstad/verkstad.js +407 -0
  51. package/dist/components/core-selector.d.ts +11 -0
  52. package/dist/components/core-selector.js +1 -0
  53. package/dist/components/core-selector2.js +1 -0
  54. package/dist/components/digi-service-job-selector.d.ts +11 -0
  55. package/dist/components/digi-service-job-selector.js +1 -0
  56. package/dist/components/fuse.js +1 -0
  57. package/dist/components/geo-valjaren.d.ts +11 -0
  58. package/dist/components/geo-valjaren.js +1 -0
  59. package/dist/components/index.d.ts +35 -0
  60. package/dist/components/index.js +1 -0
  61. package/dist/components/job-selector-demo.d.ts +11 -0
  62. package/dist/components/job-selector-demo.js +1 -0
  63. package/dist/components/job-selector.js +1 -0
  64. package/dist/components/neobrutal-search.d.ts +11 -0
  65. package/dist/components/neobrutal-search.js +1 -0
  66. package/dist/components/search.engine.js +1 -0
  67. package/dist/components/types.js +1 -0
  68. package/dist/esm/af-validation-message-F8NIH2rC.js +14 -0
  69. package/dist/esm/core-selector.entry.js +474 -0
  70. package/dist/esm/digi-service-job-selector.entry.js +1617 -0
  71. package/dist/esm/fuse-BPnhMTul.js +1629 -0
  72. package/dist/esm/geo-valjaren.entry.js +14908 -0
  73. package/dist/esm/index-CGXZllj2.js +1806 -0
  74. package/dist/esm/index.js +10 -0
  75. package/dist/esm/job-selector-demo.entry.js +690 -0
  76. package/dist/esm/loader.js +10 -0
  77. package/dist/esm/neobrutal-search.entry.js +169 -0
  78. package/dist/esm/search.engine-nZ7AcXba.js +479 -0
  79. package/dist/esm/taxonomy-registry-Dw0VR9Aw.js +430 -0
  80. package/dist/esm/types-DHUftoZJ.js +12 -0
  81. package/dist/esm/yrkesvaljaren.js +20 -0
  82. package/dist/index.cjs.js +1 -0
  83. package/dist/index.js +1 -0
  84. package/dist/types/components/core-selector/core-selector.d.ts +46 -0
  85. package/dist/types/components/core-selector/types.d.ts +1 -0
  86. package/dist/types/components/design-compat/af-icon.d.ts +7 -0
  87. package/dist/types/components/design-compat/af-label.d.ts +8 -0
  88. package/dist/types/components/design-compat/af-tag.d.ts +8 -0
  89. package/dist/types/components/design-compat/af-validation-message.d.ts +9 -0
  90. package/dist/types/components/design-compat/index.d.ts +5 -0
  91. package/dist/types/components/geovaljaren/geovaljaren.d.ts +24 -0
  92. package/dist/types/components/geovaljaren/logic/adapter.utils.d.ts +7 -0
  93. package/dist/types/components/geovaljaren/logic/data.utils.d.ts +7 -0
  94. package/dist/types/components/geovaljaren/logic/history-service.d.ts +6 -0
  95. package/dist/types/components/geovaljaren/logic/search.engine.d.ts +7 -0
  96. package/dist/types/components/job-selector/ambient.d.ts +4 -0
  97. package/dist/types/components/job-selector/constants.d.ts +13 -0
  98. package/dist/types/components/job-selector/history-service.d.ts +6 -0
  99. package/dist/types/components/job-selector/job-selector-controller.d.ts +82 -0
  100. package/dist/types/components/job-selector/job-selector.d.ts +140 -0
  101. package/dist/types/components/job-selector/keyboard-handlers.d.ts +13 -0
  102. package/dist/types/components/job-selector/logic/adapter.utils.d.ts +12 -0
  103. package/dist/types/components/job-selector/logic/label-formatter.d.ts +4 -0
  104. package/dist/types/components/job-selector/logic/search.engine.d.ts +21 -0
  105. package/dist/types/components/job-selector/search-logic.d.ts +17 -0
  106. package/dist/types/components/job-selector/search.utils.d.ts +10 -0
  107. package/dist/types/components/job-selector/suggestions-list.d.ts +13 -0
  108. package/dist/types/components/job-selector/taxonomy-registry.d.ts +20 -0
  109. package/dist/types/components/job-selector/test-data.mock.d.ts +7 -0
  110. package/dist/types/components/job-selector/types.d.ts +107 -0
  111. package/dist/types/components/job-selector/validation-message.d.ts +8 -0
  112. package/dist/types/components/job-selector-demo/job-selector-demo.d.ts +55 -0
  113. package/dist/types/components/neobrutal-search/neobrutal-search.d.ts +18 -0
  114. package/dist/types/components.d.ts +425 -0
  115. package/dist/types/demo-data.d.ts +16 -0
  116. package/dist/types/index.d.ts +5 -0
  117. package/dist/types/stencil-public-runtime.d.ts +1873 -0
  118. package/dist/types/verkstad/verkstad.d.ts +1 -0
  119. package/dist/yrkesvaljaren/fuse-B0eHOxow.js.map +1 -0
  120. package/dist/yrkesvaljaren/index-Cy4BCo5s.js.map +1 -0
  121. package/dist/yrkesvaljaren/index-DfJXTQqL.js.map +1 -0
  122. package/dist/yrkesvaljaren/index.esm.js +1 -0
  123. package/dist/yrkesvaljaren/index.esm.js.map +1 -0
  124. package/dist/yrkesvaljaren/p-16636162.entry.js +1 -0
  125. package/dist/yrkesvaljaren/p-273cf6df.entry.js +1 -0
  126. package/dist/yrkesvaljaren/p-9e48afa4.entry.js +1 -0
  127. package/dist/yrkesvaljaren/p-BPnhMTul.js +1 -0
  128. package/dist/yrkesvaljaren/p-CGXZllj2.js +3 -0
  129. package/dist/yrkesvaljaren/p-DHUftoZJ.js +1 -0
  130. package/dist/yrkesvaljaren/p-DYFAzvqX.js +1 -0
  131. package/dist/yrkesvaljaren/p-Dw0VR9Aw.js +1 -0
  132. package/dist/yrkesvaljaren/p-aef7b5ae.entry.js +1 -0
  133. package/dist/yrkesvaljaren/p-b0f16abd.entry.js +1 -0
  134. package/dist/yrkesvaljaren/p-uBrs3sSs.js +1 -0
  135. package/dist/yrkesvaljaren/search.engine-DPT6roRm.js.map +1 -0
  136. package/dist/yrkesvaljaren/taxonomy-registry-s5khf6s4.js.map +1 -0
  137. package/dist/yrkesvaljaren/types-DHUftoZJ.js.map +1 -0
  138. package/dist/yrkesvaljaren/yrkesvaljaren.css +1 -0
  139. package/dist/yrkesvaljaren/yrkesvaljaren.esm.js +1 -0
  140. package/dist/yrkesvaljaren/yrkesvaljaren.esm.js.map +1 -0
  141. package/loader/cdn.js +1 -0
  142. package/loader/index.cjs.js +1 -0
  143. package/loader/index.d.ts +24 -0
  144. package/loader/index.es2017.js +1 -0
  145. package/loader/index.js +2 -0
  146. package/package.json +126 -0
@@ -0,0 +1,1378 @@
1
+ import { h, Fragment } from "@stencil/core";
2
+ import { JobSelectorController } from "./job-selector-controller";
3
+ import { InvalidSelectionError, } from "./types";
4
+ import { getRegisteredTaxonomy, isTaxonomyRegistration, registerTaxonomy } from "./taxonomy-registry";
5
+ import { MIN_INPUT_LENGTH, PLACEHOLDER_TEXT, } from "./constants";
6
+ import { styleParentheticalText } from "./search.utils";
7
+ import { AfValidationMessage } from "../design-compat";
8
+ import log from "loglevel";
9
+ if (process.env.NODE_ENV === 'production' || process.env.NODE_ENV === 'test') {
10
+ log.setLevel(log.levels.SILENT);
11
+ }
12
+ else {
13
+ log.setLevel(log.levels.DEBUG);
14
+ }
15
+ const YV_LATEST_DATA_URL = 'https://d3hng02m5bee8m.cloudfront.net/yrkesvaljaren/latest.json';
16
+ const CONCEPT_ID_PATTERN = /^[123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ]{4}_[123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ]{3}_[123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ]{3}$/;
17
+ export class JobSelector {
18
+ hostElement;
19
+ internals;
20
+ /**
21
+ * HTML-formulärets fältnamn. Följer standardattributet `name`, på samma sätt som
22
+ * `<input name="...">`, och används som nyckel när värdet läses via `FormData`.
23
+ */
24
+ name = 'yrkesvaljaren';
25
+ /**
26
+ * Fullständig, explicit URL till en egenhostad datafil.
27
+ * Utelämna attributet för att använda senaste data automatiskt.
28
+ */
29
+ taxonomyUrl;
30
+ placeholder = PLACEHOLDER_TEXT;
31
+ label = 'Välj yrke';
32
+ /** Om satt till true kan väljaren inte användas med mus eller tangentbord. */
33
+ disabled = false;
34
+ enableMultiple = false;
35
+ showSearchIcon = true;
36
+ errorMessage;
37
+ labelDescription;
38
+ /** Om satt till `true` måste fältet ha ett värde för att vara giltigt. */
39
+ required = false;
40
+ /** Om satt till `true` tillåts fritext som inte finns i taxonomin. */
41
+ manualInput = false;
42
+ /**
43
+ * Aktiverar historiken.
44
+ * Använder Local Storage för att spara valda yrken i webbläsaren.
45
+ * Integritet: Data lagras bara lokalt i webbläsaren.
46
+ * Räckvidd: Historiken delas mellan sidor på samma domän.
47
+ */
48
+ history = true;
49
+ /**
50
+ * Om satt till true visas populära yrken initialt när historiken är tom.
51
+ */
52
+ popular = true;
53
+ // Interna konstanter och inställningar som inte exponeras för klienten
54
+ maxHistoryItems = 10;
55
+ enableSelectionAnimation = false;
56
+ afFocused = false;
57
+ populateInputOnSelect = false;
58
+ maxVisibleRows;
59
+ minLength = MIN_INPUT_LENGTH;
60
+ maxResults = 10;
61
+ get requireSelection() {
62
+ return !this.manualInput;
63
+ }
64
+ /**
65
+ * Storleken på inputfältet.
66
+ * 'small' | 'medium' | 'large'
67
+ */
68
+ size = 'medium';
69
+ constructor() {
70
+ log.debug('[JobSelector] Constructor - enableMultiple:', this.enableMultiple);
71
+ }
72
+ state = { status: 'loading' };
73
+ /**
74
+ * Spårar vilken typ av fel som är aktivt,
75
+ * för att kunna visa rätt meddelande.
76
+ */
77
+ activeErrorType = null;
78
+ animatingItemId = null;
79
+ animationTimer;
80
+ cloneSelection(selection) {
81
+ return selection.map((item) => item.id === null
82
+ ? { ...item }
83
+ : {
84
+ ...item,
85
+ related: item.related ? { ...item.related } : undefined,
86
+ });
87
+ }
88
+ rebuildValidJobIds() {
89
+ const taxonomyData = this.controller?.getTaxonomyData?.() ?? this.taxonomyData ?? [];
90
+ this.validJobIds = new Set(taxonomyData.map((item) => item.id).filter(Boolean));
91
+ }
92
+ validateSelectionCandidate(candidate) {
93
+ const values = typeof candidate === 'string'
94
+ ? [candidate]
95
+ : Array.isArray(candidate)
96
+ ? candidate
97
+ : null;
98
+ if (!values) {
99
+ return { ok: false, reason: 'invalid-input' };
100
+ }
101
+ const hasStrings = values.some((item) => typeof item === 'string');
102
+ const hasObjects = values.some((item) => typeof item === 'object' && item !== null);
103
+ if ((hasStrings && hasObjects) || values.some((item) => typeof item !== 'string' && (typeof item !== 'object' || item === null))) {
104
+ return { ok: false, reason: 'invalid-input' };
105
+ }
106
+ const selection = [];
107
+ const taxonomyIds = [];
108
+ const manualLabels = [];
109
+ for (const item of values) {
110
+ if (typeof item === 'string') {
111
+ const value = item.trim();
112
+ if (!value) {
113
+ return { ok: false, reason: 'invalid-input' };
114
+ }
115
+ if (CONCEPT_ID_PATTERN.test(value)) {
116
+ taxonomyIds.push(value);
117
+ selection.push({ id: value });
118
+ }
119
+ else {
120
+ if (!this.manualInput) {
121
+ return { ok: false, reason: 'manual-input-disabled' };
122
+ }
123
+ manualLabels.push(value);
124
+ selection.push({ type: 'manual-input', id: null, preferred_label: value });
125
+ }
126
+ continue;
127
+ }
128
+ if (!item || typeof item !== 'object') {
129
+ return { ok: false, reason: 'invalid-input' };
130
+ }
131
+ const candidateItem = item;
132
+ if (typeof candidateItem.id === 'string') {
133
+ const id = candidateItem.id.trim();
134
+ if (!id
135
+ || (candidateItem.type !== 'job-title'
136
+ && candidateItem.type !== 'occupation-name')
137
+ || typeof candidateItem.preferred_label !== 'string'
138
+ || !candidateItem.preferred_label.trim()) {
139
+ return { ok: false, reason: 'invalid-input' };
140
+ }
141
+ if (candidateItem.related !== undefined) {
142
+ const related = candidateItem.related;
143
+ if (!related
144
+ || related.type !== 'occupation-name'
145
+ || typeof related.id !== 'string'
146
+ || !related.id.trim()
147
+ || typeof related.preferred_label !== 'string'
148
+ || !related.preferred_label.trim()) {
149
+ return { ok: false, reason: 'invalid-input' };
150
+ }
151
+ }
152
+ taxonomyIds.push(id);
153
+ selection.push({ id });
154
+ }
155
+ else if (candidateItem.id === null && candidateItem.type === 'manual-input') {
156
+ if (typeof candidateItem.preferred_label !== 'string' || !candidateItem.preferred_label.trim()) {
157
+ return { ok: false, reason: 'invalid-input' };
158
+ }
159
+ if (!this.manualInput) {
160
+ return { ok: false, reason: 'manual-input-disabled' };
161
+ }
162
+ const label = candidateItem.preferred_label.trim();
163
+ manualLabels.push(label);
164
+ selection.push({ type: 'manual-input', id: null, preferred_label: label });
165
+ }
166
+ else {
167
+ return { ok: false, reason: 'invalid-input' };
168
+ }
169
+ }
170
+ if (!this.enableMultiple && selection.length > 1) {
171
+ return { ok: false, reason: 'multiple-disabled' };
172
+ }
173
+ if (new Set(taxonomyIds).size !== taxonomyIds.length) {
174
+ return { ok: false, reason: 'duplicate-taxonomy-id' };
175
+ }
176
+ if (new Set(manualLabels).size !== manualLabels.length) {
177
+ return { ok: false, reason: 'duplicate-manual-label' };
178
+ }
179
+ const invalidIds = taxonomyIds.filter((id) => !this.validJobIds.has(id));
180
+ if (invalidIds.length > 0) {
181
+ return { ok: false, reason: 'unknown-id', invalidIds };
182
+ }
183
+ return { ok: true, selection };
184
+ }
185
+ describeSelectionRejection(rejection) {
186
+ switch (rejection.reason) {
187
+ case 'invalid-input':
188
+ return 'setSelection accepterar en sträng, en homogen strängarray eller en array med fullständiga JobSelectionItem-objekt.';
189
+ case 'data-unavailable':
190
+ return 'setSelection kan inte användas eftersom yrkesdata inte kunde laddas.';
191
+ case 'duplicate-taxonomy-id':
192
+ return 'selection får inte innehålla dubbla taxonomi-id:n.';
193
+ case 'duplicate-manual-label':
194
+ return 'selection får inte innehålla dubbla manuella labels.';
195
+ case 'multiple-disabled':
196
+ return 'flera värden kräver af-enable-multiple="true".';
197
+ case 'manual-input-disabled':
198
+ return 'manual-val kräver af-manual-input="true".';
199
+ case 'unknown-id':
200
+ return `okända Concept ID:n: ${rejection.invalidIds?.join(', ')}`;
201
+ }
202
+ }
203
+ errorCodeForRejection(reason) {
204
+ switch (reason) {
205
+ case 'unknown-id':
206
+ return 'unknown-id';
207
+ case 'manual-input-disabled':
208
+ return 'manual-input-disabled';
209
+ case 'multiple-disabled':
210
+ return 'multiple-disabled';
211
+ default:
212
+ return 'invalid-input';
213
+ }
214
+ }
215
+ rejectSelection(candidate, rejection) {
216
+ const message = this.describeSelectionRejection(rejection);
217
+ console.error(`[Yrkesväljaren] Ogiltig selection: ${message} Värdet accepterades inte; senaste giltiga val behålls.`, { selection: candidate, previousSelection: this.lastAcceptedSelection });
218
+ this.activeErrorType = 'invalidInput';
219
+ this.validationChangeEmitter.emit({ isValid: false, errorType: 'invalidInput' });
220
+ this.updateValidity(false);
221
+ return new InvalidSelectionError(message, this.errorCodeForRejection(rejection.reason), rejection.invalidIds);
222
+ }
223
+ syncAcceptedSelection(selection) {
224
+ const nextSelection = this.cloneSelection(selection);
225
+ this.lastAcceptedSelection = nextSelection;
226
+ if (this.activeErrorType === 'invalidInput') {
227
+ const hasAcceptedSelection = nextSelection.length > 0;
228
+ const isValid = hasAcceptedSelection || !this.required;
229
+ const errorType = isValid ? null : 'required';
230
+ this.activeErrorType = errorType;
231
+ this.validationChangeEmitter.emit({ isValid, errorType });
232
+ this.updateValidity(isValid);
233
+ }
234
+ }
235
+ applySelection(candidate) {
236
+ if (!this.controller) {
237
+ throw this.rejectSelection(candidate, { ok: false, reason: 'data-unavailable' });
238
+ }
239
+ const validation = this.validateSelectionCandidate(candidate);
240
+ if (!validation.ok) {
241
+ throw this.rejectSelection(candidate, validation);
242
+ }
243
+ const result = this.controller.setSelection(validation.selection);
244
+ if (!result.ok) {
245
+ throw this.rejectSelection(candidate, { ok: false, reason: 'unknown-id', invalidIds: result.invalidIds });
246
+ }
247
+ this.syncAcceptedSelection(result.selection);
248
+ return this.cloneSelection(result.selection);
249
+ }
250
+ watchRequired(newValue) {
251
+ this.controller.updateConfig({ required: newValue });
252
+ }
253
+ watchManualInput(newValue) {
254
+ this.controller.updateConfig({ requireSelection: !newValue });
255
+ }
256
+ watchHistory(newValue) {
257
+ this.controller.updateConfig({ history: newValue });
258
+ }
259
+ watchPopular(newValue) {
260
+ this.controller.updateConfig({ popular: newValue });
261
+ }
262
+ watchEnableMultiple(newValue) {
263
+ this.controller.updateConfig({ enableMultiple: newValue });
264
+ }
265
+ watchDisabled(newValue) {
266
+ if (newValue)
267
+ this.afFocused = false;
268
+ }
269
+ watchTaxonomyData(newValue) {
270
+ if (this.controller && newValue && newValue.length > 0) {
271
+ this.controller.initialize(newValue).then(() => {
272
+ this.rebuildValidJobIds();
273
+ this.restoreAcceptedSelectionAfterDataUpdate();
274
+ });
275
+ }
276
+ }
277
+ /**
278
+ * Yrkesdata (taxonomi). Kan injiceras av integratören för deterministisk laddning (t.ex. i testmiljöer).
279
+ * Om den lämnas tom populeras den internt via det globala registret eller auto-fetch för att exponera aktuell data.
280
+ */
281
+ taxonomyData = [];
282
+ afJobSelectedEmitter;
283
+ syncFormValue() {
284
+ if (!this.state)
285
+ return;
286
+ if (this.enableMultiple) {
287
+ const selected = (this.state.status === 'multiSelect' || this.state.status === 'multiSelectShowingHistory')
288
+ ? this.state.selectedItems
289
+ : [];
290
+ const formData = new FormData();
291
+ selected.forEach(item => {
292
+ formData.append(this.name, item.id || item.rawLabel);
293
+ });
294
+ this.internals.setFormValue(formData);
295
+ }
296
+ else {
297
+ const selected = this.state.status === 'itemSelected' ? this.state.selectedItem : null;
298
+ this.internals.setFormValue(selected ? selected.id || selected.rawLabel : null);
299
+ }
300
+ }
301
+ handleFormValidationSync(event) {
302
+ this.updateValidity(event.detail.isValid);
303
+ }
304
+ dataLoadFailedEmitter;
305
+ validationChangeEmitter;
306
+ static nextId = 0;
307
+ componentId = `job-selector-${JobSelector.nextId++}`;
308
+ observer;
309
+ controller;
310
+ validJobIds = new Set();
311
+ lastAcceptedSelection = [];
312
+ restoreAcceptedSelectionAfterDataUpdate() {
313
+ if (this.lastAcceptedSelection.length === 0) {
314
+ return;
315
+ }
316
+ const result = this.controller.setSelection(this.lastAcceptedSelection);
317
+ if (result.ok) {
318
+ this.syncAcceptedSelection(result.selection);
319
+ return;
320
+ }
321
+ this.rejectSelection(this.lastAcceptedSelection, {
322
+ ok: false,
323
+ reason: 'unknown-id',
324
+ invalidIds: result.invalidIds,
325
+ });
326
+ }
327
+ // --- LIVSCYKEL-METODER ---
328
+ async componentWillLoad() {
329
+ log.setLevel(log.levels.WARN);
330
+ // Använd registrerad data om den finns. Annars används en explicit URL
331
+ // eller senaste publicerade data.
332
+ const registeredData = getRegisteredTaxonomy();
333
+ const hasData = (this.taxonomyData && this.taxonomyData.length > 0) || (registeredData && registeredData.length > 0);
334
+ if (!hasData) {
335
+ this.state = { status: 'loading' };
336
+ try {
337
+ const dataUrl = this.taxonomyUrl ?? YV_LATEST_DATA_URL;
338
+ log.debug(`[JobSelector] Hämtar yrkesdata från: ${dataUrl}`);
339
+ const response = await fetch(dataUrl);
340
+ if (!response.ok) {
341
+ throw new Error(`Kunde inte ladda yrkesdata från ${dataUrl}: ${response.status} ${response.statusText}`);
342
+ }
343
+ const data = await response.json();
344
+ if (!isTaxonomyRegistration(data)) {
345
+ throw new Error('Yrkesdatan har ett ogiltigt format.');
346
+ }
347
+ registerTaxonomy(data);
348
+ log.debug('[JobSelector] Data-laddning lyckades.');
349
+ }
350
+ catch (error) {
351
+ log.error('[JobSelector] Fel vid data-laddning:', error);
352
+ this.state = { status: 'error', message: 'Kunde inte ladda yrkesdata.' };
353
+ this.dataLoadFailedEmitter.emit({ error: error instanceof Error ? error : new Error(String(error)) });
354
+ return;
355
+ }
356
+ }
357
+ this.controller = new JobSelectorController({
358
+ onStateChange: (state) => {
359
+ this.state = { ...state };
360
+ this.syncFormValue();
361
+ },
362
+ onEmitSelection: (selection) => {
363
+ const nextSelection = selection
364
+ ? (Array.isArray(selection) ? selection : [selection])
365
+ : [];
366
+ this.syncAcceptedSelection(nextSelection);
367
+ this.afJobSelectedEmitter.emit(nextSelection);
368
+ },
369
+ onEmitValidation: (payload) => {
370
+ this.activeErrorType = payload.errorType;
371
+ this.validationChangeEmitter.emit(payload);
372
+ },
373
+ onEmitDataLoadFailed: (error) => { this.dataLoadFailedEmitter.emit({ error }); },
374
+ onEmitStatusChange: () => { },
375
+ }, {
376
+ enableMultiple: this.enableMultiple,
377
+ required: this.required,
378
+ requireSelection: this.requireSelection,
379
+ history: this.history,
380
+ popular: this.popular,
381
+ maxHistoryItems: this.maxHistoryItems,
382
+ minLength: this.minLength,
383
+ maxResults: this.maxResults,
384
+ });
385
+ // Initialisera controller (den läser in data från registret eller använder injicerad data)
386
+ await this.controller.initialize(this.taxonomyData);
387
+ this.taxonomyData = this.controller.getTaxonomyData();
388
+ this.rebuildValidJobIds();
389
+ // Sätt initial valideringsstatus för FACE
390
+ if (this.activeErrorType === 'invalidInput') {
391
+ this.updateValidity(false);
392
+ }
393
+ else if (this.required && this.lastAcceptedSelection.length === 0) {
394
+ this.internals.setValidity({ customError: true }, this.errorMessage || 'Välj ett yrke från listan.');
395
+ }
396
+ else {
397
+ this.internals.setValidity({});
398
+ }
399
+ }
400
+ componentDidLoad() {
401
+ this.handleHashFocus();
402
+ }
403
+ disconnectedCallback() {
404
+ if (this.observer) {
405
+ this.observer.disconnect();
406
+ }
407
+ if (this.controller) {
408
+ this.controller.destroy();
409
+ }
410
+ clearTimeout(this.animationTimer);
411
+ this.animationTimer = undefined;
412
+ }
413
+ handleHashFocus() {
414
+ const hash = window.location.hash;
415
+ if (!hash)
416
+ return;
417
+ const targetId = hash.substring(1);
418
+ const hostId = this.hostElement.getAttribute('id');
419
+ if (hostId && targetId === hostId) {
420
+ this.setFocus();
421
+ this.hostElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
422
+ history.replaceState(null, '', window.location.pathname + window.location.search);
423
+ }
424
+ }
425
+ // --- ANVÄNDARINTERAKTIONER OCH LOGIK (Delegerat till Controller via CoreSelector events) ---
426
+ handleInput(event) {
427
+ this.afFocused = true;
428
+ this.controller.handleInput(event.detail);
429
+ }
430
+ handleCsSelect(event) {
431
+ this.afFocused = true;
432
+ const genericItem = event.detail;
433
+ if (this.enableSelectionAnimation) {
434
+ this.animatingItemId = genericItem.id;
435
+ clearTimeout(this.animationTimer);
436
+ this.animationTimer = setTimeout(() => {
437
+ this.animatingItemId = null;
438
+ this.animationTimer = undefined;
439
+ }, 600);
440
+ }
441
+ // Hämta originalobjektet från metadata om det finns, annars bygg ett baserat på genericItem
442
+ const originalItem = genericItem.metadata?.original;
443
+ if (originalItem && 'displayLabel' in originalItem) {
444
+ this.controller.selectItem(originalItem);
445
+ }
446
+ else {
447
+ // Fallback för manuell inmatning eller om metadata saknas
448
+ if (genericItem.metadata?.type === 'ManualInput') {
449
+ this.controller.handleFreeTextSelection(genericItem.label);
450
+ }
451
+ else {
452
+ // Detta borde inte hända med data från suggestions, men för säkerhets skull
453
+ const searchResult = {
454
+ id: genericItem.id,
455
+ type: 'unknown',
456
+ preferred_label: genericItem.label,
457
+ rawLabel: genericItem.label,
458
+ displayLabel: genericItem.label,
459
+ highlightedLabel: genericItem.highlightedLabel || genericItem.label,
460
+ score: 1.0
461
+ };
462
+ this.controller.selectItem(searchResult);
463
+ }
464
+ }
465
+ }
466
+ handleCsClear() {
467
+ this.controller.resetSearch();
468
+ }
469
+ handleCsClearHistory() {
470
+ this.controller.clearHistory();
471
+ }
472
+ handleCsRequestHistory() {
473
+ this.controller.showHistory();
474
+ }
475
+ handleCsLoadMore() {
476
+ this.controller.loadMore();
477
+ }
478
+ handleCsRemoveItem(event) {
479
+ const genericItem = event.detail;
480
+ const originalItem = genericItem.metadata?.original;
481
+ if (originalItem && 'displayLabel' in originalItem) {
482
+ this.controller.removeItem(originalItem);
483
+ }
484
+ }
485
+ handleCsFocus() {
486
+ this.afFocused = true;
487
+ this.controller.handleFocus();
488
+ }
489
+ handleCsBlur() {
490
+ this.afFocused = false;
491
+ this.controller.handleBlur();
492
+ this.validate(); // Ensure events are emitted on blur
493
+ }
494
+ handleCsEditSelection() {
495
+ this.afFocused = true;
496
+ this.controller.editSelection();
497
+ }
498
+ /**
499
+ * Uppdaterar taxonomidatan genom att läsa om från registret.
500
+ * Används efter att registerTaxonomy() har anropats med ny data.
501
+ */
502
+ async updateData() {
503
+ log.debug('[JobSelector] updateData called');
504
+ await this.controller.initialize();
505
+ this.taxonomyData = this.controller.getTaxonomyData();
506
+ this.rebuildValidJobIds();
507
+ this.restoreAcceptedSelectionAfterDataUpdate();
508
+ }
509
+ /**
510
+ * Sätter ett programatiskt val och returnerar YV:s normaliserade, rika selection.
511
+ * Strängar som matchar Concept ID-formatet tolkas som taxonomi-id:n; övriga som fritext.
512
+ */
513
+ async setSelection(input) {
514
+ return this.applySelection(input);
515
+ }
516
+ async setFocus() {
517
+ const coreSelector = this.hostElement.shadowRoot?.querySelector('core-selector');
518
+ if (coreSelector && typeof coreSelector.setFocus === 'function') {
519
+ await coreSelector.setFocus();
520
+ }
521
+ }
522
+ /**
523
+ * Validerar komponenten manuellt.
524
+ * @param forceTouch - Om true, tvingas valideringsfel att visas även om användaren inte interagerat med fältet.
525
+ */
526
+ async validate(forceTouch = false) {
527
+ const isValid = await this.controller.runValidation(forceTouch);
528
+ this.updateValidity(isValid);
529
+ return isValid;
530
+ }
531
+ async checkValidity() {
532
+ return this.internals.checkValidity();
533
+ }
534
+ async reportValidity() {
535
+ return this.internals.reportValidity();
536
+ }
537
+ async getValidity() {
538
+ return this.internals.validity;
539
+ }
540
+ async getValidationMessage() {
541
+ return this.internals.validationMessage;
542
+ }
543
+ updateValidity(isValid) {
544
+ if (!isValid) {
545
+ const message = this.activeErrorType === 'invalidInput'
546
+ ? this.errorMessage || 'Komponenten har fått ett ogiltigt värde.'
547
+ : this.errorMessage || 'Välj ett yrke från listan.';
548
+ this.internals.setValidity({ customError: true }, message);
549
+ }
550
+ else {
551
+ this.internals.setValidity({});
552
+ }
553
+ }
554
+ // --- MAPPING HELPERS ---
555
+ _mapSearchResultToGenericItem(item) {
556
+ const isSelectedResult = this.enableMultiple
557
+ ? (this.state.status === 'multiSelect' || this.state.status === 'multiSelectShowingHistory') &&
558
+ this.state.selectedItems.some(si => {
559
+ if (si.id && item.id)
560
+ return si.id === item.id;
561
+ return si.rawLabel === item.rawLabel;
562
+ })
563
+ : this.state.status === 'itemSelected' && this.state.selectedItem?.id === item.id;
564
+ return {
565
+ id: item.id,
566
+ label: item.displayLabel, // Use the formatted label
567
+ isSelected: this.animatingItemId === item.id || isSelectedResult,
568
+ highlightedLabel: item.highlightedLabel,
569
+ displaySegments: item.displaySegments, // Pass structured segments
570
+ group: item.type === 'occupation-name' ? 'Yrkesgrupper' : 'Yrkesbenämningar',
571
+ metadata: {
572
+ original: item // Store the full object for retrieval
573
+ }
574
+ };
575
+ }
576
+ _mapStateToSelectorState(componentState) {
577
+ let coreStatus = 'loading';
578
+ let inputValue = '';
579
+ let suggestions = [];
580
+ let selectedItem;
581
+ let selectedItems = [];
582
+ let validation = 'idle';
583
+ let message = this.errorMessage || '';
584
+ let validationContext = undefined;
585
+ let listHeader;
586
+ let showHistoryHeader;
587
+ // Map Validation
588
+ if ('validation' in componentState && componentState.validation) {
589
+ validation = componentState.validation;
590
+ if (componentState.validation === 'error') {
591
+ if (this.activeErrorType === 'required') {
592
+ message = this.errorMessage || 'Välj ett yrke från listan.';
593
+ }
594
+ else if (this.activeErrorType === 'invalidInput') {
595
+ message = this.errorMessage || 'Välj ett yrke från listan.';
596
+ }
597
+ else if (this.errorMessage) {
598
+ message = this.errorMessage;
599
+ }
600
+ }
601
+ else if (componentState.validation === 'success') {
602
+ message = 'Tack!';
603
+ }
604
+ }
605
+ if ('message' in componentState && componentState.message) {
606
+ message = componentState.message;
607
+ }
608
+ if ('validationContext' in componentState) {
609
+ validationContext = componentState.validationContext;
610
+ }
611
+ // Map Status & Data
612
+ switch (componentState.status) {
613
+ case 'loading':
614
+ coreStatus = 'loading';
615
+ break;
616
+ case 'error':
617
+ coreStatus = 'error';
618
+ break;
619
+ case 'searching':
620
+ coreStatus = 'searching';
621
+ inputValue = componentState.inputValue;
622
+ suggestions = componentState.suggestions.map(s => this._mapSearchResultToGenericItem(s));
623
+ break;
624
+ case 'showingHistory':
625
+ coreStatus = 'searching';
626
+ inputValue = componentState.inputValue || '';
627
+ suggestions = componentState.historyItems.map(s => this._mapSearchResultToGenericItem(s));
628
+ break;
629
+ case 'itemSelected':
630
+ coreStatus = 'itemSelected';
631
+ selectedItem = this._mapSearchResultToGenericItem(componentState.selectedItem);
632
+ selectedItem.highlightedLabel = styleParentheticalText(selectedItem.label);
633
+ inputValue = componentState.inputValue || '';
634
+ break;
635
+ case 'multiSelect':
636
+ coreStatus = 'multiSelect';
637
+ inputValue = componentState.inputValue;
638
+ suggestions = componentState.suggestions.map(s => this._mapSearchResultToGenericItem(s));
639
+ selectedItems = componentState.selectedItems.map(s => this._mapSearchResultToGenericItem(s));
640
+ break;
641
+ case 'multiSelectShowingHistory':
642
+ coreStatus = 'multiSelect';
643
+ inputValue = componentState.inputValue || '';
644
+ suggestions = componentState.historyItems.map(s => this._mapSearchResultToGenericItem(s));
645
+ selectedItems = componentState.selectedItems.map(s => this._mapSearchResultToGenericItem(s));
646
+ break;
647
+ }
648
+ const isHistoryView = componentState.status === 'showingHistory' || componentState.status === 'multiSelectShowingHistory';
649
+ if (coreStatus === 'multiSelect' || coreStatus === 'itemSelected' || coreStatus === 'searching') {
650
+ // 1. Hide already selected items from the dropdown (YV specific requirement)
651
+ if (selectedItems.length > 0) {
652
+ suggestions = suggestions.filter(s => {
653
+ return !selectedItems.some(si => (si.id && s.id) ? si.id === s.id : si.label === s.label);
654
+ });
655
+ }
656
+ // 1. Set dynamic list header
657
+ if (isHistoryView && suggestions.length > 0) {
658
+ if (componentState.isPopularView) {
659
+ listHeader = 'Populära sökningar';
660
+ showHistoryHeader = false; // Hide Rensa link
661
+ }
662
+ else {
663
+ // Rule: Historik should show "Din privata historik - Rensa".
664
+ // We let CoreSelector's default HistoryHeader handle this
665
+ showHistoryHeader = true;
666
+ listHeader = undefined;
667
+ }
668
+ }
669
+ else {
670
+ // Reset when searching or when no suggestions
671
+ listHeader = undefined;
672
+ showHistoryHeader = false;
673
+ }
674
+ }
675
+ else {
676
+ // Not in a state that should show a list header
677
+ listHeader = undefined;
678
+ showHistoryHeader = false;
679
+ }
680
+ return {
681
+ status: coreStatus,
682
+ inputValue,
683
+ suggestions,
684
+ selectedItem,
685
+ selectedItems,
686
+ validation,
687
+ validationContext,
688
+ message,
689
+ listHeader,
690
+ showHistoryHeader,
691
+ isHistoryView
692
+ };
693
+ }
694
+ render() {
695
+ const selectorState = this._mapStateToSelectorState(this.state);
696
+ // Explicitly construct config to match SelectorConfig interface
697
+ const config = {
698
+ disabled: this.disabled,
699
+ placeholder: this.placeholder,
700
+ minLength: this.minLength,
701
+ maxResults: this.maxResults,
702
+ enableMultiple: this.enableMultiple,
703
+ showSearchIcon: this.showSearchIcon,
704
+ requireSelection: this.requireSelection,
705
+ size: this.size,
706
+ enableSelectionAnimation: this.enableSelectionAnimation,
707
+ populateInputOnSelect: this.populateInputOnSelect,
708
+ maxVisibleRows: this.maxVisibleRows,
709
+ manualInputBehavior: 'select',
710
+ showHistoryHeader: this.history && !selectorState.listHeader, // Only show default if not override
711
+ listHeader: selectorState.listHeader,
712
+ enableInfiniteScroll: true
713
+ };
714
+ // Pass external error message if present and we are not in loading/error state
715
+ // (This logic is now moved to _mapStateToSelectorState to respect validation state)
716
+ return (h("div", { key: '11b208182109be21dcf759010cb87f1bbd10b618', class: "job-selector-wrapper" }, this.state.status === 'error' ? (h("div", { class: "error-message" }, h(AfValidationMessage, { variant: "error", role: "alert" }, this.state.message || 'Ett fel uppstod.'))) : (h(Fragment, null, h("core-selector", { id: this.componentId, componentId: this.componentId, state: selectorState, config: config, label: this.label, labelDescription: this.labelDescription, size: this.size, afFocused: this.afFocused })))));
717
+ }
718
+ static get is() { return "digi-service-job-selector"; }
719
+ static get encapsulation() { return "shadow"; }
720
+ static get delegatesFocus() { return true; }
721
+ static get formAssociated() { return true; }
722
+ static get originalStyleUrls() {
723
+ return {
724
+ "$": ["job-selector.css"]
725
+ };
726
+ }
727
+ static get styleUrls() {
728
+ return {
729
+ "$": ["job-selector.css"]
730
+ };
731
+ }
732
+ static get properties() {
733
+ return {
734
+ "name": {
735
+ "type": "string",
736
+ "mutable": false,
737
+ "complexType": {
738
+ "original": "string",
739
+ "resolved": "string",
740
+ "references": {}
741
+ },
742
+ "required": false,
743
+ "optional": false,
744
+ "docs": {
745
+ "tags": [],
746
+ "text": "HTML-formul\u00E4rets f\u00E4ltnamn. F\u00F6ljer standardattributet `name`, p\u00E5 samma s\u00E4tt som\n`<input name=\"...\">`, och anv\u00E4nds som nyckel n\u00E4r v\u00E4rdet l\u00E4ses via `FormData`."
747
+ },
748
+ "getter": false,
749
+ "setter": false,
750
+ "reflect": true,
751
+ "attribute": "name",
752
+ "defaultValue": "'yrkesvaljaren'"
753
+ },
754
+ "taxonomyUrl": {
755
+ "type": "string",
756
+ "mutable": false,
757
+ "complexType": {
758
+ "original": "string",
759
+ "resolved": "string | undefined",
760
+ "references": {}
761
+ },
762
+ "required": false,
763
+ "optional": true,
764
+ "docs": {
765
+ "tags": [],
766
+ "text": "Fullst\u00E4ndig, explicit URL till en egenhostad datafil.\nUtel\u00E4mna attributet f\u00F6r att anv\u00E4nda senaste data automatiskt."
767
+ },
768
+ "getter": false,
769
+ "setter": false,
770
+ "reflect": true,
771
+ "attribute": "af-taxonomy-url"
772
+ },
773
+ "placeholder": {
774
+ "type": "string",
775
+ "mutable": false,
776
+ "complexType": {
777
+ "original": "string",
778
+ "resolved": "string",
779
+ "references": {}
780
+ },
781
+ "required": false,
782
+ "optional": false,
783
+ "docs": {
784
+ "tags": [],
785
+ "text": ""
786
+ },
787
+ "getter": false,
788
+ "setter": false,
789
+ "reflect": true,
790
+ "attribute": "af-placeholder",
791
+ "defaultValue": "\"T.ex. fris\u00F6r, v\u00E5rdbitr\u00E4de, snickare\""
792
+ },
793
+ "label": {
794
+ "type": "string",
795
+ "mutable": false,
796
+ "complexType": {
797
+ "original": "string",
798
+ "resolved": "string",
799
+ "references": {}
800
+ },
801
+ "required": false,
802
+ "optional": false,
803
+ "docs": {
804
+ "tags": [],
805
+ "text": ""
806
+ },
807
+ "getter": false,
808
+ "setter": false,
809
+ "reflect": true,
810
+ "attribute": "af-label",
811
+ "defaultValue": "'V\u00E4lj yrke'"
812
+ },
813
+ "disabled": {
814
+ "type": "boolean",
815
+ "mutable": false,
816
+ "complexType": {
817
+ "original": "boolean",
818
+ "resolved": "boolean",
819
+ "references": {}
820
+ },
821
+ "required": false,
822
+ "optional": false,
823
+ "docs": {
824
+ "tags": [],
825
+ "text": "Om satt till true kan v\u00E4ljaren inte anv\u00E4ndas med mus eller tangentbord."
826
+ },
827
+ "getter": false,
828
+ "setter": false,
829
+ "reflect": true,
830
+ "attribute": "disabled",
831
+ "defaultValue": "false"
832
+ },
833
+ "enableMultiple": {
834
+ "type": "boolean",
835
+ "mutable": false,
836
+ "complexType": {
837
+ "original": "boolean",
838
+ "resolved": "boolean",
839
+ "references": {}
840
+ },
841
+ "required": false,
842
+ "optional": false,
843
+ "docs": {
844
+ "tags": [],
845
+ "text": ""
846
+ },
847
+ "getter": false,
848
+ "setter": false,
849
+ "reflect": true,
850
+ "attribute": "af-enable-multiple",
851
+ "defaultValue": "false"
852
+ },
853
+ "showSearchIcon": {
854
+ "type": "boolean",
855
+ "mutable": false,
856
+ "complexType": {
857
+ "original": "boolean",
858
+ "resolved": "boolean",
859
+ "references": {}
860
+ },
861
+ "required": false,
862
+ "optional": false,
863
+ "docs": {
864
+ "tags": [],
865
+ "text": ""
866
+ },
867
+ "getter": false,
868
+ "setter": false,
869
+ "reflect": true,
870
+ "attribute": "af-show-search-icon",
871
+ "defaultValue": "true"
872
+ },
873
+ "errorMessage": {
874
+ "type": "string",
875
+ "mutable": false,
876
+ "complexType": {
877
+ "original": "string",
878
+ "resolved": "string | undefined",
879
+ "references": {}
880
+ },
881
+ "required": false,
882
+ "optional": true,
883
+ "docs": {
884
+ "tags": [],
885
+ "text": ""
886
+ },
887
+ "getter": false,
888
+ "setter": false,
889
+ "reflect": true,
890
+ "attribute": "af-error-message"
891
+ },
892
+ "labelDescription": {
893
+ "type": "string",
894
+ "mutable": false,
895
+ "complexType": {
896
+ "original": "string",
897
+ "resolved": "string | undefined",
898
+ "references": {}
899
+ },
900
+ "required": false,
901
+ "optional": true,
902
+ "docs": {
903
+ "tags": [],
904
+ "text": ""
905
+ },
906
+ "getter": false,
907
+ "setter": false,
908
+ "reflect": true,
909
+ "attribute": "af-label-description"
910
+ },
911
+ "required": {
912
+ "type": "boolean",
913
+ "mutable": false,
914
+ "complexType": {
915
+ "original": "boolean",
916
+ "resolved": "boolean",
917
+ "references": {}
918
+ },
919
+ "required": false,
920
+ "optional": false,
921
+ "docs": {
922
+ "tags": [],
923
+ "text": "Om satt till `true` m\u00E5ste f\u00E4ltet ha ett v\u00E4rde f\u00F6r att vara giltigt."
924
+ },
925
+ "getter": false,
926
+ "setter": false,
927
+ "reflect": true,
928
+ "attribute": "af-required",
929
+ "defaultValue": "false"
930
+ },
931
+ "manualInput": {
932
+ "type": "boolean",
933
+ "mutable": false,
934
+ "complexType": {
935
+ "original": "boolean",
936
+ "resolved": "boolean",
937
+ "references": {}
938
+ },
939
+ "required": false,
940
+ "optional": false,
941
+ "docs": {
942
+ "tags": [],
943
+ "text": "Om satt till `true` till\u00E5ts fritext som inte finns i taxonomin."
944
+ },
945
+ "getter": false,
946
+ "setter": false,
947
+ "reflect": true,
948
+ "attribute": "af-manual-input",
949
+ "defaultValue": "false"
950
+ },
951
+ "history": {
952
+ "type": "boolean",
953
+ "mutable": false,
954
+ "complexType": {
955
+ "original": "boolean",
956
+ "resolved": "boolean",
957
+ "references": {}
958
+ },
959
+ "required": false,
960
+ "optional": false,
961
+ "docs": {
962
+ "tags": [],
963
+ "text": "Aktiverar historiken.\nAnv\u00E4nder Local Storage f\u00F6r att spara valda yrken i webbl\u00E4saren.\nIntegritet: Data lagras bara lokalt i webbl\u00E4saren.\nR\u00E4ckvidd: Historiken delas mellan sidor p\u00E5 samma dom\u00E4n."
964
+ },
965
+ "getter": false,
966
+ "setter": false,
967
+ "reflect": true,
968
+ "attribute": "af-history",
969
+ "defaultValue": "true"
970
+ },
971
+ "popular": {
972
+ "type": "boolean",
973
+ "mutable": false,
974
+ "complexType": {
975
+ "original": "boolean",
976
+ "resolved": "boolean",
977
+ "references": {}
978
+ },
979
+ "required": false,
980
+ "optional": false,
981
+ "docs": {
982
+ "tags": [],
983
+ "text": "Om satt till true visas popul\u00E4ra yrken initialt n\u00E4r historiken \u00E4r tom."
984
+ },
985
+ "getter": false,
986
+ "setter": false,
987
+ "reflect": true,
988
+ "attribute": "af-popular",
989
+ "defaultValue": "true"
990
+ },
991
+ "size": {
992
+ "type": "string",
993
+ "mutable": false,
994
+ "complexType": {
995
+ "original": "'small' | 'medium' | 'large'",
996
+ "resolved": "\"large\" | \"medium\" | \"small\"",
997
+ "references": {}
998
+ },
999
+ "required": false,
1000
+ "optional": false,
1001
+ "docs": {
1002
+ "tags": [],
1003
+ "text": "Storleken p\u00E5 inputf\u00E4ltet.\n'small' | 'medium' | 'large'"
1004
+ },
1005
+ "getter": false,
1006
+ "setter": false,
1007
+ "reflect": true,
1008
+ "attribute": "af-size",
1009
+ "defaultValue": "'medium'"
1010
+ },
1011
+ "taxonomyData": {
1012
+ "type": "unknown",
1013
+ "mutable": true,
1014
+ "complexType": {
1015
+ "original": "TaxonomyItem[]",
1016
+ "resolved": "TaxonomyItem[]",
1017
+ "references": {
1018
+ "TaxonomyItem": {
1019
+ "location": "import",
1020
+ "path": "./types",
1021
+ "id": "src/components/job-selector/types.ts::TaxonomyItem",
1022
+ "referenceLocation": "TaxonomyItem"
1023
+ }
1024
+ }
1025
+ },
1026
+ "required": false,
1027
+ "optional": false,
1028
+ "docs": {
1029
+ "tags": [],
1030
+ "text": "Yrkesdata (taxonomi). Kan injiceras av integrat\u00F6ren f\u00F6r deterministisk laddning (t.ex. i testmilj\u00F6er). \nOm den l\u00E4mnas tom populeras den internt via det globala registret eller auto-fetch f\u00F6r att exponera aktuell data."
1031
+ },
1032
+ "getter": false,
1033
+ "setter": false,
1034
+ "defaultValue": "[]"
1035
+ }
1036
+ };
1037
+ }
1038
+ static get states() {
1039
+ return {
1040
+ "afFocused": {},
1041
+ "state": {},
1042
+ "activeErrorType": {},
1043
+ "animatingItemId": {}
1044
+ };
1045
+ }
1046
+ static get events() {
1047
+ return [{
1048
+ "method": "afJobSelectedEmitter",
1049
+ "name": "afJobSelected",
1050
+ "bubbles": true,
1051
+ "cancelable": true,
1052
+ "composed": true,
1053
+ "docs": {
1054
+ "tags": [],
1055
+ "text": ""
1056
+ },
1057
+ "complexType": {
1058
+ "original": "JobSelectionItem[]",
1059
+ "resolved": "JobSelectionItem[]",
1060
+ "references": {
1061
+ "JobSelectionItem": {
1062
+ "location": "import",
1063
+ "path": "./types",
1064
+ "id": "src/components/job-selector/types.ts::JobSelectionItem",
1065
+ "referenceLocation": "JobSelectionItem"
1066
+ }
1067
+ }
1068
+ }
1069
+ }, {
1070
+ "method": "dataLoadFailedEmitter",
1071
+ "name": "afJobDataLoadFailed",
1072
+ "bubbles": true,
1073
+ "cancelable": true,
1074
+ "composed": true,
1075
+ "docs": {
1076
+ "tags": [],
1077
+ "text": ""
1078
+ },
1079
+ "complexType": {
1080
+ "original": "{ error: Error }",
1081
+ "resolved": "{ error: Error; }",
1082
+ "references": {
1083
+ "Error": {
1084
+ "location": "global",
1085
+ "id": "global::Error"
1086
+ }
1087
+ }
1088
+ }
1089
+ }, {
1090
+ "method": "validationChangeEmitter",
1091
+ "name": "afJobValidationChange",
1092
+ "bubbles": true,
1093
+ "cancelable": true,
1094
+ "composed": true,
1095
+ "docs": {
1096
+ "tags": [],
1097
+ "text": ""
1098
+ },
1099
+ "complexType": {
1100
+ "original": "{\n isValid: boolean;\n errorType: 'required' | 'invalidInput' | null;\n }",
1101
+ "resolved": "{ isValid: boolean; errorType: \"required\" | \"invalidInput\" | null; }",
1102
+ "references": {}
1103
+ }
1104
+ }];
1105
+ }
1106
+ static get methods() {
1107
+ return {
1108
+ "updateData": {
1109
+ "complexType": {
1110
+ "signature": "() => Promise<void>",
1111
+ "parameters": [],
1112
+ "references": {
1113
+ "Promise": {
1114
+ "location": "global",
1115
+ "id": "global::Promise"
1116
+ }
1117
+ },
1118
+ "return": "Promise<void>"
1119
+ },
1120
+ "docs": {
1121
+ "text": "Uppdaterar taxonomidatan genom att l\u00E4sa om fr\u00E5n registret.\nAnv\u00E4nds efter att registerTaxonomy() har anropats med ny data.",
1122
+ "tags": []
1123
+ }
1124
+ },
1125
+ "setSelection": {
1126
+ "complexType": {
1127
+ "signature": "(input: JobSelectionInput) => Promise<JobSelectionItem[]>",
1128
+ "parameters": [{
1129
+ "name": "input",
1130
+ "type": "string | string[] | JobSelectionItem[]",
1131
+ "docs": ""
1132
+ }],
1133
+ "references": {
1134
+ "Promise": {
1135
+ "location": "global",
1136
+ "id": "global::Promise"
1137
+ },
1138
+ "JobSelectionItem": {
1139
+ "location": "import",
1140
+ "path": "./types",
1141
+ "id": "src/components/job-selector/types.ts::JobSelectionItem",
1142
+ "referenceLocation": "JobSelectionItem"
1143
+ },
1144
+ "JobSelectionInput": {
1145
+ "location": "import",
1146
+ "path": "./types",
1147
+ "id": "src/components/job-selector/types.ts::JobSelectionInput",
1148
+ "referenceLocation": "JobSelectionInput"
1149
+ }
1150
+ },
1151
+ "return": "Promise<JobSelectionItem[]>"
1152
+ },
1153
+ "docs": {
1154
+ "text": "S\u00E4tter ett programatiskt val och returnerar YV:s normaliserade, rika selection.\nStr\u00E4ngar som matchar Concept ID-formatet tolkas som taxonomi-id:n; \u00F6vriga som fritext.",
1155
+ "tags": []
1156
+ }
1157
+ },
1158
+ "setFocus": {
1159
+ "complexType": {
1160
+ "signature": "() => Promise<void>",
1161
+ "parameters": [],
1162
+ "references": {
1163
+ "Promise": {
1164
+ "location": "global",
1165
+ "id": "global::Promise"
1166
+ },
1167
+ "HTMLCoreSelectorElement": {
1168
+ "location": "global",
1169
+ "id": "global::HTMLCoreSelectorElement"
1170
+ }
1171
+ },
1172
+ "return": "Promise<void>"
1173
+ },
1174
+ "docs": {
1175
+ "text": "",
1176
+ "tags": []
1177
+ }
1178
+ },
1179
+ "validate": {
1180
+ "complexType": {
1181
+ "signature": "(forceTouch?: boolean) => Promise<boolean>",
1182
+ "parameters": [{
1183
+ "name": "forceTouch",
1184
+ "type": "boolean",
1185
+ "docs": "- Om true, tvingas valideringsfel att visas \u00E4ven om anv\u00E4ndaren inte interagerat med f\u00E4ltet."
1186
+ }],
1187
+ "references": {
1188
+ "Promise": {
1189
+ "location": "global",
1190
+ "id": "global::Promise"
1191
+ }
1192
+ },
1193
+ "return": "Promise<boolean>"
1194
+ },
1195
+ "docs": {
1196
+ "text": "Validerar komponenten manuellt.",
1197
+ "tags": [{
1198
+ "name": "param",
1199
+ "text": "forceTouch - Om true, tvingas valideringsfel att visas \u00E4ven om anv\u00E4ndaren inte interagerat med f\u00E4ltet."
1200
+ }]
1201
+ }
1202
+ },
1203
+ "checkValidity": {
1204
+ "complexType": {
1205
+ "signature": "() => Promise<boolean>",
1206
+ "parameters": [],
1207
+ "references": {
1208
+ "Promise": {
1209
+ "location": "global",
1210
+ "id": "global::Promise"
1211
+ }
1212
+ },
1213
+ "return": "Promise<boolean>"
1214
+ },
1215
+ "docs": {
1216
+ "text": "",
1217
+ "tags": []
1218
+ }
1219
+ },
1220
+ "reportValidity": {
1221
+ "complexType": {
1222
+ "signature": "() => Promise<boolean>",
1223
+ "parameters": [],
1224
+ "references": {
1225
+ "Promise": {
1226
+ "location": "global",
1227
+ "id": "global::Promise"
1228
+ }
1229
+ },
1230
+ "return": "Promise<boolean>"
1231
+ },
1232
+ "docs": {
1233
+ "text": "",
1234
+ "tags": []
1235
+ }
1236
+ },
1237
+ "getValidity": {
1238
+ "complexType": {
1239
+ "signature": "() => Promise<ValidityState>",
1240
+ "parameters": [],
1241
+ "references": {
1242
+ "Promise": {
1243
+ "location": "global",
1244
+ "id": "global::Promise"
1245
+ },
1246
+ "ValidityState": {
1247
+ "location": "global",
1248
+ "id": "global::ValidityState"
1249
+ }
1250
+ },
1251
+ "return": "Promise<ValidityState>"
1252
+ },
1253
+ "docs": {
1254
+ "text": "",
1255
+ "tags": []
1256
+ }
1257
+ },
1258
+ "getValidationMessage": {
1259
+ "complexType": {
1260
+ "signature": "() => Promise<string>",
1261
+ "parameters": [],
1262
+ "references": {
1263
+ "Promise": {
1264
+ "location": "global",
1265
+ "id": "global::Promise"
1266
+ }
1267
+ },
1268
+ "return": "Promise<string>"
1269
+ },
1270
+ "docs": {
1271
+ "text": "",
1272
+ "tags": []
1273
+ }
1274
+ }
1275
+ };
1276
+ }
1277
+ static get elementRef() { return "hostElement"; }
1278
+ static get watchers() {
1279
+ return [{
1280
+ "propName": "required",
1281
+ "methodName": "watchRequired"
1282
+ }, {
1283
+ "propName": "manualInput",
1284
+ "methodName": "watchManualInput"
1285
+ }, {
1286
+ "propName": "history",
1287
+ "methodName": "watchHistory"
1288
+ }, {
1289
+ "propName": "popular",
1290
+ "methodName": "watchPopular"
1291
+ }, {
1292
+ "propName": "enableMultiple",
1293
+ "methodName": "watchEnableMultiple"
1294
+ }, {
1295
+ "propName": "disabled",
1296
+ "methodName": "watchDisabled"
1297
+ }, {
1298
+ "propName": "taxonomyData",
1299
+ "methodName": "watchTaxonomyData"
1300
+ }];
1301
+ }
1302
+ static get listeners() {
1303
+ return [{
1304
+ "name": "afJobValidationChange",
1305
+ "method": "handleFormValidationSync",
1306
+ "target": undefined,
1307
+ "capture": false,
1308
+ "passive": false
1309
+ }, {
1310
+ "name": "hashchange",
1311
+ "method": "handleHashFocus",
1312
+ "target": "window",
1313
+ "capture": false,
1314
+ "passive": false
1315
+ }, {
1316
+ "name": "csInput",
1317
+ "method": "handleInput",
1318
+ "target": undefined,
1319
+ "capture": false,
1320
+ "passive": false
1321
+ }, {
1322
+ "name": "csSelect",
1323
+ "method": "handleCsSelect",
1324
+ "target": undefined,
1325
+ "capture": false,
1326
+ "passive": false
1327
+ }, {
1328
+ "name": "csClear",
1329
+ "method": "handleCsClear",
1330
+ "target": undefined,
1331
+ "capture": false,
1332
+ "passive": false
1333
+ }, {
1334
+ "name": "csClearHistory",
1335
+ "method": "handleCsClearHistory",
1336
+ "target": undefined,
1337
+ "capture": false,
1338
+ "passive": false
1339
+ }, {
1340
+ "name": "csRequestHistory",
1341
+ "method": "handleCsRequestHistory",
1342
+ "target": undefined,
1343
+ "capture": false,
1344
+ "passive": false
1345
+ }, {
1346
+ "name": "csLoadMore",
1347
+ "method": "handleCsLoadMore",
1348
+ "target": undefined,
1349
+ "capture": false,
1350
+ "passive": false
1351
+ }, {
1352
+ "name": "csRemoveItem",
1353
+ "method": "handleCsRemoveItem",
1354
+ "target": undefined,
1355
+ "capture": false,
1356
+ "passive": false
1357
+ }, {
1358
+ "name": "csFocus",
1359
+ "method": "handleCsFocus",
1360
+ "target": undefined,
1361
+ "capture": false,
1362
+ "passive": false
1363
+ }, {
1364
+ "name": "csBlur",
1365
+ "method": "handleCsBlur",
1366
+ "target": undefined,
1367
+ "capture": false,
1368
+ "passive": false
1369
+ }, {
1370
+ "name": "csEditSelection",
1371
+ "method": "handleCsEditSelection",
1372
+ "target": undefined,
1373
+ "capture": false,
1374
+ "passive": false
1375
+ }];
1376
+ }
1377
+ static get attachInternalsMemberName() { return "internals"; }
1378
+ }