use-prms 0.2.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 +320 -0
- package/dist/hash.cjs +505 -0
- package/dist/hash.cjs.map +1 -0
- package/dist/hash.d.cts +1 -0
- package/dist/hash.d.ts +1 -0
- package/dist/hash.js +475 -0
- package/dist/hash.js.map +1 -0
- package/dist/index.cjs +502 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +341 -0
- package/dist/index.d.ts +341 -0
- package/dist/index.js +472 -0
- package/dist/index.js.map +1 -0
- package/package.json +60 -0
package/README.md
ADDED
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
# use-prms
|
|
2
|
+
|
|
3
|
+
Type-safe URL query parameter management with minimal, human-readable encoding.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🎯 **Type-safe**: Full TypeScript support with generic `Param<T>` interface
|
|
8
|
+
- 📦 **Tiny URLs**: Smart encoding - omit defaults, use short keys, `+` for spaces
|
|
9
|
+
- ⚛️ **React hooks**: `useUrlParam()` and `useUrlParams()` for seamless integration
|
|
10
|
+
- 🔧 **Framework-agnostic**: Core utilities work anywhere, React hooks are optional
|
|
11
|
+
- 🌳 **Tree-shakeable**: ESM + CJS builds with TypeScript declarations
|
|
12
|
+
- 0️⃣ **Zero dependencies**: Except React (peer dependency, optional)
|
|
13
|
+
- 🔁 **Multi-value params**: Support for repeated keys like `?tag=a&tag=b`
|
|
14
|
+
- #️⃣ **Hash params**: Use hash fragment (`#key=value`) instead of query string
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install use-prms
|
|
20
|
+
# or
|
|
21
|
+
pnpm add use-prms
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Quick Start
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
import { useUrlParam, boolParam, stringParam, intParam } from 'use-prms'
|
|
28
|
+
|
|
29
|
+
function MyComponent() {
|
|
30
|
+
const [zoom, setZoom] = useUrlParam('z', boolParam)
|
|
31
|
+
const [device, setDevice] = useUrlParam('d', stringParam())
|
|
32
|
+
const [count, setCount] = useUrlParam('n', intParam(10))
|
|
33
|
+
|
|
34
|
+
// URL: ?z&d=gym&n=5
|
|
35
|
+
// zoom = true, device = "gym", count = 5
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<div>
|
|
39
|
+
<button onClick={() => setZoom(!zoom)}>Toggle Zoom</button>
|
|
40
|
+
<input value={device ?? ''} onChange={e => setDevice(e.target.value)} />
|
|
41
|
+
<button onClick={() => setCount(count + 1)}>Count: {count}</button>
|
|
42
|
+
</div>
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Built-in Param Types
|
|
48
|
+
|
|
49
|
+
### Boolean
|
|
50
|
+
```typescript
|
|
51
|
+
const [enabled, setEnabled] = useUrlParam('e', boolParam)
|
|
52
|
+
// ?e → true
|
|
53
|
+
// (absent) → false
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Strings
|
|
57
|
+
```typescript
|
|
58
|
+
const [name, setName] = useUrlParam('n', stringParam()) // optional
|
|
59
|
+
const [mode, setMode] = useUrlParam('m', defStringParam('auto')) // with default
|
|
60
|
+
// ?n=foo → "foo"
|
|
61
|
+
// (absent) → undefined / "auto"
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Numbers
|
|
65
|
+
```typescript
|
|
66
|
+
const [count, setCount] = useUrlParam('c', intParam(0))
|
|
67
|
+
const [ratio, setRatio] = useUrlParam('r', floatParam(1.0))
|
|
68
|
+
const [id, setId] = useUrlParam('id', optIntParam) // number | null
|
|
69
|
+
// ?c=5&r=1.5&id=123 → 5, 1.5, 123
|
|
70
|
+
// (absent) → 0, 1.0, null
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Enums
|
|
74
|
+
```typescript
|
|
75
|
+
const [theme, setTheme] = useUrlParam(
|
|
76
|
+
't',
|
|
77
|
+
enumParam('light', ['light', 'dark', 'auto'] as const)
|
|
78
|
+
)
|
|
79
|
+
// ?t=dark → "dark"
|
|
80
|
+
// ?t=invalid → "light" (warns in console)
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Arrays (delimiter-separated)
|
|
84
|
+
```typescript
|
|
85
|
+
const [tags, setTags] = useUrlParam('tags', stringsParam([], ','))
|
|
86
|
+
const [ids, setIds] = useUrlParam('ids', numberArrayParam([]))
|
|
87
|
+
// ?tags=foo,bar,baz → ["foo", "bar", "baz"]
|
|
88
|
+
// ?ids=1,2,3 → [1, 2, 3]
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Multi-value Arrays (repeated keys)
|
|
92
|
+
```typescript
|
|
93
|
+
import { useMultiUrlParam, multiStringParam, multiIntParam } from 'use-prms'
|
|
94
|
+
|
|
95
|
+
const [tags, setTags] = useMultiUrlParam('tag', multiStringParam())
|
|
96
|
+
// ?tag=foo&tag=bar&tag=baz → ["foo", "bar", "baz"]
|
|
97
|
+
|
|
98
|
+
const [ids, setIds] = useMultiUrlParam('id', multiIntParam())
|
|
99
|
+
// ?id=1&id=2&id=3 → [1, 2, 3]
|
|
100
|
+
|
|
101
|
+
// Also available: multiFloatParam()
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Compact Code Mapping
|
|
105
|
+
```typescript
|
|
106
|
+
// Single value with short codes
|
|
107
|
+
const [metric, setMetric] = useUrlParam('y', codeParam('Rides', {
|
|
108
|
+
Rides: 'r',
|
|
109
|
+
Minutes: 'm',
|
|
110
|
+
}))
|
|
111
|
+
// ?y=m → "Minutes", omitted for default "Rides"
|
|
112
|
+
|
|
113
|
+
// Multi-value with short codes (omits when all selected)
|
|
114
|
+
const [regions, setRegions] = useUrlParam('r', codesParam(
|
|
115
|
+
['NYC', 'JC', 'HOB'],
|
|
116
|
+
{ NYC: 'n', JC: 'j', HOB: 'h' }
|
|
117
|
+
))
|
|
118
|
+
// ?r=nj → ["NYC", "JC"], omitted when all three selected
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Pagination
|
|
122
|
+
```typescript
|
|
123
|
+
const [page, setPage] = useUrlParam('p', paginationParam(20))
|
|
124
|
+
// Encodes offset + pageSize compactly using + as delimiter:
|
|
125
|
+
// { offset: 0, pageSize: 20 } → (omitted)
|
|
126
|
+
// { offset: 0, pageSize: 50 } → ?p=+50
|
|
127
|
+
// { offset: 100, pageSize: 20 } → ?p=100
|
|
128
|
+
// { offset: 100, pageSize: 50 } → ?p=100+50
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Custom Params
|
|
132
|
+
|
|
133
|
+
Create your own param encoders/decoders:
|
|
134
|
+
|
|
135
|
+
```typescript
|
|
136
|
+
import type { Param } from 'use-prms'
|
|
137
|
+
|
|
138
|
+
// Example: Compact date encoding (YYMMDD)
|
|
139
|
+
const dateParam: Param<Date> = {
|
|
140
|
+
encode: (date) => {
|
|
141
|
+
const yy = String(date.getFullYear()).slice(-2)
|
|
142
|
+
const mm = String(date.getMonth() + 1).padStart(2, '0')
|
|
143
|
+
const dd = String(date.getDate()).padStart(2, '0')
|
|
144
|
+
return `${yy}${mm}${dd}`
|
|
145
|
+
},
|
|
146
|
+
decode: (str) => {
|
|
147
|
+
if (!str || str.length !== 6) return new Date()
|
|
148
|
+
const yy = parseInt('20' + str.slice(0, 2), 10)
|
|
149
|
+
const mm = parseInt(str.slice(2, 4), 10) - 1
|
|
150
|
+
const dd = parseInt(str.slice(4, 6), 10)
|
|
151
|
+
return new Date(yy, mm, dd)
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const [date, setDate] = useUrlParam('d', dateParam)
|
|
156
|
+
// ?d=251123 → Date(2025, 10, 23)
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Batch Updates
|
|
160
|
+
|
|
161
|
+
Use `useUrlParams()` to update multiple parameters atomically:
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
import { useUrlParams, intParam, boolParam } from 'use-prms'
|
|
165
|
+
|
|
166
|
+
const { values, setValues } = useUrlParams({
|
|
167
|
+
page: intParam(1),
|
|
168
|
+
size: intParam(20),
|
|
169
|
+
grid: boolParam
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
// Update multiple params at once (single history entry)
|
|
173
|
+
setValues({ page: 2, size: 50 })
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## URL Encoding
|
|
177
|
+
|
|
178
|
+
- **Spaces**: Encoded as `+` (standard form-urlencoded)
|
|
179
|
+
- **Defaults**: Omitted from URL (keeps URLs minimal)
|
|
180
|
+
- **Booleans**: Present = true (`?z`), absent = false
|
|
181
|
+
- **Empty values**: Valueless params (`?key` without `=`)
|
|
182
|
+
|
|
183
|
+
Example:
|
|
184
|
+
```typescript
|
|
185
|
+
const [devices, setDevices] = useUrlParam('d', stringsParam([], ' '))
|
|
186
|
+
setDevices(['gym', 'bedroom'])
|
|
187
|
+
// URL: ?d=gym+bedroom
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## Framework-Agnostic Core
|
|
191
|
+
|
|
192
|
+
Use the core utilities without React:
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
import { boolParam, serializeParams, parseParams } from 'use-prms'
|
|
196
|
+
|
|
197
|
+
// Encode
|
|
198
|
+
const params = { z: boolParam.encode(true), d: 'gym' }
|
|
199
|
+
const search = serializeParams(params) // "z&d=gym"
|
|
200
|
+
|
|
201
|
+
// Decode
|
|
202
|
+
const parsed = parseParams(window.location.search)
|
|
203
|
+
const zoom = boolParam.decode(parsed.z) // true
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## Hash Params
|
|
207
|
+
|
|
208
|
+
Use hash fragment (`#key=value`) instead of query string (`?key=value`):
|
|
209
|
+
|
|
210
|
+
```typescript
|
|
211
|
+
// Just change the import path
|
|
212
|
+
import { useUrlParam, boolParam } from 'use-prms/hash'
|
|
213
|
+
|
|
214
|
+
const [zoom, setZoom] = useUrlParam('z', boolParam)
|
|
215
|
+
// URL: https://example.com/#z (instead of ?z)
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Same API, different URL location. Useful when query strings conflict with server routing or you want params to survive page reloads without server involvement.
|
|
219
|
+
|
|
220
|
+
## API Reference
|
|
221
|
+
|
|
222
|
+
### `useUrlParam<T>(key: string, param: Param<T>, push?: boolean)`
|
|
223
|
+
|
|
224
|
+
React hook for managing a single URL parameter.
|
|
225
|
+
|
|
226
|
+
- `key`: Query parameter key
|
|
227
|
+
- `param`: Param encoder/decoder
|
|
228
|
+
- `push`: Use pushState (true) or replaceState (false, default)
|
|
229
|
+
- Returns: `[value: T, setValue: (value: T) => void]`
|
|
230
|
+
|
|
231
|
+
### `useUrlParams<P>(params: P, push?: boolean)`
|
|
232
|
+
|
|
233
|
+
React hook for managing multiple URL parameters together.
|
|
234
|
+
|
|
235
|
+
- `params`: Object mapping keys to Param types
|
|
236
|
+
- `push`: Use pushState (true) or replaceState (false, default)
|
|
237
|
+
- Returns: `{ values, setValues }`
|
|
238
|
+
|
|
239
|
+
### `useMultiUrlParam<T>(key: string, param: MultiParam<T>, push?: boolean)`
|
|
240
|
+
|
|
241
|
+
React hook for managing a multi-value URL parameter (repeated keys).
|
|
242
|
+
|
|
243
|
+
- `key`: Query parameter key
|
|
244
|
+
- `param`: MultiParam encoder/decoder
|
|
245
|
+
- `push`: Use pushState (true) or replaceState (false, default)
|
|
246
|
+
- Returns: `[value: T, setValue: (value: T) => void]`
|
|
247
|
+
|
|
248
|
+
### `Param<T>`
|
|
249
|
+
|
|
250
|
+
Bidirectional encoder/decoder interface:
|
|
251
|
+
|
|
252
|
+
```typescript
|
|
253
|
+
type Param<T> = {
|
|
254
|
+
encode: (value: T) => string | undefined
|
|
255
|
+
decode: (encoded: string | undefined) => T
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### `MultiParam<T>`
|
|
260
|
+
|
|
261
|
+
Multi-value encoder/decoder interface:
|
|
262
|
+
|
|
263
|
+
```typescript
|
|
264
|
+
type MultiParam<T> = {
|
|
265
|
+
encode: (value: T) => string[]
|
|
266
|
+
decode: (encoded: string[]) => T
|
|
267
|
+
}
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
### Built-in Param Types
|
|
271
|
+
|
|
272
|
+
| Param | Type | Description |
|
|
273
|
+
|-------|------|-------------|
|
|
274
|
+
| `boolParam` | `Param<boolean>` | `?key` = true, absent = false |
|
|
275
|
+
| `stringParam(init?)` | `Param<string \| undefined>` | Optional string |
|
|
276
|
+
| `defStringParam(init)` | `Param<string>` | Required string with default |
|
|
277
|
+
| `intParam(init)` | `Param<number>` | Integer with default |
|
|
278
|
+
| `floatParam(init)` | `Param<number>` | Float with default |
|
|
279
|
+
| `optIntParam` | `Param<number \| null>` | Optional integer |
|
|
280
|
+
| `enumParam(init, values)` | `Param<T>` | Validated enum |
|
|
281
|
+
| `stringsParam(init?, delim?)` | `Param<string[]>` | Delimiter-separated strings |
|
|
282
|
+
| `numberArrayParam(init?)` | `Param<number[]>` | Comma-separated numbers |
|
|
283
|
+
| `codeParam(init, codeMap)` | `Param<T>` | Enum with short URL codes |
|
|
284
|
+
| `codesParam(allValues, codeMap, sep?)` | `Param<T[]>` | Multi-value with short codes |
|
|
285
|
+
| `paginationParam(defaultSize, validSizes?)` | `Param<Pagination>` | Offset + page size |
|
|
286
|
+
|
|
287
|
+
### Built-in MultiParam Types
|
|
288
|
+
|
|
289
|
+
| Param | Type | Description |
|
|
290
|
+
|-------|------|-------------|
|
|
291
|
+
| `multiStringParam(init?)` | `MultiParam<string[]>` | Repeated string params |
|
|
292
|
+
| `multiIntParam(init?)` | `MultiParam<number[]>` | Repeated integer params |
|
|
293
|
+
| `multiFloatParam(init?)` | `MultiParam<number[]>` | Repeated float params |
|
|
294
|
+
|
|
295
|
+
### Core Utilities
|
|
296
|
+
|
|
297
|
+
- `serializeParams(params)`: Convert params object to URL query string *(deprecated, use `serializeMultiParams`)*
|
|
298
|
+
- `parseParams(source)`: Parse URL string or URLSearchParams to object *(deprecated, use `parseMultiParams`)*
|
|
299
|
+
- `serializeMultiParams(params)`: Convert multi-value params to URL query string
|
|
300
|
+
- `parseMultiParams(source)`: Parse URL to multi-value params object
|
|
301
|
+
- `getCurrentParams()`: Get current URL params (browser only)
|
|
302
|
+
- `updateUrl(params, push?)`: Update URL without reloading (browser only)
|
|
303
|
+
|
|
304
|
+
## Examples
|
|
305
|
+
|
|
306
|
+
Projects using `use-prms`:
|
|
307
|
+
|
|
308
|
+
- [runsascoded/awair] – Air quality dashboard with URL-persisted chart settings
|
|
309
|
+
|
|
310
|
+
Example: [`awair.runsascoded.com/?d=+br&y=thZ&t=-3d`][awair-example]
|
|
311
|
+
- `d=+br`: show default device + "bedroom" (`+` encodes space, leading space means "include default")
|
|
312
|
+
- `y=thZ`: left axis = temp (`t`), right axis = humidity (`h`), Y-axes don't start from zero (`Z`)
|
|
313
|
+
- `t=-3d`: time range = last 3 days
|
|
314
|
+
|
|
315
|
+
[runsascoded/awair]: https://github.com/runsascoded/awair
|
|
316
|
+
[awair-example]: https://awair.runsascoded.com/?d=+br&y=thZ&t=-3d
|
|
317
|
+
|
|
318
|
+
## License
|
|
319
|
+
|
|
320
|
+
MIT
|