@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 @@
1
+ export {};
@@ -0,0 +1,13 @@
1
+ import { h } from "@stencil/core";
2
+ const svgMarkup = {
3
+ search: '<svg width="26" height="26" viewBox="0 0 26 26" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" style="width:100%;height:100%"><path d="M25.645 22.437l-5.063-5.053a1.22 1.22 0 00-.864-.355h-.827a10.477 10.477 0 002.234-6.487C21.125 4.719 16.397 0 10.562 0 4.729 0 0 4.719 0 10.542c0 5.823 4.728 10.542 10.563 10.542 2.452 0 4.707-.831 6.5-2.23v.826c0 .324.126.633.355.862l5.063 5.053c.477.476 1.25.476 1.721 0l1.437-1.435a1.22 1.22 0 00.006-1.723zm-15.082-5.408c-3.59 0-6.5-2.899-6.5-6.487a6.49 6.49 0 016.5-6.487c3.59 0 6.5 2.899 6.5 6.487a6.49 6.49 0 01-6.5 6.487z" fill="currentColor" fill-rule="nonzero"/></svg>',
4
+ check: '<svg width="23" height="18" viewBox="0 0 23 18" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" style="width:100%;height:100%"><path d="M20.547.18L7.315 13.627 2.453 8.686a.597.597 0 00-.853 0L.177 10.132a.62.62 0 000 .868l6.711 6.82c.236.24.618.24.854 0L22.823 2.493a.62.62 0 000-.867L21.4.18a.597.597 0 00-.853 0z" fill="currentColor"/></svg>',
5
+ x: '<svg width="12" height="12" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" style="width:100%;height:100%"><g stroke="currentColor" stroke-width="2" fill="none" fill-rule="evenodd" stroke-linecap="square"><path d="M2 2l7.986 7.986M9.986 2L2 9.986"/></g></svg>',
6
+ };
7
+ export const AfIcon = ({ name, class: className }) => (h("span", { class: className, "aria-hidden": "true", innerHTML: svgMarkup[name], style: {
8
+ display: 'flex',
9
+ width: className ? undefined : '12px',
10
+ height: className ? undefined : '12px',
11
+ minHeight: '1.4em',
12
+ alignItems: 'center',
13
+ } }));
@@ -0,0 +1,2 @@
1
+ import { h } from "@stencil/core";
2
+ export const AfLabel = ({ label, forId, description, required = false }) => (h("div", { class: "af-form-label" }, h("label", { class: "af-form-label__label", htmlFor: forId }, label, required ? ' *' : ''), description && h("span", { class: "af-form-label__description" }, description)));
@@ -0,0 +1,6 @@
1
+ import { h } from "@stencil/core";
2
+ import { AfIcon } from "./af-icon";
3
+ export const AfTag = ({ text, breakWords = false, onRemove }) => (h("span", { class: "af-tag" }, h("button", { type: "button", class: "af-tag__button", "aria-label": `Ta bort ${text}`, onClick: (event) => {
4
+ event.preventDefault();
5
+ onRemove();
6
+ } }, h("span", { class: { 'af-tag__text': true, 'af-tag__text--break': breakWords } }, text), h(AfIcon, { name: "x", class: "af-tag__remove-icon" }))));
@@ -0,0 +1,11 @@
1
+ import { h } from "@stencil/core";
2
+ export const AfValidationMessage = (props, children) => {
3
+ const extraClass = props.class || '';
4
+ return (h("div", { id: props.id, role: props.role, class: { [extraClass]: !!extraClass } }, h("div", { class: {
5
+ 'af-validation-message': true,
6
+ [`af-validation-message--${props.variant}`]: true,
7
+ } }, h("span", { class: {
8
+ 'af-validation-message__icon': true,
9
+ [`af-validation-message__icon--${props.variant}`]: true,
10
+ }, "aria-hidden": "true" }), h("span", { class: "af-validation-message__text" }, children))));
11
+ };
@@ -0,0 +1,4 @@
1
+ export { AfIcon } from './af-icon';
2
+ export { AfLabel } from './af-label';
3
+ export { AfTag } from './af-tag';
4
+ export { AfValidationMessage } from './af-validation-message';
@@ -0,0 +1,21 @@
1
+ :host {
2
+ display: block;
3
+ width: 100%;
4
+ }
5
+
6
+ .geo-valjaren-wrapper {
7
+ position: relative;
8
+ width: 100%;
9
+ }
10
+
11
+ .history-header {
12
+ user-select: none;
13
+ }
14
+
15
+ .history-header a {
16
+ cursor: pointer;
17
+ }
18
+
19
+ .history-header a:hover {
20
+ text-decoration: underline !important;
21
+ }
@@ -0,0 +1,304 @@
1
+ import { h } from "@stencil/core";
2
+ import { GeoDataService } from "./logic/data.utils";
3
+ import { SearchEngine } from "./logic/search.engine";
4
+ import { GeoHistoryService } from "./logic/history-service";
5
+ export class Geovaljaren {
6
+ label = 'Din kommun';
7
+ placeholder = 'Vilken kommun bor du i?';
8
+ selectorState = {
9
+ status: 'loading',
10
+ inputValue: '',
11
+ suggestions: [],
12
+ selectedItems: [],
13
+ validation: 'idle'
14
+ };
15
+ selectionEmitter;
16
+ config = {
17
+ placeholder: this.placeholder,
18
+ minLength: 1,
19
+ maxResults: 10,
20
+ enableMultiple: false,
21
+ showSearchIcon: true,
22
+ requireSelection: true,
23
+ size: 'medium',
24
+ enableToast: false,
25
+ showHistoryHeader: true
26
+ };
27
+ engine = null;
28
+ allGeoItems = [];
29
+ async componentWillLoad() {
30
+ this.selectorState = { ...this.selectorState, status: 'loading' };
31
+ try {
32
+ this.allGeoItems = await GeoDataService.fetchAll();
33
+ this.engine = new SearchEngine(this.allGeoItems);
34
+ this.selectorState = { ...this.selectorState, status: 'searching' };
35
+ }
36
+ catch (err) {
37
+ console.error('Failed to load geo data', err);
38
+ this.selectorState = { ...this.selectorState, status: 'error', message: 'Kunde inte ladda kommuner.' };
39
+ }
40
+ }
41
+ showHistory() {
42
+ if (!this.allGeoItems.length)
43
+ return;
44
+ // Don't show history if there's typed text
45
+ if (this.selectorState.inputValue.trim() !== '')
46
+ return;
47
+ const historyItems = GeoHistoryService.loadHistory();
48
+ // Lazy Validation: Only show items that still exist in our current dataset
49
+ const validatedHistory = historyItems.filter(hItem => this.allGeoItems.some(item => item.id === hItem.id));
50
+ this.selectorState = {
51
+ ...this.selectorState,
52
+ status: 'searching',
53
+ suggestions: validatedHistory,
54
+ inputValue: this.selectorState.inputValue
55
+ };
56
+ }
57
+ addToHistory(item) {
58
+ const currentHistory = GeoHistoryService.loadHistory();
59
+ const filtered = currentHistory.filter(h => h.id !== item.id);
60
+ const newHistory = [item, ...filtered].slice(0, 10);
61
+ GeoHistoryService.saveHistory(newHistory);
62
+ }
63
+ clearHistory() {
64
+ GeoHistoryService.clearHistory();
65
+ this.selectorState = {
66
+ ...this.selectorState,
67
+ suggestions: []
68
+ };
69
+ }
70
+ handleInput(event) {
71
+ const query = event.detail;
72
+ if (!this.engine)
73
+ return;
74
+ if (query.trim() === '') {
75
+ this.selectorState = { ...this.selectorState, inputValue: '' };
76
+ this.showHistory();
77
+ return;
78
+ }
79
+ const suggestions = query.length >= this.config.minLength ? this.engine.search(query) : [];
80
+ const wasInError = this.selectorState.validation === 'error' || this.selectorState.validationContext === 'from-error';
81
+ this.selectorState = {
82
+ ...this.selectorState,
83
+ inputValue: query,
84
+ suggestions,
85
+ status: 'searching',
86
+ validation: 'idle',
87
+ validationContext: wasInError ? 'from-error' : undefined
88
+ };
89
+ }
90
+ handleFocus() {
91
+ // Handled by csRequestHistory
92
+ }
93
+ handleRequestHistory() {
94
+ this.showHistory();
95
+ }
96
+ handleSelect(event) {
97
+ const selection = event.detail;
98
+ const wasInError = this.selectorState.validation === 'error' || this.selectorState.validationContext === 'from-error';
99
+ this.addToHistory(selection);
100
+ this.selectorState = {
101
+ ...this.selectorState,
102
+ status: 'itemSelected',
103
+ selectedItem: selection,
104
+ suggestions: [],
105
+ inputValue: selection.label, // Keep label as input value
106
+ validation: wasInError ? 'success' : 'idle',
107
+ message: wasInError ? 'Klart!' : '',
108
+ validationContext: wasInError ? 'from-error' : undefined
109
+ };
110
+ this.selectionEmitter.emit(selection);
111
+ if (wasInError) {
112
+ setTimeout(() => {
113
+ if (this.selectorState.status === 'itemSelected' && this.selectorState.validation === 'success') {
114
+ this.selectorState = { ...this.selectorState, validation: 'idle' };
115
+ }
116
+ }, 3000);
117
+ }
118
+ }
119
+ handleBlur() {
120
+ if (this.selectorState.status === 'searching' && this.selectorState.inputValue.trim() !== '') {
121
+ const suggestions = this.selectorState.suggestions;
122
+ if (suggestions.length === 0) {
123
+ this.selectorState = {
124
+ ...this.selectorState,
125
+ validation: 'error',
126
+ message: 'Du måste välja ett alternativ från listan.'
127
+ };
128
+ }
129
+ }
130
+ }
131
+ handleClear() {
132
+ // Only clear if explicitly triggered by the X button
133
+ this.selectorState = {
134
+ ...this.selectorState,
135
+ status: 'searching',
136
+ inputValue: '',
137
+ suggestions: [],
138
+ selectedItem: undefined,
139
+ validation: 'idle',
140
+ validationContext: undefined
141
+ };
142
+ this.showHistory();
143
+ }
144
+ handleCsClearHistory() {
145
+ this.clearHistory();
146
+ }
147
+ handleEdit() {
148
+ const wasInError = this.selectorState.validation === 'error' || this.selectorState.validationContext === 'from-error';
149
+ // When clicking to edit, keep the label of the item as search text
150
+ const currentLabel = this.selectorState.selectedItem?.label || '';
151
+ this.selectorState = {
152
+ ...this.selectorState,
153
+ status: 'searching',
154
+ inputValue: currentLabel,
155
+ suggestions: [],
156
+ validation: 'idle',
157
+ validationContext: wasInError ? 'from-error' : undefined
158
+ };
159
+ // Don't show history if we have text
160
+ if (currentLabel === '') {
161
+ this.showHistory();
162
+ }
163
+ }
164
+ render() {
165
+ return (h("div", { key: '5f7e4f0fb8fa815145ddd5690894ec7906dc2f1a', class: "geo-valjaren-wrapper" }, h("core-selector", { key: '73940c1ae5092f5f5f84bbd53edc6112e4374310', state: this.selectorState, config: { ...this.config, placeholder: this.placeholder }, label: this.label, labelDescription: "Ange den kommun du bor i f\u00F6r att se pendlingsalternativ" })));
166
+ }
167
+ static get is() { return "geo-valjaren"; }
168
+ static get encapsulation() { return "shadow"; }
169
+ static get originalStyleUrls() {
170
+ return {
171
+ "$": ["geovaljaren.css"]
172
+ };
173
+ }
174
+ static get styleUrls() {
175
+ return {
176
+ "$": ["geovaljaren.css"]
177
+ };
178
+ }
179
+ static get properties() {
180
+ return {
181
+ "label": {
182
+ "type": "string",
183
+ "mutable": false,
184
+ "complexType": {
185
+ "original": "string",
186
+ "resolved": "string",
187
+ "references": {}
188
+ },
189
+ "required": false,
190
+ "optional": false,
191
+ "docs": {
192
+ "tags": [],
193
+ "text": ""
194
+ },
195
+ "getter": false,
196
+ "setter": false,
197
+ "reflect": true,
198
+ "attribute": "af-label",
199
+ "defaultValue": "'Din kommun'"
200
+ },
201
+ "placeholder": {
202
+ "type": "string",
203
+ "mutable": false,
204
+ "complexType": {
205
+ "original": "string",
206
+ "resolved": "string",
207
+ "references": {}
208
+ },
209
+ "required": false,
210
+ "optional": false,
211
+ "docs": {
212
+ "tags": [],
213
+ "text": ""
214
+ },
215
+ "getter": false,
216
+ "setter": false,
217
+ "reflect": true,
218
+ "attribute": "af-placeholder",
219
+ "defaultValue": "'Vilken kommun bor du i?'"
220
+ }
221
+ };
222
+ }
223
+ static get states() {
224
+ return {
225
+ "selectorState": {}
226
+ };
227
+ }
228
+ static get events() {
229
+ return [{
230
+ "method": "selectionEmitter",
231
+ "name": "afGeoSelected",
232
+ "bubbles": true,
233
+ "cancelable": true,
234
+ "composed": true,
235
+ "docs": {
236
+ "tags": [],
237
+ "text": ""
238
+ },
239
+ "complexType": {
240
+ "original": "GenericItem | null",
241
+ "resolved": "GenericItem<unknown> | null",
242
+ "references": {
243
+ "GenericItem": {
244
+ "location": "import",
245
+ "path": "../core-selector/types",
246
+ "id": "src/components/core-selector/types.ts::GenericItem",
247
+ "referenceLocation": "GenericItem"
248
+ }
249
+ }
250
+ }
251
+ }];
252
+ }
253
+ static get listeners() {
254
+ return [{
255
+ "name": "csInput",
256
+ "method": "handleInput",
257
+ "target": undefined,
258
+ "capture": false,
259
+ "passive": false
260
+ }, {
261
+ "name": "csFocus",
262
+ "method": "handleFocus",
263
+ "target": undefined,
264
+ "capture": false,
265
+ "passive": false
266
+ }, {
267
+ "name": "csRequestHistory",
268
+ "method": "handleRequestHistory",
269
+ "target": undefined,
270
+ "capture": false,
271
+ "passive": false
272
+ }, {
273
+ "name": "csSelect",
274
+ "method": "handleSelect",
275
+ "target": undefined,
276
+ "capture": false,
277
+ "passive": false
278
+ }, {
279
+ "name": "csBlur",
280
+ "method": "handleBlur",
281
+ "target": undefined,
282
+ "capture": false,
283
+ "passive": false
284
+ }, {
285
+ "name": "csClear",
286
+ "method": "handleClear",
287
+ "target": undefined,
288
+ "capture": false,
289
+ "passive": false
290
+ }, {
291
+ "name": "csClearHistory",
292
+ "method": "handleCsClearHistory",
293
+ "target": undefined,
294
+ "capture": false,
295
+ "passive": false
296
+ }, {
297
+ "name": "csEditSelection",
298
+ "method": "handleEdit",
299
+ "target": undefined,
300
+ "capture": false,
301
+ "passive": false
302
+ }];
303
+ }
304
+ }
@@ -0,0 +1,20 @@
1
+ export const GeoAdapter = {
2
+ /**
3
+ * Transforms the pendlingskommuner JSON data structure into GenericItems.
4
+ */
5
+ toGenericItems: (jsonData) => {
6
+ if (!jsonData || !jsonData.data) {
7
+ console.warn("Invalid JSON data structure for GeoAdapter");
8
+ return [];
9
+ }
10
+ // jsonData.data is an object where keys are IDs
11
+ return Object.entries(jsonData.data).map(([id, value]) => {
12
+ return {
13
+ id: id,
14
+ label: value.preferred_label || 'Okänd kommun',
15
+ description: `Kommun-ID: ${id}`, // We don't have much else to show right now
16
+ metadata: value // Keep original for destinations access later if needed
17
+ };
18
+ });
19
+ }
20
+ };
@@ -0,0 +1,12 @@
1
+ import geoData from "../assets/pendlingskommuner.json";
2
+ import { GeoAdapter } from "./adapter.utils";
3
+ export const GeoDataService = {
4
+ /**
5
+ * Simulates an async fetch of geo data
6
+ */
7
+ fetchAll: async () => {
8
+ // Simulate network delay
9
+ await new Promise(resolve => setTimeout(resolve, 500));
10
+ return GeoAdapter.toGenericItems(geoData);
11
+ }
12
+ };
@@ -0,0 +1,25 @@
1
+ export const GeoHistoryService = {
2
+ saveHistory(history) {
3
+ try {
4
+ localStorage.setItem('geovaljaren_history', JSON.stringify(history));
5
+ }
6
+ catch (e) {
7
+ console.error('[GeoHistoryService] Kunde inte spara historik.', e);
8
+ }
9
+ },
10
+ loadHistory() {
11
+ try {
12
+ const historyString = localStorage.getItem('geovaljaren_history');
13
+ if (historyString) {
14
+ return JSON.parse(historyString);
15
+ }
16
+ }
17
+ catch (e) {
18
+ console.error('[GeoHistoryService] Kunde inte läsa historik.', e);
19
+ }
20
+ return [];
21
+ },
22
+ clearHistory() {
23
+ localStorage.removeItem('geovaljaren_history');
24
+ }
25
+ };
@@ -0,0 +1,28 @@
1
+ import Fuse from "fuse.js";
2
+ const FUSE_OPTIONS = {
3
+ includeScore: true,
4
+ threshold: 0.3,
5
+ keys: [
6
+ { name: 'label', weight: 2 },
7
+ { name: 'description', weight: 1 }
8
+ ],
9
+ shouldSort: true,
10
+ minMatchCharLength: 1,
11
+ };
12
+ export class SearchEngine {
13
+ fuse = null;
14
+ constructor(data) {
15
+ this.updateIndex(data);
16
+ }
17
+ search(query, maxResults = 10) {
18
+ if (!this.fuse || !query)
19
+ return [];
20
+ // Fuse search
21
+ const results = this.fuse.search(query);
22
+ // Map back to item
23
+ return results.map(r => r.item).slice(0, maxResults);
24
+ }
25
+ updateIndex(newData) {
26
+ this.fuse = new Fuse(newData, FUSE_OPTIONS);
27
+ }
28
+ }
@@ -0,0 +1,26 @@
1
+ // Minimum number of characters required to trigger search
2
+ export const MIN_INPUT_LENGTH = 1;
3
+ // Debounce time for search input (ms)
4
+ export const DEBOUNCE_TIME = 100; // ms - Återställt till 250 baserat på sok-box.tsx initialt
5
+ // Default maximum number of results to display
6
+ export const MAX_RESULTS = 10;
7
+ // Configuration for Fuse.js
8
+ export const FUSE_OPTIONS = {
9
+ keys: ['preferred_label'], // Ändrat från 'label' till 'preferred_label' för att matcha datastrukturen
10
+ includeScore: true,
11
+ ignoreDiacritics: true, // Lägg till för att matcha t.ex. vard -> vård
12
+ ignoreFieldNorm: true, // Lägg till för att ignorera fältlängd vid poängsättning
13
+ threshold: 0.6, // Behåll 0.6
14
+ minMatchCharLength: MIN_INPUT_LENGTH, // Refererar till konstant ovan
15
+ ignoreLocation: true, // Match anywhere in the string
16
+ distance: 100, // Allow matches further apart
17
+ };
18
+ // UI Text Strings
19
+ export const PLACEHOLDER_TEXT = "T.ex. frisör, vårdbiträde, snickare"; // Uppdaterad standardplaceholder enligt Figma-design
20
+ export const NO_RESULTS_TEXT = "Inga resultat matchade din sökning.";
21
+ export const VALIDATION_ERROR_REQUIRED_FIELD = "Du måste göra ett val";
22
+ export const VALIDATION_ERROR_EMPTY_FIELD = "Välj ett yrke från listan";
23
+ // Nya konstanter för aria-label
24
+ export const CLEAR_BUTTON_LABEL_TEXT = "Rensa sökfält";
25
+ export const REMOVE_ITEM_LABEL_TEXT_PREFIX = "Ta bort";
26
+ export const POSITIVE_VALIDATION_TEXT = "Valt yrke";
@@ -0,0 +1,41 @@
1
+ import log from "loglevel";
2
+ let memoryCache = null;
3
+ export const HistoryService = {
4
+ saveHistory(history) {
5
+ memoryCache = history; // Omedelbar uppdatering av UI-state
6
+ try {
7
+ localStorage.setItem('job-selector_history', JSON.stringify(history));
8
+ log.debug('[HistoryService] saveHistory - History saved.');
9
+ }
10
+ catch (e) {
11
+ log.error('Kunde inte spara historik.', e);
12
+ }
13
+ },
14
+ loadHistory() {
15
+ if (memoryCache) {
16
+ return memoryCache;
17
+ }
18
+ try {
19
+ const historyString = localStorage.getItem('job-selector_history');
20
+ if (historyString) {
21
+ memoryCache = JSON.parse(historyString);
22
+ return memoryCache;
23
+ }
24
+ }
25
+ catch (e) {
26
+ log.error('Kunde inte läsa historik.', e);
27
+ }
28
+ memoryCache = [];
29
+ return memoryCache;
30
+ },
31
+ clearHistory() {
32
+ memoryCache = [];
33
+ try {
34
+ localStorage.removeItem('job-selector_history');
35
+ log.debug('[HistoryService] clearHistory - History cleared.');
36
+ }
37
+ catch (e) {
38
+ log.error('Kunde inte rensa historik.', e);
39
+ }
40
+ }
41
+ };