@inglorious/web 6.0.0 → 6.0.1
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 +54 -3
- package/package.json +5 -1
- package/src/geolocation/handlers.js +168 -0
- package/src/geolocation/index.js +3 -0
- package/types/geolocation.d.ts +56 -0
package/README.md
CHANGED
|
@@ -7,6 +7,8 @@ A lightweight, reactive-enough web framework built on **pure JavaScript**, the e
|
|
|
7
7
|
|
|
8
8
|
Unlike modern frameworks that invent their own languages or rely on signals, proxies, or compilers, **@inglorious/web embraces plain JavaScript** and a transparent architecture.
|
|
9
9
|
|
|
10
|
+
It's a **complete framework**: it ships with a client-side router, declarative forms, geolocation state, async helpers (via `@inglorious/store/async`), and a dedicated testing utilities entry point (`@inglorious/web/test`) that re-exports `trigger` from `@inglorious/store` and `render` from `lit-html`. UI primitives (controls, data display, navigation, feedback, layout, surfaces) live in the companion package **[Inglorious UI](https://inglorious.dev/ui)**.
|
|
11
|
+
|
|
10
12
|
---
|
|
11
13
|
|
|
12
14
|
## Features
|
|
@@ -26,8 +28,8 @@ Unlike modern frameworks that invent their own languages or rely on signals, pro
|
|
|
26
28
|
Zero magic, zero reactivity graphs, zero compiler.
|
|
27
29
|
Just JavaScript functions and store events.
|
|
28
30
|
|
|
29
|
-
- **Router, Forms,
|
|
30
|
-
High-level primitives built on the same predictable model.
|
|
31
|
+
- **Router, Forms, Geolocation, Async, Testing utilities**
|
|
32
|
+
High-level primitives built on the same predictable model. UI primitives (tables, virtual lists, comboboxes, and more) live in **[Inglorious UI](https://inglorious.dev/ui)**.
|
|
31
33
|
|
|
32
34
|
- **Zero Component State**
|
|
33
35
|
All state lives in the store — never inside components.
|
|
@@ -347,7 +349,12 @@ One of Inglorious Web's greatest strengths is **testability**. Entity handlers a
|
|
|
347
349
|
|
|
348
350
|
### Testing Utilities
|
|
349
351
|
|
|
350
|
-
|
|
352
|
+
The `@inglorious/web/test` entry point re-exports two helpers so you can test handlers and templates without any extra setup:
|
|
353
|
+
|
|
354
|
+
- **`trigger`** — comes from `@inglorious/store` (`@inglorious/store/test`). It executes an entity handler and returns the new state plus any dispatched events.
|
|
355
|
+
- **`render`** — comes from `lit-html`. It renders a template to a DOM node so you can assert on its output.
|
|
356
|
+
|
|
357
|
+
Both are re-exported from `@inglorious/web/test` purely as a convenience, so a web app only needs to depend on `@inglorious/web` to test its handlers and templates.
|
|
351
358
|
|
|
352
359
|
#### `trigger(entity, handler, payload?, api?)`
|
|
353
360
|
|
|
@@ -1224,6 +1231,50 @@ For ready-made UI primitives (controls, data display, navigation, feedback, layo
|
|
|
1224
1231
|
|
|
1225
1232
|
---
|
|
1226
1233
|
|
|
1234
|
+
## Geolocation
|
|
1235
|
+
|
|
1236
|
+
`@inglorious/web` includes a `Geolocation` type for keeping browser geolocation state inside the store.
|
|
1237
|
+
|
|
1238
|
+
```javascript
|
|
1239
|
+
import { createStore } from "@inglorious/store"
|
|
1240
|
+
import { Geolocation } from "@inglorious/web/geolocation"
|
|
1241
|
+
|
|
1242
|
+
const store = createStore({
|
|
1243
|
+
types: { Geolocation },
|
|
1244
|
+
autoCreateEntities: true,
|
|
1245
|
+
})
|
|
1246
|
+
|
|
1247
|
+
// The store now contains a `geolocation` entity.
|
|
1248
|
+
store.getState().geolocation
|
|
1249
|
+
```
|
|
1250
|
+
|
|
1251
|
+
The entity state contains:
|
|
1252
|
+
|
|
1253
|
+
- `isSupported` — whether `navigator.geolocation` is available
|
|
1254
|
+
- `isLoading` — whether a current-position request or first watch result is pending
|
|
1255
|
+
- `isWatching` — whether a geolocation watch is active
|
|
1256
|
+
- `position` — the latest normalized `{ coords, timestamp }` value
|
|
1257
|
+
- `error` — the latest normalized `{ code, message }` error
|
|
1258
|
+
- `watchId` — the active browser watch id, or `null`
|
|
1259
|
+
|
|
1260
|
+
Events:
|
|
1261
|
+
|
|
1262
|
+
- `#geolocation:getCurrentPosition` — request the current position
|
|
1263
|
+
- `#geolocation:watch` — start watching position changes
|
|
1264
|
+
- `#geolocation:clearWatch` — stop the active watch
|
|
1265
|
+
|
|
1266
|
+
```javascript
|
|
1267
|
+
api.notify("#geolocation:getCurrentPosition", {
|
|
1268
|
+
enableHighAccuracy: true,
|
|
1269
|
+
timeout: 5000,
|
|
1270
|
+
})
|
|
1271
|
+
|
|
1272
|
+
api.notify("#geolocation:watch")
|
|
1273
|
+
api.notify("#geolocation:clearWatch")
|
|
1274
|
+
```
|
|
1275
|
+
|
|
1276
|
+
---
|
|
1277
|
+
|
|
1227
1278
|
## Forms
|
|
1228
1279
|
|
|
1229
1280
|
`@inglorious/web` includes a small but powerful `form` type for managing form state inside your entity store. It offers:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inglorious/web",
|
|
3
|
-
"version": "6.0.
|
|
3
|
+
"version": "6.0.1",
|
|
4
4
|
"description": "A new web framework that leverages the power of the Inglorious Store combined with the performance and simplicity of lit-html.",
|
|
5
5
|
"author": "IceOnFire <antony.mistretta@gmail.com> (https://ingloriouscoderz.it)",
|
|
6
6
|
"license": "MIT",
|
|
@@ -34,6 +34,10 @@
|
|
|
34
34
|
"types": "./types/form.d.ts",
|
|
35
35
|
"import": "./src/form/index.js"
|
|
36
36
|
},
|
|
37
|
+
"./geolocation": {
|
|
38
|
+
"types": "./types/geolocation.d.ts",
|
|
39
|
+
"import": "./src/geolocation/index.js"
|
|
40
|
+
},
|
|
37
41
|
"./hydrate": "./src/hydrate.js",
|
|
38
42
|
"./router": {
|
|
39
43
|
"types": "./types/router.d.ts",
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {import("../../types/geolocation.js").GeolocationEntity} GeolocationEntity
|
|
3
|
+
* @typedef {import("../../types/geolocation.js").GeolocationOptions} GeolocationOptions
|
|
4
|
+
* @typedef {import("../../types/geolocation.js").GeolocationPosition} GeolocationPosition
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const NO_WATCH_ID = null
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Stops an active geolocation watch.
|
|
11
|
+
* @param {GeolocationEntity} entity - The geolocation entity.
|
|
12
|
+
*/
|
|
13
|
+
export function unwatch(entity) {
|
|
14
|
+
const geolocation = getGeolocation()
|
|
15
|
+
|
|
16
|
+
if (entity.watchId !== NO_WATCH_ID && geolocation) {
|
|
17
|
+
geolocation.clearWatch(entity.watchId)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
entity.isLoading = false
|
|
21
|
+
entity.isWatching = false
|
|
22
|
+
entity.watchId = NO_WATCH_ID
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Initializes the geolocation entity with default state.
|
|
27
|
+
* @param {GeolocationEntity} entity - The geolocation entity.
|
|
28
|
+
*/
|
|
29
|
+
export function create(entity) {
|
|
30
|
+
entity.error ??= null
|
|
31
|
+
entity.isLoading ??= false
|
|
32
|
+
entity.isSupported = Boolean(getGeolocation())
|
|
33
|
+
entity.isWatching ??= false
|
|
34
|
+
entity.position ??= null
|
|
35
|
+
entity.watchId ??= NO_WATCH_ID
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Clears the geolocation watch before the entity is destroyed.
|
|
40
|
+
* @param {GeolocationEntity} entity - The geolocation entity.
|
|
41
|
+
*/
|
|
42
|
+
export function destroy(entity) {
|
|
43
|
+
unwatch(entity)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Stores a normalized geolocation error.
|
|
48
|
+
* @param {GeolocationEntity} entity - The geolocation entity.
|
|
49
|
+
* @param {GeolocationPositionError} error - The geolocation error.
|
|
50
|
+
*/
|
|
51
|
+
export function error(entity, error) {
|
|
52
|
+
entity.error = normalizeError(error)
|
|
53
|
+
entity.isLoading = false
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Requests the current geolocation position.
|
|
58
|
+
* @param {GeolocationEntity} entity - The geolocation entity.
|
|
59
|
+
* @param {GeolocationOptions} options - Position options.
|
|
60
|
+
* @param {{ notify: Function }} api - The store API.
|
|
61
|
+
*/
|
|
62
|
+
export function request(entity, options = {}, api) {
|
|
63
|
+
const geolocation = getGeolocation()
|
|
64
|
+
|
|
65
|
+
entity.isSupported = Boolean(geolocation)
|
|
66
|
+
entity.error = null
|
|
67
|
+
|
|
68
|
+
if (!geolocation) {
|
|
69
|
+
entity.isLoading = false
|
|
70
|
+
api.notify(`#${entity.id}:error`, createUnsupportedError())
|
|
71
|
+
return
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
entity.isLoading = true
|
|
75
|
+
|
|
76
|
+
geolocation.getCurrentPosition(
|
|
77
|
+
(position) => {
|
|
78
|
+
api.notify(`#${entity.id}:success`, normalizePosition(position))
|
|
79
|
+
},
|
|
80
|
+
(error) => {
|
|
81
|
+
api.notify(`#${entity.id}:error`, error)
|
|
82
|
+
},
|
|
83
|
+
options,
|
|
84
|
+
)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Stores a normalized geolocation position.
|
|
89
|
+
* @param {GeolocationEntity} entity - The geolocation entity.
|
|
90
|
+
* @param {GeolocationPosition} position - The geolocation position.
|
|
91
|
+
*/
|
|
92
|
+
export function success(entity, position) {
|
|
93
|
+
entity.error = null
|
|
94
|
+
entity.isLoading = false
|
|
95
|
+
entity.position = position
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Starts watching geolocation changes.
|
|
100
|
+
* @param {GeolocationEntity} entity - The geolocation entity.
|
|
101
|
+
* @param {GeolocationOptions} options - Watch options.
|
|
102
|
+
* @param {{ notify: Function }} api - The store API.
|
|
103
|
+
*/
|
|
104
|
+
export function watch(entity, options = {}, api) {
|
|
105
|
+
const geolocation = getGeolocation()
|
|
106
|
+
|
|
107
|
+
entity.isSupported = Boolean(geolocation)
|
|
108
|
+
entity.error = null
|
|
109
|
+
|
|
110
|
+
if (!geolocation) {
|
|
111
|
+
entity.isLoading = false
|
|
112
|
+
api.notify(`#${entity.id}:error`, createUnsupportedError())
|
|
113
|
+
return
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (entity.watchId !== NO_WATCH_ID) {
|
|
117
|
+
return
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
entity.isLoading = true
|
|
121
|
+
entity.isWatching = true
|
|
122
|
+
entity.watchId = geolocation.watchPosition(
|
|
123
|
+
(position) => {
|
|
124
|
+
api.notify(`#${entity.id}:success`, normalizePosition(position))
|
|
125
|
+
},
|
|
126
|
+
(error) => {
|
|
127
|
+
api.notify(`#${entity.id}:error`, error)
|
|
128
|
+
},
|
|
129
|
+
options,
|
|
130
|
+
)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function createUnsupportedError() {
|
|
134
|
+
return {
|
|
135
|
+
code: 0,
|
|
136
|
+
message: "Geolocation is not supported by this environment.",
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function getGeolocation() {
|
|
141
|
+
return globalThis.navigator?.geolocation
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function normalizeCoords(coords) {
|
|
145
|
+
return {
|
|
146
|
+
accuracy: coords.accuracy,
|
|
147
|
+
altitude: coords.altitude,
|
|
148
|
+
altitudeAccuracy: coords.altitudeAccuracy,
|
|
149
|
+
heading: coords.heading,
|
|
150
|
+
latitude: coords.latitude,
|
|
151
|
+
longitude: coords.longitude,
|
|
152
|
+
speed: coords.speed,
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function normalizeError(error) {
|
|
157
|
+
return {
|
|
158
|
+
code: error.code,
|
|
159
|
+
message: error.message,
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function normalizePosition(position) {
|
|
164
|
+
return {
|
|
165
|
+
coords: normalizeCoords(position.coords),
|
|
166
|
+
timestamp: position.timestamp,
|
|
167
|
+
}
|
|
168
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export interface GeolocationCoordinates {
|
|
2
|
+
accuracy: number
|
|
3
|
+
altitude: number | null
|
|
4
|
+
altitudeAccuracy: number | null
|
|
5
|
+
heading: number | null
|
|
6
|
+
latitude: number
|
|
7
|
+
longitude: number
|
|
8
|
+
speed: number | null
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface GeolocationEntity {
|
|
12
|
+
error: GeolocationError | null
|
|
13
|
+
id: string | number
|
|
14
|
+
isLoading: boolean
|
|
15
|
+
isSupported: boolean
|
|
16
|
+
isWatching: boolean
|
|
17
|
+
position: GeolocationPosition | null
|
|
18
|
+
type?: "Geolocation"
|
|
19
|
+
watchId: number | null
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface GeolocationError {
|
|
23
|
+
code: number
|
|
24
|
+
message: string
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface GeolocationOptions {
|
|
28
|
+
enableHighAccuracy?: boolean
|
|
29
|
+
maximumAge?: number
|
|
30
|
+
timeout?: number
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface GeolocationPosition {
|
|
34
|
+
coords: GeolocationCoordinates
|
|
35
|
+
timestamp: number
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface GeolocationType {
|
|
39
|
+
unwatch(entity: GeolocationEntity): void
|
|
40
|
+
create(entity: GeolocationEntity): void
|
|
41
|
+
destroy(entity: GeolocationEntity): void
|
|
42
|
+
error(entity: GeolocationEntity, error: GeolocationError): void
|
|
43
|
+
request(
|
|
44
|
+
entity: GeolocationEntity,
|
|
45
|
+
options: GeolocationOptions | undefined,
|
|
46
|
+
api: { notify: (event: string, payload?: unknown) => void },
|
|
47
|
+
): void
|
|
48
|
+
success(entity: GeolocationEntity, position: GeolocationPosition): void
|
|
49
|
+
watch(
|
|
50
|
+
entity: GeolocationEntity,
|
|
51
|
+
options: GeolocationOptions | undefined,
|
|
52
|
+
api: { notify: (event: string, payload?: unknown) => void },
|
|
53
|
+
): void
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export declare const Geolocation: GeolocationType
|