litewire 1.0.2 → 1.0.4

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 (2) hide show
  1. package/README.MD +206 -27
  2. package/package.json +1 -1
package/README.MD CHANGED
@@ -1,20 +1,28 @@
1
- # Litewire 1.0.2
1
+ # Litewire 1.0.3
2
2
 
3
- Litewire is a browser-side HTML-over-the-wire library. It binds requests and
4
- reactive behaviors to HTML attributes, swaps server-rendered HTML into the
5
- page, and can load ES module components on demand.
3
+ Litewire is a small browser-side HTML-over-the-wire library. It binds HTTP
4
+ requests, reactive state, and event expressions to HTML attributes, swaps
5
+ server-rendered HTML into the page, and can load ES module components on
6
+ demand.
7
+
8
+ Version 1.0.3 adds reactive `lw:class` bindings, iterable lists with
9
+ `template[lw:for]`, and event-triggered component mounting. It also improves
10
+ radio model initialization and browser-history markup restoration.
6
11
 
7
12
  ## Contents
8
13
 
9
14
  - [Install and start](#install-and-start)
15
+ - [End-to-end example](#end-to-end-example)
10
16
  - [Configuration](#configuration)
11
17
  - [AJAX requests](#ajax-requests)
12
18
  - [Reactive state and bindings](#reactive-state-and-bindings)
19
+ - [Lists](#lists)
13
20
  - [Dynamic components](#dynamic-components)
14
21
  - [HTML swaps and DOM updates](#html-swaps-and-dom-updates)
15
22
  - [Browser history](#browser-history)
16
23
  - [Lifecycle events](#lifecycle-events)
17
24
  - [CSRF](#csrf)
25
+ - [Troubleshooting](#troubleshooting)
18
26
  - [Security considerations](#security-considerations)
19
27
  - [API reference](#api-reference)
20
28
 
@@ -34,10 +42,10 @@ continues scanning elements added later.
34
42
 
35
43
  ### Use the npm package
36
44
 
37
- If version 1.0.2 is available from your configured npm registry:
45
+ Install version 1.0.3 from your configured npm registry:
38
46
 
39
47
  ```sh
40
- npm install litewire@1.0.2
48
+ npm install litewire@1.0.3
41
49
  ```
42
50
 
43
51
  Import the package from your application's JavaScript entry point:
@@ -66,6 +74,45 @@ and observers.
66
74
  Litewire sends a GET request when the button is clicked and replaces the
67
75
  contents of `#result` with the response HTML.
68
76
 
77
+ ## End-to-end example
78
+
79
+ This example uses an HTML form to submit a search query and swaps the
80
+ server-rendered result into a separate element:
81
+
82
+ ```html
83
+ <form
84
+ lw-get="/search"
85
+ lw-target="#search-results"
86
+ lw-indicator="#search-status"
87
+ >
88
+ <label>
89
+ Search
90
+ <input name="q" type="search" required>
91
+ </label>
92
+ <button type="submit">Search</button>
93
+ <span id="search-status" aria-live="polite">Ready</span>
94
+ </form>
95
+
96
+ <section id="search-results" aria-live="polite">
97
+ Enter a query to see results.
98
+ </section>
99
+ ```
100
+
101
+ The browser sends a request like `GET /search?q=...`. The endpoint should
102
+ return an HTML fragment, for example:
103
+
104
+ ```html
105
+ <h2>Results for “tea”</h2>
106
+ <ul>
107
+ <li>Green tea</li>
108
+ <li>Black tea</li>
109
+ </ul>
110
+ ```
111
+
112
+ Litewire replaces the contents of `#search-results` with that fragment. The
113
+ new content is scanned for Litewire attributes and any `lw-component` modules
114
+ are loaded. No JSON decoding or client-side template rendering is performed.
115
+
69
116
  ## Configuration
70
117
 
71
118
  Litewire resolves its base URL in this order:
@@ -160,11 +207,38 @@ the element is scanned:
160
207
  <div id="account-summary"></div>
161
208
  ```
162
209
 
210
+ ### Request and form behavior
211
+
212
+ - A form listens for `submit` by default. Other elements listen for `click`.
213
+ Set `lw-trigger` to a DOM event name to use another event; `load` runs the
214
+ request immediately when the element is discovered.
215
+ - A GET request serializes all successful controls in the nearest containing
216
+ form as URL query parameters. If there is no containing form, a control with
217
+ a `name` and `value` contributes that one parameter.
218
+ - POST, PUT, and DELETE requests send the nearest containing form as
219
+ `FormData`. If there is no form, Litewire sends no request body.
220
+ - Litewire checks `response.ok`; a non-success status is treated as a request
221
+ failure. Successful response bodies are read as text and inserted as HTML.
222
+ Set the response `Content-Type` appropriately on the server, even though
223
+ Litewire does not parse it.
224
+ - `lw-target` is resolved with `document.querySelector`. If omitted, the
225
+ triggering element is the target. Ensure the selector matches the intended
226
+ element when the response is applied.
227
+ - A trigger and each loading indicator receive the configured loading class
228
+ while the request is in flight. By default, indicators are descendants with
229
+ the `litewire-indicator` class; `lw-indicator` can select indicators
230
+ elsewhere in the document.
231
+ - Request failures are logged to the console and emit `litewire:error` from
232
+ the trigger. The loading class is removed on both success and failure.
233
+
234
+ `lw-trigger="load"` is useful for initial content, but avoid putting it on
235
+ large numbers of elements if they would all issue requests at once.
236
+
163
237
  ## Reactive state and bindings
164
238
 
165
- Litewire 1.0.2 adds a small reactive layer using `lw:state`, `lw:model`,
166
- `lw:text`, `lw:show`, and event-expression attributes. The colon in these
167
- attribute names is intentional.
239
+ Litewire provides a small reactive layer using `lw:state`, `lw:model`,
240
+ `lw:text`, `lw:show`, `lw:class`, `lw:for`, and event-expression attributes.
241
+ The colon in these attribute names is intentional.
168
242
 
169
243
  ### Local state
170
244
 
@@ -181,9 +255,10 @@ object:
181
255
  ```
182
256
 
183
257
  Assignments and deletions of top-level state properties trigger rendering of
184
- descendant `lw:text` and `lw:show` bindings owned by that state root. State is
185
- shallowly reactive: mutating a nested object or array in place does not itself
186
- trigger a render. Replace the top-level property to trigger one, for example
258
+ bindings on that element and its descendants that belong to that state root.
259
+ State is shallowly reactive:
260
+ mutating a nested object or array in place does not itself trigger a render.
261
+ Replace the top-level property to trigger one, for example
187
262
  `settings = { ...settings, enabled: false }`.
188
263
 
189
264
  Nested `lw:state` elements establish their own state scope. A binding uses the
@@ -196,10 +271,38 @@ global models instead.
196
271
  text. `null` and `undefined` render as an empty string.
197
272
  - `lw:show="expression"` sets the element's `hidden` property based on the
198
273
  truthiness of the result.
274
+ - `lw:class="{ className: expression }"` adds each class whose expression is
275
+ truthy and removes classes previously managed by this binding when they
276
+ become false. Other classes on the element are left alone; a key may contain
277
+ multiple space-separated class names.
199
278
 
200
279
  Expressions are evaluated when the element is first scanned and again when
201
- their owning local state renders. Global model updates also update matching
202
- `lw:text` bindings.
280
+ their owning local state renders. Global model updates refresh matching
281
+ `lw:text` bindings and reevaluate global `lw:class` bindings and loops.
282
+
283
+ ### Lists
284
+
285
+ Use `lw:for` on a `<template>` to repeat its contents for each value in an
286
+ iterable. The repeated expressions can access the item name and `$index`;
287
+ `(item, index)` also declares a named index:
288
+
289
+ ```html
290
+ <ul lw:state="{ items: ['Apples', 'Pears'] }">
291
+ <template lw:for="(item, index) of items">
292
+ <li>
293
+ <span lw:text="index + 1"></span>.
294
+ <span lw:text="item"></span>
295
+ </li>
296
+ </template>
297
+ </ul>
298
+ ```
299
+
300
+ The expression must evaluate to an iterable, such as an array, string, `Set`,
301
+ or `Map`; `null` and `undefined` render no rows. Assign a new array to the
302
+ top-level state property to refresh the list. Litewire recreates the repeated
303
+ DOM when the owning state renders; it does not key or preserve individual rows
304
+ across updates. `lw:for` also works with global models set through
305
+ `litewire.setModel()`.
203
306
 
204
307
  ### Form models
205
308
 
@@ -216,18 +319,44 @@ their owning local state renders. Global model updates also update matching
216
319
  The default `lw:model` and `lw:model.live` listen for `input`.
217
320
  `lw:model.change` listens for `change`, and `lw:model.blur` listens for `blur`.
218
321
  For checkboxes, the model value is a boolean; for radio inputs, it is the
219
- checked value or `null`; other controls use their value.
322
+ checked value or `null`. Unchecked radio buttons do not initialize or overwrite
323
+ the shared model; other controls use their value.
220
324
 
221
325
  Without a local `lw:state` root, models are stored globally and can be read or
222
326
  written with the instance methods `litewire.getModel(name)` and
223
327
  `litewire.setModel(name, value)`. Global model changes dispatch a
224
328
  `litewire:model` event on `document`, with `{ name, value }` in `event.detail`.
225
329
 
330
+ For example, a control outside a local state root can populate a global model,
331
+ and a matching text binding can display it:
332
+
333
+ ```html
334
+ <input name="status" lw:model="status" aria-label="Status">
335
+ <p>Current status: <span lw:text="status"></span></p>
336
+
337
+ <script type="module">
338
+ import "litewire";
339
+
340
+ window.addEventListener("DOMContentLoaded", () => {
341
+ window.litewire.setModel("status", "Ready");
342
+ });
343
+ </script>
344
+ ```
345
+
346
+ When setting global models from application code, set them on the initialized
347
+ `window.litewire` instance. `setModel()` updates a `lw:text` binding whose
348
+ expression is exactly that model name, reevaluates global `lw:class` and
349
+ `lw:for` bindings, and dispatches `litewire:model`.
350
+
226
351
  A model name beginning with `#` or `.` is treated as a CSS selector instead of
227
352
  a state key. Litewire copies the control's value to matching elements (to
228
353
  `value` for form controls, otherwise to `textContent`) and dispatches
229
354
  `litewire:model` on each target with `{ value }` in `event.detail`.
230
355
 
356
+ For example, `lw:model="#live-preview"` copies an input value to the
357
+ `textContent` of the element with that ID. Selector-based models update the
358
+ matching targets; they are not reactive state properties.
359
+
231
360
  ### Event expressions
232
361
 
233
362
  Supported event attributes are:
@@ -271,14 +400,18 @@ Supported event modifiers:
271
400
  | `.throttle` | Runs at most once per 250 ms |
272
401
 
273
402
  Modifiers can be combined, for example `lw:click.prevent.once="save()"`.
403
+ Debounce and throttle delays can be customized in milliseconds, for example
404
+ `lw:input.debounce.400ms="search()"` or
405
+ `lw:click.throttle.1000ms="refresh()"`. `.outside` listens at the document
406
+ level so it can observe events outside the element.
274
407
 
275
408
  ## Dynamic components
276
409
 
277
410
  An element with `lw-component` dynamically imports a JavaScript module. The
278
411
  path is joined to Litewire's base URL. Litewire selects the module's default
279
- export first, then a named `imageUploader` export, then the first export. If
280
- the selected export is a function, it is called with the host element and its
281
- `data-*` attributes converted to an object:
412
+ export first, then its first export. If the selected export is a function, it
413
+ is called with the host element and its `data-*` attributes converted to an
414
+ object:
282
415
 
283
416
  ```html
284
417
  <div
@@ -297,7 +430,30 @@ export default class Greeting {
297
430
  ```
298
431
 
299
432
  New components inserted by a swap or added to the document are discovered
300
- automatically. A given element is mounted only once.
433
+ automatically. By default, and with `lw-trigger="load"`, a component mounts
434
+ when discovered. Set `lw-trigger` to another event name to defer mounting
435
+ until that event fires. On a triggered component, `lw-target` optionally
436
+ selects the element passed to the component constructor; the component path
437
+ and `data-*` parameters still come from the element carrying `lw-component`.
438
+ A component is mounted only once after a successful mount. To load one after
439
+ an event and mount it into another element, put `lw-trigger` and `lw-target`
440
+ on the component source:
441
+
442
+ ```html
443
+ <button
444
+ type="button"
445
+ lw-component="components/widget.js"
446
+ lw-trigger="click"
447
+ lw-target="#widget"
448
+ data-mode="compact"
449
+ >
450
+ Load widget
451
+ </button>
452
+ <div id="widget"></div>
453
+ ```
454
+
455
+ The module path and `data-*` parameters come from the element carrying
456
+ `lw-component`; `lw-target` is the element passed to the component.
301
457
 
302
458
  ## HTML swaps and DOM updates
303
459
 
@@ -332,11 +488,13 @@ a custom URL as the attribute value:
332
488
  </a>
333
489
  ```
334
490
 
335
- Litewire stores the URL, target selector, and target HTML in history state.
336
- When navigating with Back or Forward, it restores cached HTML when the state
337
- contains a selector and markup and the target still exists. Otherwise it
338
- attempts to fetch the stored URL. This caches only the selected target's HTML,
339
- not the complete document or JavaScript state.
491
+ Litewire stores the URL, target selector, swap type, and target markup in
492
+ history state. When it creates the initial history entry, it saves the current
493
+ markup so Back can restore the prior view. For `outerHTML`, cached markup
494
+ includes the target element itself. When navigating with Back or Forward,
495
+ Litewire restores cached markup when the target still exists; otherwise it
496
+ attempts to fetch the stored URL. This caches only the selected target's
497
+ markup, not the complete document or JavaScript state.
340
498
 
341
499
  ## Lifecycle events
342
500
 
@@ -373,6 +531,19 @@ When this meta tag is present, Litewire adds its token as the
373
531
  The server must still validate the token and enforce its own authorization and
374
532
  request protections.
375
533
 
534
+ ## Troubleshooting
535
+
536
+ | Symptom | Things to check |
537
+ |---|---|
538
+ | The global `window.litewire` is missing | Load Litewire in a browser as a module and check the console for module loading errors. Auto-initialization occurs after the document is ready. |
539
+ | A request does not run | Confirm the element has one of `lw-get`, `lw-post`, `lw-put`, or `lw-delete`. Check its default or configured `lw-trigger` event and whether another handler prevents the event from reaching it. |
540
+ | The server receives no form values | Make sure the trigger is inside the form, or is the form itself. Non-GET requests without a containing form have no body. For GET, a standalone control needs both `name` and `value`. |
541
+ | The request succeeds but the page does not change | Return an HTML fragment, verify `lw-target` matches an element, and check that the selected `lw-swap` behavior is suitable. |
542
+ | A request reports an error | Inspect the browser console and the `litewire:error` event. Litewire treats HTTP statuses outside the 2xx range as failures. |
543
+ | A state binding stays unchanged | Confirm the binding is under the intended `lw:state` root. Replace a top-level state property; nested mutations are not observed. |
544
+ | A component does not mount | Check the module URL, its default or first export, and that the export is a function. For deferred components, verify the triggering event and optional `lw-target` selector. |
545
+ | A directive in newly inserted HTML does not run | Ensure the HTML is inserted through a Litewire swap or added to the observed document body. The observer watches additions under `document.body`. |
546
+
376
547
  ## Security considerations
377
548
 
378
549
  Litewire inserts response bodies as HTML. Only return trusted or appropriately
@@ -387,18 +558,26 @@ untrusted user input.
387
558
  | Attribute / API | Description |
388
559
  |---|---|
389
560
  | `lw-get`, `lw-post`, `lw-put`, `lw-delete` | Request path and HTTP method |
390
- | `lw-trigger` | Event name; defaults to `click` or form `submit`; `load` runs immediately |
391
- | `lw-target` | CSS selector for the response target |
561
+ | `lw-trigger` | Request event (defaults to `click` or form `submit`; `load` runs on scan); for components, event to defer mounting |
562
+ | `lw-target` | CSS selector for the response target; for a triggered component, selects the element passed to the component |
392
563
  | `lw-swap` | `innerHTML`, `outerHTML`, `prepend`, or `append` |
393
564
  | `lw-indicator` | CSS selector for loading indicators |
394
565
  | `lw-push-url` | `true`, `false`, or a custom history URL |
395
- | `lw-component` | Path to a dynamically imported ES module |
566
+ | `lw-component` | Path to a dynamically imported ES module; its default export or first export is mounted |
396
567
  | `lw:state` | JavaScript object expression that initializes local reactive state |
397
568
  | `lw:model`, `.live`, `.change`, `.blur` | Bind a form control to a state or global model |
398
569
  | `lw:text` | Render an expression as text |
399
570
  | `lw:show` | Toggle the element's `hidden` property from an expression |
571
+ | `lw:class` | Add or remove classes reactively from an object expression |
572
+ | `template[lw:for]` | Repeat template contents for an iterable expression; item and index variables are available to bindings |
400
573
  | `lw:click`, `lw:input`, `lw:change`, `lw:submit`, `lw:keydown`, `lw:keyup`, `lw:focus`, `lw:blur` | Evaluate an expression in response to an event; supports the modifiers documented above |
401
574
  | `litewire.getModel(name)` | Read a global model |
402
575
  | `litewire.setModel(name, value)` | Set a global model and update matching bindings |
576
+ | `new Litewire({ baseUrl, loadingClass })` | Construct and initialize an instance with optional configuration |
577
+
578
+ The browser module exports the `Litewire` class as its default export and
579
+ auto-creates `window.litewire` if that global is not already set. When creating
580
+ a custom instance, set `window.litewire` to a placeholder before importing the
581
+ module if needed to prevent auto-initialization, then assign the instance.
403
582
 
404
583
  **Buy my kids schoolbooks:** [https://paypal.me/justumg](https://paypal.me/justumg)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "litewire",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "A lightweight HTML-driven request engine with reactive state and dynamic ES module components.",
5
5
  "main": "./litewire.js",
6
6
  "module": "./litewire.js",