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 +21 -0
- package/README.md +168 -0
- package/dist/actions-CQVSvZlC.d.mts +134 -0
- package/dist/actions-k0j8z15o.mjs +883 -0
- package/dist/actions.d.mts +2 -0
- package/dist/actions.mjs +2 -0
- package/dist/react.d.mts +88 -0
- package/dist/react.mjs +635 -0
- package/dist/turbo-stream.d.mts +7 -0
- package/dist/turbo-stream.mjs +72 -0
- package/dist/utils-C2S0wWJJ.mjs +350 -0
- package/dist/utils.d.mts +80 -0
- package/dist/utils.mjs +2 -0
- package/package.json +72 -0
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 };
|