@inglorious/web 6.0.0 → 6.0.2

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 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, Tables, Virtual Lists**
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
- Inglorious Web provides two simple utilities for testing via `@inglorious/web/test`:
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.0",
3
+ "version": "6.0.2",
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,170 @@
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
+ * Initializes the geolocation entity with default state.
11
+ * @param {GeolocationEntity} entity - The geolocation entity.
12
+ */
13
+ export function create(entity) {
14
+ entity.error ??= null
15
+ entity.isLoading ??= false
16
+ entity.isSupported = Boolean(getGeolocation())
17
+ entity.isWatching ??= false
18
+ entity.position ??= null
19
+ entity.watchId ??= NO_WATCH_ID
20
+ }
21
+
22
+ /**
23
+ * Clears the geolocation watch before the entity is destroyed.
24
+ * @param {GeolocationEntity} entity - The geolocation entity.
25
+ */
26
+ export function destroy(entity) {
27
+ unwatch(entity)
28
+ }
29
+
30
+ /**
31
+ * Stores a normalized geolocation error.
32
+ * @param {GeolocationEntity} entity - The geolocation entity.
33
+ * @param {GeolocationPositionError} error - The geolocation error.
34
+ */
35
+ export function error(entity, error) {
36
+ entity.error = normalizeError(error)
37
+ entity.isLoading = false
38
+ }
39
+
40
+ /**
41
+ * Requests the current geolocation position.
42
+ * @param {GeolocationEntity} entity - The geolocation entity.
43
+ * @param {GeolocationOptions} options - Position options.
44
+ * @param {{ notify: Function }} api - The store API.
45
+ */
46
+ export function request(entity, options = {}, api) {
47
+ const geolocation = getGeolocation()
48
+
49
+ entity.isSupported = Boolean(geolocation)
50
+ entity.error = null
51
+ const entityId = entity.id
52
+
53
+ if (!geolocation) {
54
+ entity.isLoading = false
55
+ api.notify(`#${entityId}:error`, createUnsupportedError())
56
+ return
57
+ }
58
+
59
+ entity.isLoading = true
60
+
61
+ geolocation.getCurrentPosition(
62
+ (position) => {
63
+ api.notify(`#${entityId}:success`, normalizePosition(position))
64
+ },
65
+ (error) => {
66
+ api.notify(`#${entityId}:error`, error)
67
+ },
68
+ options,
69
+ )
70
+ }
71
+
72
+ /**
73
+ * Stores a normalized geolocation position.
74
+ * @param {GeolocationEntity} entity - The geolocation entity.
75
+ * @param {GeolocationPosition} position - The geolocation position.
76
+ */
77
+ export function success(entity, position) {
78
+ entity.error = null
79
+ entity.isLoading = false
80
+ entity.position = position
81
+ }
82
+
83
+ /**
84
+ * Stops an active geolocation watch.
85
+ * @param {GeolocationEntity} entity - The geolocation entity.
86
+ */
87
+ export function unwatch(entity) {
88
+ const geolocation = getGeolocation()
89
+
90
+ if (entity.watchId !== NO_WATCH_ID && geolocation) {
91
+ geolocation.clearWatch(entity.watchId)
92
+ }
93
+
94
+ entity.isLoading = false
95
+ entity.isWatching = false
96
+ entity.watchId = NO_WATCH_ID
97
+ }
98
+
99
+ /**
100
+ * Starts watching geolocation changes.
101
+ * @param {GeolocationEntity} entity - The geolocation entity.
102
+ * @param {GeolocationOptions} options - Watch options.
103
+ * @param {{ notify: Function }} api - The store API.
104
+ */
105
+ export function watch(entity, options = {}, api) {
106
+ const geolocation = getGeolocation()
107
+
108
+ entity.isSupported = Boolean(geolocation)
109
+ entity.error = null
110
+ const entityId = entity.id
111
+
112
+ if (!geolocation) {
113
+ entity.isLoading = false
114
+ api.notify(`#${entityId}:error`, createUnsupportedError())
115
+ return
116
+ }
117
+
118
+ if (entity.watchId !== NO_WATCH_ID) {
119
+ return
120
+ }
121
+
122
+ entity.isLoading = true
123
+ entity.isWatching = true
124
+ entity.watchId = geolocation.watchPosition(
125
+ (position) => {
126
+ api.notify(`#${entityId}:success`, normalizePosition(position))
127
+ },
128
+ (error) => {
129
+ api.notify(`#${entityId}:error`, error)
130
+ },
131
+ options,
132
+ )
133
+ }
134
+
135
+ function createUnsupportedError() {
136
+ return {
137
+ code: 0,
138
+ message: "Geolocation is not supported by this environment.",
139
+ }
140
+ }
141
+
142
+ function getGeolocation() {
143
+ return globalThis.navigator?.geolocation
144
+ }
145
+
146
+ function normalizeCoords(coords) {
147
+ return {
148
+ accuracy: coords.accuracy,
149
+ altitude: coords.altitude,
150
+ altitudeAccuracy: coords.altitudeAccuracy,
151
+ heading: coords.heading,
152
+ latitude: coords.latitude,
153
+ longitude: coords.longitude,
154
+ speed: coords.speed,
155
+ }
156
+ }
157
+
158
+ function normalizeError(error) {
159
+ return {
160
+ code: error.code,
161
+ message: error.message,
162
+ }
163
+ }
164
+
165
+ function normalizePosition(position) {
166
+ return {
167
+ coords: normalizeCoords(position.coords),
168
+ timestamp: position.timestamp,
169
+ }
170
+ }
@@ -0,0 +1,3 @@
1
+ import * as handlers from "./handlers.js"
2
+
3
+ export const Geolocation = { ...handlers }
@@ -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