@pro6pp/infer-js 0.0.2-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +104 -0
- package/dist/index.d.mts +20 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.global.js +366 -0
- package/dist/index.mjs +156 -0
- package/package.json +33 -0
package/README.md
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# @pro6pp/infer-js
|
|
2
|
+
|
|
3
|
+
The official Vanilla JS SDK for the [Pro6PP Infer API](https://www.pro6pp.com/developer/infer/nl/parameters).
|
|
4
|
+
A library that adds address autocompletion to any HTML input field.
|
|
5
|
+
|
|
6
|
+
## Installation
|
|
7
|
+
|
|
8
|
+
### Option 1: CDN
|
|
9
|
+
|
|
10
|
+
Simply add this script to your HTML file. It exposes a global `Pro6PP` variable.
|
|
11
|
+
|
|
12
|
+
```html
|
|
13
|
+
// TODO: add CDN src
|
|
14
|
+
<script src=""></script>
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
### Option 2: NPM
|
|
18
|
+
|
|
19
|
+
If you are using Webpack, Vite, or Rollup but not a framework like React.
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install @pro6pp/infer-js
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
> **Note:** If you are using React, use [`@pro6pp/infer-react`](../react) instead.
|
|
26
|
+
|
|
27
|
+
## Usage
|
|
28
|
+
|
|
29
|
+
### Option 1: CDN
|
|
30
|
+
|
|
31
|
+
1. Add the script to your page.
|
|
32
|
+
2. Create an input field.
|
|
33
|
+
3. Attach the core logic to that input using `Pro6PP.attach()`.
|
|
34
|
+
|
|
35
|
+
```html
|
|
36
|
+
<!DOCTYPE html>
|
|
37
|
+
<html lang="en">
|
|
38
|
+
<head>
|
|
39
|
+
<meta charset="UTF-8" />
|
|
40
|
+
<title>Address Autocomplete</title>
|
|
41
|
+
</head>
|
|
42
|
+
<body>
|
|
43
|
+
<label>Address:</label>
|
|
44
|
+
// TODO: add CDN src
|
|
45
|
+
<script src=""></script>
|
|
46
|
+
|
|
47
|
+
<script>
|
|
48
|
+
Pro6PP.attach('#my-address-input', {
|
|
49
|
+
authKey: 'YOUR_AUTH_KEY',
|
|
50
|
+
country: 'NL',
|
|
51
|
+
onSelect: function (result) {
|
|
52
|
+
console.log('Selected Address:', result);
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
</script>
|
|
56
|
+
</body>
|
|
57
|
+
</html>
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Option 2: NPM
|
|
61
|
+
|
|
62
|
+
```javascript
|
|
63
|
+
import { attach } from '@pro6pp/infer-js';
|
|
64
|
+
|
|
65
|
+
// attach to an input element directly
|
|
66
|
+
const inputElement = document.querySelector('input[name="address"]');
|
|
67
|
+
|
|
68
|
+
attach(inputElement, {
|
|
69
|
+
authKey: 'YOUR_AUTH_KEY',
|
|
70
|
+
country: 'NL',
|
|
71
|
+
onSelect: (result) => {
|
|
72
|
+
console.log(result);
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Styling
|
|
78
|
+
|
|
79
|
+
By default, the SDK injects a small CSS block to make the dropdown look decent. If you want to control the styling with your own CSS, set `style: 'none'` in the config.
|
|
80
|
+
|
|
81
|
+
HTML created by the SDK:
|
|
82
|
+
|
|
83
|
+
```html
|
|
84
|
+
<div class="pro6pp-autocomplete-wrapper">
|
|
85
|
+
<input ... />
|
|
86
|
+
<ul class="pro6pp-results">
|
|
87
|
+
<li class="pro6pp-item">Suggestion 1</li>
|
|
88
|
+
<li class="pro6pp-item pro6pp-selected">Suggestion 2</li>
|
|
89
|
+
</ul>
|
|
90
|
+
</div>
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
You can target these classes in your CSS:
|
|
94
|
+
|
|
95
|
+
```css
|
|
96
|
+
.pro6pp-results {
|
|
97
|
+
background: white;
|
|
98
|
+
border: 1px solid #ccc;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.pro6pp-item:hover {
|
|
102
|
+
background: #f0f0f0;
|
|
103
|
+
}
|
|
104
|
+
```
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { InferConfig } from '@pro6pp/infer-core';
|
|
2
|
+
|
|
3
|
+
interface InferJSConfig extends InferConfig {
|
|
4
|
+
style?: 'default' | 'none';
|
|
5
|
+
}
|
|
6
|
+
declare class InferJS {
|
|
7
|
+
private core;
|
|
8
|
+
private input;
|
|
9
|
+
private list;
|
|
10
|
+
private wrapper;
|
|
11
|
+
private useDefaultStyles;
|
|
12
|
+
constructor(target: string | HTMLInputElement, config: InferJSConfig);
|
|
13
|
+
private injectStyles;
|
|
14
|
+
private setupDOM;
|
|
15
|
+
private bindEvents;
|
|
16
|
+
private render;
|
|
17
|
+
}
|
|
18
|
+
declare function attach(target: string | HTMLInputElement, config: InferJSConfig): InferJS;
|
|
19
|
+
|
|
20
|
+
export { InferJS, type InferJSConfig, attach };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { InferConfig } from '@pro6pp/infer-core';
|
|
2
|
+
|
|
3
|
+
interface InferJSConfig extends InferConfig {
|
|
4
|
+
style?: 'default' | 'none';
|
|
5
|
+
}
|
|
6
|
+
declare class InferJS {
|
|
7
|
+
private core;
|
|
8
|
+
private input;
|
|
9
|
+
private list;
|
|
10
|
+
private wrapper;
|
|
11
|
+
private useDefaultStyles;
|
|
12
|
+
constructor(target: string | HTMLInputElement, config: InferJSConfig);
|
|
13
|
+
private injectStyles;
|
|
14
|
+
private setupDOM;
|
|
15
|
+
private bindEvents;
|
|
16
|
+
private render;
|
|
17
|
+
}
|
|
18
|
+
declare function attach(target: string | HTMLInputElement, config: InferJSConfig): InferJS;
|
|
19
|
+
|
|
20
|
+
export { InferJS, type InferJSConfig, attach };
|
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var Pro6PP = (() => {
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
|
+
|
|
21
|
+
// src/index.ts
|
|
22
|
+
var index_exports = {};
|
|
23
|
+
__export(index_exports, {
|
|
24
|
+
InferJS: () => InferJS,
|
|
25
|
+
attach: () => attach
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// ../core/src/core.ts
|
|
29
|
+
var DEFAULTS = {
|
|
30
|
+
API_URL: "https://api.pro6pp.nl/v2",
|
|
31
|
+
LIMIT: 1e3,
|
|
32
|
+
DEBOUNCE_MS: 300
|
|
33
|
+
};
|
|
34
|
+
var PATTERNS = {
|
|
35
|
+
DIGITS_1_3: /^[0-9]{1,3}$/
|
|
36
|
+
};
|
|
37
|
+
var INITIAL_STATE = {
|
|
38
|
+
query: "",
|
|
39
|
+
stage: null,
|
|
40
|
+
cities: [],
|
|
41
|
+
streets: [],
|
|
42
|
+
suggestions: [],
|
|
43
|
+
isValid: false,
|
|
44
|
+
isError: false,
|
|
45
|
+
isLoading: false
|
|
46
|
+
};
|
|
47
|
+
var InferCore = class {
|
|
48
|
+
constructor(config) {
|
|
49
|
+
this.abortController = null;
|
|
50
|
+
this.country = config.country;
|
|
51
|
+
this.authKey = config.authKey;
|
|
52
|
+
this.apiUrl = config.apiUrl || DEFAULTS.API_URL;
|
|
53
|
+
this.limit = config.limit || DEFAULTS.LIMIT;
|
|
54
|
+
this.fetcher = config.fetcher || ((url, init) => fetch(url, init));
|
|
55
|
+
this.onStateChange = config.onStateChange || (() => {
|
|
56
|
+
});
|
|
57
|
+
this.onSelect = config.onSelect || (() => {
|
|
58
|
+
});
|
|
59
|
+
this.state = { ...INITIAL_STATE };
|
|
60
|
+
this.debouncedFetch = this.debounce(
|
|
61
|
+
(val) => this.executeFetch(val),
|
|
62
|
+
DEFAULTS.DEBOUNCE_MS
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
handleInput(value) {
|
|
66
|
+
this.updateState({
|
|
67
|
+
query: value,
|
|
68
|
+
isValid: false,
|
|
69
|
+
isLoading: !!value.trim()
|
|
70
|
+
});
|
|
71
|
+
if (this.state.stage === "final") {
|
|
72
|
+
this.onSelect(null);
|
|
73
|
+
}
|
|
74
|
+
this.debouncedFetch(value);
|
|
75
|
+
}
|
|
76
|
+
handleKeyDown(event) {
|
|
77
|
+
const target = event.target;
|
|
78
|
+
const val = target.value;
|
|
79
|
+
if (event.key === " " && this.shouldAutoInsertComma(val)) {
|
|
80
|
+
event.preventDefault();
|
|
81
|
+
const next = `${val.trim()}, `;
|
|
82
|
+
this.updateQueryAndFetch(next);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
selectItem(item) {
|
|
86
|
+
const label = typeof item === "string" ? item : item.label;
|
|
87
|
+
const value = typeof item !== "string" ? item.value : void 0;
|
|
88
|
+
const subtitle = typeof item !== "string" ? item.subtitle : null;
|
|
89
|
+
if (this.state.stage === "final") {
|
|
90
|
+
this.finishSelection(label, value);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
this.processSelection(label, subtitle);
|
|
94
|
+
}
|
|
95
|
+
shouldAutoInsertComma(currentVal) {
|
|
96
|
+
const isStartOfSegmentAndNumeric = !currentVal.includes(",") && PATTERNS.DIGITS_1_3.test(currentVal.trim());
|
|
97
|
+
if (isStartOfSegmentAndNumeric) return true;
|
|
98
|
+
if (this.state.stage === "house_number") {
|
|
99
|
+
const currentFragment = this.getCurrentFragment(currentVal);
|
|
100
|
+
return PATTERNS.DIGITS_1_3.test(currentFragment);
|
|
101
|
+
}
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
finishSelection(label, value) {
|
|
105
|
+
this.updateState({ query: label, suggestions: [], cities: [], streets: [], isValid: true });
|
|
106
|
+
this.onSelect(value || label);
|
|
107
|
+
}
|
|
108
|
+
processSelection(label, subtitle) {
|
|
109
|
+
const { stage, query } = this.state;
|
|
110
|
+
let nextQuery = query;
|
|
111
|
+
const isContextualSelection = subtitle && (stage === "city" || stage === "street" || stage === "mixed");
|
|
112
|
+
if (isContextualSelection) {
|
|
113
|
+
if (stage === "city") {
|
|
114
|
+
nextQuery = `${subtitle}, ${label}, `;
|
|
115
|
+
} else {
|
|
116
|
+
const prefix = this.getQueryPrefix(query);
|
|
117
|
+
nextQuery = prefix ? `${prefix} ${label}, ${subtitle}, ` : `${label}, ${subtitle}, `;
|
|
118
|
+
}
|
|
119
|
+
this.updateQueryAndFetch(nextQuery);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
if (stage === "direct" || stage === "addition") {
|
|
123
|
+
this.finishSelection(label);
|
|
124
|
+
this.handleInput(label);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const hasComma = query.includes(",");
|
|
128
|
+
const isFirstSegment = !hasComma && (stage === "city" || stage === "street" || stage === "house_number_first");
|
|
129
|
+
if (isFirstSegment) {
|
|
130
|
+
nextQuery = `${label}, `;
|
|
131
|
+
} else {
|
|
132
|
+
nextQuery = this.replaceLastSegment(query, label);
|
|
133
|
+
if (stage !== "house_number") {
|
|
134
|
+
nextQuery += ", ";
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
this.updateQueryAndFetch(nextQuery);
|
|
138
|
+
}
|
|
139
|
+
executeFetch(val) {
|
|
140
|
+
const text = (val || "").toString();
|
|
141
|
+
if (!text.trim()) {
|
|
142
|
+
this.abortController?.abort();
|
|
143
|
+
this.resetState();
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
this.updateState({ isError: false });
|
|
147
|
+
if (this.abortController) this.abortController.abort();
|
|
148
|
+
this.abortController = new AbortController();
|
|
149
|
+
const url = new URL(`${this.apiUrl}/infer/${this.country.toLowerCase()}`);
|
|
150
|
+
const params = {
|
|
151
|
+
authKey: this.authKey,
|
|
152
|
+
query: text,
|
|
153
|
+
limit: this.limit.toString()
|
|
154
|
+
};
|
|
155
|
+
url.search = new URLSearchParams(params).toString();
|
|
156
|
+
this.fetcher(url.toString(), { signal: this.abortController.signal }).then((res) => {
|
|
157
|
+
if (!res.ok) throw new Error("Network error");
|
|
158
|
+
return res.json();
|
|
159
|
+
}).then((data) => this.mapResponseToState(data)).catch((e) => {
|
|
160
|
+
if (e.name !== "AbortError") {
|
|
161
|
+
this.updateState({ isError: true, isLoading: false });
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
mapResponseToState(data) {
|
|
166
|
+
const newState = {
|
|
167
|
+
stage: data.stage,
|
|
168
|
+
isLoading: false
|
|
169
|
+
};
|
|
170
|
+
if (data.stage === "mixed") {
|
|
171
|
+
newState.cities = data.cities || [];
|
|
172
|
+
newState.streets = data.streets || [];
|
|
173
|
+
newState.suggestions = [];
|
|
174
|
+
} else {
|
|
175
|
+
newState.suggestions = data.suggestions || [];
|
|
176
|
+
newState.cities = [];
|
|
177
|
+
newState.streets = [];
|
|
178
|
+
}
|
|
179
|
+
newState.isValid = data.stage === "final";
|
|
180
|
+
this.updateState(newState);
|
|
181
|
+
}
|
|
182
|
+
updateQueryAndFetch(nextQuery) {
|
|
183
|
+
this.updateState({ query: nextQuery, suggestions: [], cities: [], streets: [] });
|
|
184
|
+
this.handleInput(nextQuery);
|
|
185
|
+
}
|
|
186
|
+
replaceLastSegment(fullText, newSegment) {
|
|
187
|
+
const lastCommaIndex = fullText.lastIndexOf(",");
|
|
188
|
+
if (lastCommaIndex === -1) return newSegment;
|
|
189
|
+
return `${fullText.slice(0, lastCommaIndex + 1)} ${newSegment}`.trim();
|
|
190
|
+
}
|
|
191
|
+
getQueryPrefix(q) {
|
|
192
|
+
const lastComma = q.lastIndexOf(",");
|
|
193
|
+
return lastComma === -1 ? "" : q.slice(0, lastComma + 1).trimEnd();
|
|
194
|
+
}
|
|
195
|
+
getCurrentFragment(q) {
|
|
196
|
+
return (q.split(",").slice(-1)[0] ?? "").trim();
|
|
197
|
+
}
|
|
198
|
+
resetState() {
|
|
199
|
+
this.updateState({ ...INITIAL_STATE, query: this.state.query });
|
|
200
|
+
}
|
|
201
|
+
updateState(updates) {
|
|
202
|
+
this.state = { ...this.state, ...updates };
|
|
203
|
+
this.onStateChange(this.state);
|
|
204
|
+
}
|
|
205
|
+
debounce(func, wait) {
|
|
206
|
+
let timeout;
|
|
207
|
+
return (...args) => {
|
|
208
|
+
if (timeout) clearTimeout(timeout);
|
|
209
|
+
timeout = setTimeout(() => func.apply(this, args), wait);
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
// src/index.ts
|
|
215
|
+
var DEFAULT_STYLES = `
|
|
216
|
+
.pro6pp-wrapper {
|
|
217
|
+
position: relative;
|
|
218
|
+
display: block;
|
|
219
|
+
width: 100%;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.pro6pp-dropdown {
|
|
223
|
+
position: absolute;
|
|
224
|
+
top: 100%;
|
|
225
|
+
left: 0;
|
|
226
|
+
right: 0;
|
|
227
|
+
z-index: 10000;
|
|
228
|
+
background-color: #ffffff;
|
|
229
|
+
border: 1px solid #e2e8f0;
|
|
230
|
+
border-radius: 0 0 4px 4px;
|
|
231
|
+
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
|
232
|
+
list-style: none;
|
|
233
|
+
margin: 0;
|
|
234
|
+
padding: 0;
|
|
235
|
+
max-height: 250px;
|
|
236
|
+
overflow-y: auto;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
.pro6pp-item {
|
|
240
|
+
padding: 10px 12px;
|
|
241
|
+
cursor: pointer;
|
|
242
|
+
border-bottom: 1px solid #f1f5f9;
|
|
243
|
+
font-family: inherit;
|
|
244
|
+
font-size: 14px;
|
|
245
|
+
line-height: 1.4;
|
|
246
|
+
color: #1e293b;
|
|
247
|
+
transition: background-color 0.15s ease;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
.pro6pp-item:last-child {
|
|
251
|
+
border-bottom: none;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
.pro6pp-item:hover, .pro6pp-item--active {
|
|
255
|
+
background-color: #f8fafc;
|
|
256
|
+
color: #0f172a;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
.pro6pp-item__subtitle {
|
|
260
|
+
display: block;
|
|
261
|
+
font-size: 0.85em;
|
|
262
|
+
color: #64748b;
|
|
263
|
+
margin-top: 2px;
|
|
264
|
+
}
|
|
265
|
+
`;
|
|
266
|
+
var InferJS = class {
|
|
267
|
+
constructor(target, config) {
|
|
268
|
+
const el = typeof target === "string" ? document.querySelector(target) : target;
|
|
269
|
+
if (!el || !(el instanceof HTMLInputElement)) {
|
|
270
|
+
throw new Error(`InferJS: Target element not found or is not an input.`);
|
|
271
|
+
}
|
|
272
|
+
this.input = el;
|
|
273
|
+
this.useDefaultStyles = config.style !== "none";
|
|
274
|
+
if (this.useDefaultStyles) {
|
|
275
|
+
this.injectStyles();
|
|
276
|
+
}
|
|
277
|
+
this.core = new InferCore({
|
|
278
|
+
...config,
|
|
279
|
+
onStateChange: (state) => this.render(state),
|
|
280
|
+
onSelect: (selection) => {
|
|
281
|
+
if (typeof selection === "string") {
|
|
282
|
+
this.input.value = selection;
|
|
283
|
+
} else if (selection && typeof selection === "object") {
|
|
284
|
+
this.input.value = this.core.state.query;
|
|
285
|
+
}
|
|
286
|
+
if (config.onSelect) config.onSelect(selection);
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
this.setupDOM();
|
|
290
|
+
this.bindEvents();
|
|
291
|
+
}
|
|
292
|
+
injectStyles() {
|
|
293
|
+
const styleId = "pro6pp-infer-styles";
|
|
294
|
+
if (!document.getElementById(styleId)) {
|
|
295
|
+
const styleEl = document.createElement("style");
|
|
296
|
+
styleEl.id = styleId;
|
|
297
|
+
styleEl.textContent = DEFAULT_STYLES;
|
|
298
|
+
document.head.appendChild(styleEl);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
setupDOM() {
|
|
302
|
+
this.wrapper = document.createElement("div");
|
|
303
|
+
this.wrapper.className = "pro6pp-wrapper";
|
|
304
|
+
this.input.parentNode?.insertBefore(this.wrapper, this.input);
|
|
305
|
+
this.wrapper.appendChild(this.input);
|
|
306
|
+
this.list = document.createElement("ul");
|
|
307
|
+
this.list.className = "pro6pp-dropdown";
|
|
308
|
+
this.list.style.display = "none";
|
|
309
|
+
this.wrapper.appendChild(this.list);
|
|
310
|
+
}
|
|
311
|
+
bindEvents() {
|
|
312
|
+
this.input.addEventListener("input", (e) => {
|
|
313
|
+
const val = e.target.value;
|
|
314
|
+
this.core.handleInput(val);
|
|
315
|
+
});
|
|
316
|
+
this.input.addEventListener("keydown", (e) => {
|
|
317
|
+
this.core.handleKeyDown(e);
|
|
318
|
+
});
|
|
319
|
+
document.addEventListener("click", (e) => {
|
|
320
|
+
if (!this.wrapper.contains(e.target)) {
|
|
321
|
+
this.list.style.display = "none";
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
render(state) {
|
|
326
|
+
if (this.input.value !== state.query) {
|
|
327
|
+
this.input.value = state.query;
|
|
328
|
+
}
|
|
329
|
+
this.list.innerHTML = "";
|
|
330
|
+
const items = [
|
|
331
|
+
...state.cities.map((c) => ({ item: c, type: "city" })),
|
|
332
|
+
...state.streets.map((s) => ({ item: s, type: "street" })),
|
|
333
|
+
...state.suggestions.map((s) => ({ item: s, type: "suggestion" }))
|
|
334
|
+
];
|
|
335
|
+
if (items.length === 0) {
|
|
336
|
+
this.list.style.display = "none";
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
this.list.style.display = "block";
|
|
340
|
+
items.forEach(({ item }) => {
|
|
341
|
+
const li = document.createElement("li");
|
|
342
|
+
li.className = "pro6pp-item";
|
|
343
|
+
li.setAttribute("role", "option");
|
|
344
|
+
const labelSpan = document.createElement("span");
|
|
345
|
+
labelSpan.className = "pro6pp-item__label";
|
|
346
|
+
labelSpan.textContent = item.label;
|
|
347
|
+
li.appendChild(labelSpan);
|
|
348
|
+
if (item.subtitle) {
|
|
349
|
+
const subSpan = document.createElement("span");
|
|
350
|
+
subSpan.className = "pro6pp-item__subtitle";
|
|
351
|
+
subSpan.textContent = item.subtitle;
|
|
352
|
+
li.appendChild(subSpan);
|
|
353
|
+
}
|
|
354
|
+
li.onclick = (e) => {
|
|
355
|
+
e.stopPropagation();
|
|
356
|
+
this.core.selectItem(item);
|
|
357
|
+
};
|
|
358
|
+
this.list.appendChild(li);
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
};
|
|
362
|
+
function attach(target, config) {
|
|
363
|
+
return new InferJS(target, config);
|
|
364
|
+
}
|
|
365
|
+
return __toCommonJS(index_exports);
|
|
366
|
+
})();
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { InferCore } from "@pro6pp/infer-core";
|
|
3
|
+
var DEFAULT_STYLES = `
|
|
4
|
+
.pro6pp-wrapper {
|
|
5
|
+
position: relative;
|
|
6
|
+
display: block;
|
|
7
|
+
width: 100%;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.pro6pp-dropdown {
|
|
11
|
+
position: absolute;
|
|
12
|
+
top: 100%;
|
|
13
|
+
left: 0;
|
|
14
|
+
right: 0;
|
|
15
|
+
z-index: 10000;
|
|
16
|
+
background-color: #ffffff;
|
|
17
|
+
border: 1px solid #e2e8f0;
|
|
18
|
+
border-radius: 0 0 4px 4px;
|
|
19
|
+
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
|
20
|
+
list-style: none;
|
|
21
|
+
margin: 0;
|
|
22
|
+
padding: 0;
|
|
23
|
+
max-height: 250px;
|
|
24
|
+
overflow-y: auto;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.pro6pp-item {
|
|
28
|
+
padding: 10px 12px;
|
|
29
|
+
cursor: pointer;
|
|
30
|
+
border-bottom: 1px solid #f1f5f9;
|
|
31
|
+
font-family: inherit;
|
|
32
|
+
font-size: 14px;
|
|
33
|
+
line-height: 1.4;
|
|
34
|
+
color: #1e293b;
|
|
35
|
+
transition: background-color 0.15s ease;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.pro6pp-item:last-child {
|
|
39
|
+
border-bottom: none;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.pro6pp-item:hover, .pro6pp-item--active {
|
|
43
|
+
background-color: #f8fafc;
|
|
44
|
+
color: #0f172a;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.pro6pp-item__subtitle {
|
|
48
|
+
display: block;
|
|
49
|
+
font-size: 0.85em;
|
|
50
|
+
color: #64748b;
|
|
51
|
+
margin-top: 2px;
|
|
52
|
+
}
|
|
53
|
+
`;
|
|
54
|
+
var InferJS = class {
|
|
55
|
+
constructor(target, config) {
|
|
56
|
+
const el = typeof target === "string" ? document.querySelector(target) : target;
|
|
57
|
+
if (!el || !(el instanceof HTMLInputElement)) {
|
|
58
|
+
throw new Error(`InferJS: Target element not found or is not an input.`);
|
|
59
|
+
}
|
|
60
|
+
this.input = el;
|
|
61
|
+
this.useDefaultStyles = config.style !== "none";
|
|
62
|
+
if (this.useDefaultStyles) {
|
|
63
|
+
this.injectStyles();
|
|
64
|
+
}
|
|
65
|
+
this.core = new InferCore({
|
|
66
|
+
...config,
|
|
67
|
+
onStateChange: (state) => this.render(state),
|
|
68
|
+
onSelect: (selection) => {
|
|
69
|
+
if (typeof selection === "string") {
|
|
70
|
+
this.input.value = selection;
|
|
71
|
+
} else if (selection && typeof selection === "object") {
|
|
72
|
+
this.input.value = this.core.state.query;
|
|
73
|
+
}
|
|
74
|
+
if (config.onSelect) config.onSelect(selection);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
this.setupDOM();
|
|
78
|
+
this.bindEvents();
|
|
79
|
+
}
|
|
80
|
+
injectStyles() {
|
|
81
|
+
const styleId = "pro6pp-infer-styles";
|
|
82
|
+
if (!document.getElementById(styleId)) {
|
|
83
|
+
const styleEl = document.createElement("style");
|
|
84
|
+
styleEl.id = styleId;
|
|
85
|
+
styleEl.textContent = DEFAULT_STYLES;
|
|
86
|
+
document.head.appendChild(styleEl);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
setupDOM() {
|
|
90
|
+
this.wrapper = document.createElement("div");
|
|
91
|
+
this.wrapper.className = "pro6pp-wrapper";
|
|
92
|
+
this.input.parentNode?.insertBefore(this.wrapper, this.input);
|
|
93
|
+
this.wrapper.appendChild(this.input);
|
|
94
|
+
this.list = document.createElement("ul");
|
|
95
|
+
this.list.className = "pro6pp-dropdown";
|
|
96
|
+
this.list.style.display = "none";
|
|
97
|
+
this.wrapper.appendChild(this.list);
|
|
98
|
+
}
|
|
99
|
+
bindEvents() {
|
|
100
|
+
this.input.addEventListener("input", (e) => {
|
|
101
|
+
const val = e.target.value;
|
|
102
|
+
this.core.handleInput(val);
|
|
103
|
+
});
|
|
104
|
+
this.input.addEventListener("keydown", (e) => {
|
|
105
|
+
this.core.handleKeyDown(e);
|
|
106
|
+
});
|
|
107
|
+
document.addEventListener("click", (e) => {
|
|
108
|
+
if (!this.wrapper.contains(e.target)) {
|
|
109
|
+
this.list.style.display = "none";
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
render(state) {
|
|
114
|
+
if (this.input.value !== state.query) {
|
|
115
|
+
this.input.value = state.query;
|
|
116
|
+
}
|
|
117
|
+
this.list.innerHTML = "";
|
|
118
|
+
const items = [
|
|
119
|
+
...state.cities.map((c) => ({ item: c, type: "city" })),
|
|
120
|
+
...state.streets.map((s) => ({ item: s, type: "street" })),
|
|
121
|
+
...state.suggestions.map((s) => ({ item: s, type: "suggestion" }))
|
|
122
|
+
];
|
|
123
|
+
if (items.length === 0) {
|
|
124
|
+
this.list.style.display = "none";
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
this.list.style.display = "block";
|
|
128
|
+
items.forEach(({ item }) => {
|
|
129
|
+
const li = document.createElement("li");
|
|
130
|
+
li.className = "pro6pp-item";
|
|
131
|
+
li.setAttribute("role", "option");
|
|
132
|
+
const labelSpan = document.createElement("span");
|
|
133
|
+
labelSpan.className = "pro6pp-item__label";
|
|
134
|
+
labelSpan.textContent = item.label;
|
|
135
|
+
li.appendChild(labelSpan);
|
|
136
|
+
if (item.subtitle) {
|
|
137
|
+
const subSpan = document.createElement("span");
|
|
138
|
+
subSpan.className = "pro6pp-item__subtitle";
|
|
139
|
+
subSpan.textContent = item.subtitle;
|
|
140
|
+
li.appendChild(subSpan);
|
|
141
|
+
}
|
|
142
|
+
li.onclick = (e) => {
|
|
143
|
+
e.stopPropagation();
|
|
144
|
+
this.core.selectItem(item);
|
|
145
|
+
};
|
|
146
|
+
this.list.appendChild(li);
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
function attach(target, config) {
|
|
151
|
+
return new InferJS(target, config);
|
|
152
|
+
}
|
|
153
|
+
export {
|
|
154
|
+
InferJS,
|
|
155
|
+
attach
|
|
156
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pro6pp/infer-js",
|
|
3
|
+
"version": "0.0.2-beta.0",
|
|
4
|
+
"main": "./dist/index.js",
|
|
5
|
+
"module": "./dist/index.mjs",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
|
+
"unpkg": "./dist/index.global.js",
|
|
8
|
+
"jsdelivr": "./dist/index.global.js",
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"README.md"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsup src/index.ts --format iife,esm --dts --global-name Pro6PP --clean",
|
|
15
|
+
"dev": "tsup src/index.ts --format iife,esm --dts --watch --global-name Pro6PP",
|
|
16
|
+
"type-check": "tsc --noEmit"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@pro6pp/infer-core": "0.0.2-beta.0"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"tsup": "^8.0.0",
|
|
23
|
+
"typescript": "^5.0.0"
|
|
24
|
+
},
|
|
25
|
+
"publishConfig": {
|
|
26
|
+
"access": "public"
|
|
27
|
+
},
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "https://gitlab.d-centralize.nl/dc/pro6pp/pro6pp-infer-sdk"
|
|
32
|
+
}
|
|
33
|
+
}
|