coldwired 0.18.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Paul Chavard
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,168 @@
1
+ # @coldwired/actions [![npm package][npm-badge]][npm]
2
+
3
+ [npm-badge]: https://img.shields.io/npm/v/coldwired.svg
4
+ [npm]: https://www.npmjs.com/package/coldwired
5
+
6
+ ## Why?
7
+
8
+ Initial inspiration was [turbo-stream](https://turbo.hotwired.dev/handbook/streams), which allows
9
+ for the application of incremental changes to the page. The problem we faced was that applying
10
+ changes wiped out client changes and it was not always practical to propagate the client state
11
+ necessary for rendering to the server. We wanted to be able to preserve some state, such as open
12
+ dialogs an menus or input values.
13
+
14
+ ## How?
15
+
16
+ `Actions` will create a `MutationObserver` and a `WeakMap` of some of the DOM state, such as class
17
+ names, aria attributes, and input values. This allows it to preserve state across morph changes. You
18
+ always have the possibility to force a state update through the attribute `data-turbo-force`.
19
+
20
+ ## Usage
21
+
22
+ ### Action
23
+
24
+ An action is an object describing a DOM operation. Actions can be fully serialized to carry them
25
+ over the wire ([turbo-stream](https://turbo.hotwired.dev/handbook/streams)).
26
+
27
+ ```ts
28
+ type Action = {
29
+ action:
30
+ | 'after'
31
+ | 'before'
32
+ | 'append'
33
+ | 'prepend'
34
+ | 'replace'
35
+ | 'update'
36
+ | 'remove'
37
+ | 'focus'
38
+ | 'enable'
39
+ | 'disable'
40
+ | 'hide'
41
+ | 'show';
42
+ targets: Element[] | string;
43
+ fragment?: DocumentFragment | string;
44
+ delay?: number;
45
+ pin?: boolean;
46
+ };
47
+ ```
48
+
49
+ ### Setup
50
+
51
+ Before you start working with actions, you need to create and register an instance of `Actions`.
52
+ After that, actions can be applied through the `Actions` instance or dispatched as events. We also
53
+ provide an implementation of [turbo-stream](https://turbo.hotwired.dev/handbook/streams) on top of
54
+ `Actions` through the [coldwired/turbo-stream](https://www.npmjs.com/package/coldwired)
55
+ package.
56
+
57
+ ```ts
58
+ import { Actions } from 'coldwired/actions';
59
+
60
+ const actions = new Actions({ element: document.body });
61
+ actions.observe();
62
+ ```
63
+
64
+ ### DOM manipulation
65
+
66
+ ```ts
67
+ // Insert a fragment after each target element
68
+ actions.after({ targets: '.item', fragment: '<p>Hello World</p>' });
69
+
70
+ // Insert a fragment before each target element
71
+ actions.before({ targets: '.item', fragment: '<p>Hello World</p>' });
72
+
73
+ // Append a fragment after the last child of each target element
74
+ actions.append({ targets: '.item', fragment: '<p>Hello World</p>' });
75
+
76
+ // Prepend a fragment before the first child of each target element
77
+ actions.prepend({ targets: '.item', fragment: '<p>Hello World</p>' });
78
+
79
+ // Replace every target element with the fragment.
80
+ // Uses morph to preserve interactive state
81
+ actions.replace({ targets: '.item', fragment: '<p>Hello World</p>' });
82
+
83
+ // Update every target inner with the fragment.
84
+ // Uses morph to preserve interactive state
85
+ actions.update({ targets: '.item', fragment: '<p>Hello World</p>' });
86
+
87
+ // Remove all target elements
88
+ actions.remove({ targets: '.item' });
89
+
90
+ // Focus first target element
91
+ actions.focus({ targets: '.item' });
92
+
93
+ // Disable all target elements
94
+ actions.disable({ targets: '.item' });
95
+
96
+ // Enable all target elements
97
+ actions.enable({ targets: '.item' });
98
+
99
+ // Hide all target elements
100
+ actions.hide({ targets: '.item' });
101
+
102
+ // Show all target elements
103
+ actions.show({ targets: '.item' });
104
+
105
+ // Apply actions in batch. This is the low level API
106
+ actions.applyActions([
107
+ {
108
+ action: 'update',
109
+ targets: '.item-to-update',
110
+ fragment: '<p>Hello World</p>',
111
+ },
112
+ {
113
+ action: 'remove',
114
+ targets: '.item-to-remove',
115
+ },
116
+ ]);
117
+ ```
118
+
119
+ ### Dispatch from anywhere
120
+
121
+ If you want to dispatch actions from places where you don't have access to the `Actions` instance,
122
+ you can use global API.
123
+
124
+ ```ts
125
+ import * as Actions from 'coldwired/actions';
126
+
127
+ Actions.after({ targets: '.item', fragment: '<p>Hello World</p>' });
128
+
129
+ // Same as `applyActions` but you don't need access to the instance
130
+ Actions.dispatchActions([
131
+ {
132
+ action: 'update',
133
+ targets: '.item-to-update',
134
+ fragment: '<p>Hello World</p>',
135
+ },
136
+ {
137
+ action: 'remove',
138
+ targets: '.item-to-remove',
139
+ },
140
+ ]);
141
+ ```
142
+
143
+ ### Delayed actions
144
+
145
+ You can add a delay to any action, which is useful for hiding flash messages after a short period of
146
+ time, for example.
147
+
148
+ ```ts
149
+ // Hide targets after 2 seconds delay
150
+ actions.hide({ targets: '.item', delay: 2000 });
151
+ ```
152
+
153
+ ### Pinned actions
154
+
155
+ An action can be pinned — this is mostly useful in combination with full-page morph.
156
+
157
+ ```ts
158
+ // In some client code append a new warning
159
+ actions.append({ targets: '.warnings', fragment: '<p>Warning !</p>', pin: true });
160
+
161
+ // Later, refresh the whole page. It will wipe out the warning added earlier.
162
+ // By running `applyPinnedActions`, you can restore previous changes
163
+ actions.morph(document, newDocument);
164
+ actions.applyPinnedActions();
165
+
166
+ // When you navigate to a new page, you might want to reset any pinned actions
167
+ actions.reset();
168
+ ```
@@ -0,0 +1,134 @@
1
+ //#region src/actions/plugin.d.ts
2
+ interface Plugin {
3
+ ready(): Promise<void>;
4
+ init(element: Element): void;
5
+ validate?(element: Element | Document): void;
6
+ onCreateElement?(element: Element): boolean;
7
+ onBeforeUpdateElement?(element: Element, toElement: Element | DocumentFragment): boolean;
8
+ onBeforeDestroyElement?(element: Element): boolean;
9
+ }
10
+ //#endregion
11
+ //#region src/actions/schema.d.ts
12
+ type Schema = {
13
+ forceAttribute: string;
14
+ focusGroupAttribute: string;
15
+ focusDirectionAttribute: string;
16
+ hiddenClassName: string;
17
+ };
18
+ declare const defaultSchema: Schema;
19
+ //#endregion
20
+ //#region src/actions/actions.d.ts
21
+ declare const voidActionNames: readonly ['remove', 'focus', 'enable', 'disable', 'hide', 'show', 'reset', 'refresh'];
22
+ declare const fragmentActionNames: readonly ['after', 'before', 'append', 'prepend', 'replace', 'update'];
23
+ type VoidActionName = (typeof voidActionNames)[number];
24
+ type FragmentActionName = (typeof fragmentActionNames)[number];
25
+ type ActionName = VoidActionName | FragmentActionName;
26
+ type VoidAction = {
27
+ action: VoidActionName;
28
+ delay?: number;
29
+ pin?: boolean | 'last';
30
+ targets: string;
31
+ };
32
+ type FragmentAction = {
33
+ action: FragmentActionName;
34
+ delay?: number;
35
+ pin?: boolean | 'last';
36
+ targets: string;
37
+ fragment: DocumentFragment | string;
38
+ };
39
+ type MaterializedVoidAction = Pick<VoidAction, 'action'> & {
40
+ targets: Element[];
41
+ };
42
+ type MaterializedFragmentAction = Pick<FragmentAction, 'action' | 'fragment'> & {
43
+ targets: Element[];
44
+ };
45
+ type Action = VoidAction | FragmentAction;
46
+ type MaterializedAction = MaterializedVoidAction | MaterializedFragmentAction;
47
+ type ActionsOptions = {
48
+ element?: Element;
49
+ schema?: Partial<Schema>;
50
+ debug?: boolean;
51
+ plugins?: Plugin[];
52
+ };
53
+ declare class Actions {
54
+ #private;
55
+ constructor(options?: ActionsOptions);
56
+ ready(): Promise<void>;
57
+ get element(): Element;
58
+ get plugins(): Plugin[];
59
+ observe(): void;
60
+ disconnect(): void;
61
+ clear(): void;
62
+ applyActions(actions: (Action | MaterializedAction)[]): void;
63
+ applyPinnedActions(element: Element): void;
64
+ after(params: Omit<FragmentAction, 'action'> | Omit<MaterializedFragmentAction, 'action'>): void;
65
+ before(params: Omit<FragmentAction, 'action'> | Omit<MaterializedFragmentAction, 'action'>): void;
66
+ append(params: Omit<FragmentAction, 'action'> | Omit<MaterializedFragmentAction, 'action'>): void;
67
+ prepend(params: Omit<FragmentAction, 'action'> | Omit<MaterializedFragmentAction, 'action'>): void;
68
+ replace(params: Omit<FragmentAction, 'action'> | Omit<MaterializedFragmentAction, 'action'>): void;
69
+ update(params: Omit<FragmentAction, 'action'> | Omit<MaterializedFragmentAction, 'action'>): void;
70
+ remove(params: Omit<VoidAction, 'action'> | Omit<MaterializedVoidAction, 'action'>): void;
71
+ focus(params: Omit<VoidAction, 'action'> | Omit<MaterializedVoidAction, 'action'>): void;
72
+ disable(params: Omit<VoidAction, 'action'> | Omit<MaterializedVoidAction, 'action'>): void;
73
+ enable(params: Omit<VoidAction, 'action'> | Omit<MaterializedVoidAction, 'action'>): void;
74
+ hide(params: Omit<VoidAction, 'action'> | Omit<MaterializedVoidAction, 'action'>): void;
75
+ show(params: Omit<VoidAction, 'action'> | Omit<MaterializedVoidAction, 'action'>): void;
76
+ reset(params: Omit<VoidAction, 'action'> | Omit<MaterializedVoidAction, 'action'>): void;
77
+ refresh(params: Omit<VoidAction, 'action'> | Omit<MaterializedVoidAction, 'action'>): void;
78
+ morph(from: Element | Document, to: string | Element | Document | DocumentFragment, options?: {
79
+ childrenOnly?: boolean;
80
+ }): void;
81
+ private scheduleActions;
82
+ private scheduleMaterializedActions;
83
+ private materializeActions;
84
+ private _scheduleActions;
85
+ private _applyActionsInContext;
86
+ private _applyActions;
87
+ private _debugMaterializeActions;
88
+ private _debugApplyActions;
89
+ private pinAction;
90
+ _morph(from: Element | Document, to: string | Element | Document | DocumentFragment, options?: {
91
+ childrenOnly?: boolean;
92
+ }): void;
93
+ private _after;
94
+ private _before;
95
+ private _append;
96
+ private _prepend;
97
+ private _replace;
98
+ private _update;
99
+ private _remove;
100
+ private _focus;
101
+ private _show;
102
+ private _hide;
103
+ private _enable;
104
+ private _disable;
105
+ private _reset;
106
+ private _refresh;
107
+ private handleEvent;
108
+ private classListChanged;
109
+ private attributeChanged;
110
+ }
111
+ declare function isValidActionName(actionName: unknown): actionName is ActionName;
112
+ declare function dispatchActions(actions: (Action | MaterializedAction)[]): void;
113
+ declare function dispatchAction({
114
+ targets,
115
+ ...action
116
+ }: (Pick<VoidAction, 'action'> | Pick<FragmentAction, 'action' | 'fragment'>) & {
117
+ targets?: Element | Element[] | string | null;
118
+ }): void;
119
+ //#endregion
120
+ //#region src/actions.d.ts
121
+ type Targets = Element | Element[] | string | null;
122
+ declare function hide(targets?: Targets): void;
123
+ declare function show(targets?: Targets): void;
124
+ declare function disable(targets?: Targets): void;
125
+ declare function enable(targets?: Targets): void;
126
+ declare function remove(targets?: Targets): void;
127
+ declare function append(targets: Targets, fragment: string | DocumentFragment): void;
128
+ declare function prepend(targets: Targets, fragment: string | DocumentFragment): void;
129
+ declare function after(targets: Targets, fragment: string | DocumentFragment): void;
130
+ declare function before(targets: Targets, fragment: string | DocumentFragment): void;
131
+ declare function update(targets: Targets, fragment: string | DocumentFragment): void;
132
+ declare function replace(targets: Targets, fragment: string | DocumentFragment): void;
133
+ //#endregion
134
+ export { Plugin as S, dispatchAction as _, enable as a, Schema as b, remove as c, update as d, Action as f, MaterializedAction as g, ActionsOptions as h, disable as i, replace as l, Actions as m, append as n, hide as o, ActionName as p, before as r, prepend as s, after as t, show as u, dispatchActions as v, defaultSchema as x, isValidActionName as y };