@tripod311/splash 0.0.16 → 0.1.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.
Files changed (2) hide show
  1. package/README.md +181 -152
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,88 +1,88 @@
1
1
  # Splash
2
2
 
3
- Splash is a zero-dependency minimalistic UI framework that tries to achieve JSX/Vue template level of comfort without a virtual DOM.
3
+ Splash is a small, zero-dependency UI library built around direct DOM rendering. It aims to provide a component-oriented development experience without introducing a virtual DOM or a separate rendering tree.
4
4
 
5
- In Splash every component is an extended DOM node with all the consequences you can add, remove or insert them in regular DOM elements.
6
- That’s why it’s called **Splash** — just splash your component anywhere on the page and it will work according to its inner logic.
5
+ Each Splash component owns a single root DOM element. Components can be mounted into slots or regular DOM containers, moved between containers, and accessed through their underlying DOM nodes when direct control is useful.
7
6
 
8
- Splash does not separate props and state variables like other frameworks, primarily because it focuses on rendering the view only.
9
- That means a clear split between **component view** and **component logic**.
10
- Also it means that all the data passed from a parent component to its children can affect child views immediately you just need to take it into account.
7
+ Splash uses a deliberately compact model: component inputs and local values live in one state object, while templates describe how selected state fields are reflected in the DOM. This approach is designed for small applications and interfaces where a lightweight abstraction and explicit DOM behavior are more useful than a larger framework ecosystem.
8
+
9
+ Splash is also a practical exploration of component lifecycle, selective reactivity, template caching, and direct DOM updates. It is not intended as a drop-in replacement for established production frameworks; it represents a different set of trade-offs tailored to the author's own projects.
11
10
 
12
11
  ---
13
12
 
14
- ## Core Ideas
15
-
16
- 1. **Component = one DOM element**
17
- - No ambiguities, no phantom nodes.
18
- - Conditional rendering is solved inside the component.
19
- - If you need a hybrid case — use `ref` and manage it manually.
20
-
21
- 2. **Direct DOM**
22
- - No virtual tree.
23
- - Everything is stored and updated directly in the DOM.
24
- - The browser already optimizes repaints — no need for batching.
25
-
26
- 3. **Minimal directives**
27
- Just six built-in directives:
28
- - `data-ref` — reference to a specific element.
29
- - `data-slot` — mount point for other components.
30
- - `data-text` — reactive text.
31
- - `data-html` — dynamic HTML.
32
- - `data-class` — reactive classes.
33
- - `data-style` — reactive styles.
34
- Additionally:
35
- - `data-prop-*` — reactive binding to any attribute (src, href, etc.).
36
-
37
- 4. **Lifecycle hooks**
38
- - `mounted()` — called after insertion into the DOM.
39
- - `unmounted()` — called when removed.
40
- - `transitionReady()` — called after mounting + rendering (for CSS animations).
41
-
42
- 5. **Single source of truth: state**
43
- - Each component receives `options: Record<string, any>`.
44
- - These turn into the component’s `state`.
45
- - No props vs. state confusion — only a single object.
46
- - State updates trigger view updates exactly where needed.
47
-
48
- 6. **Errors are not hidden**
49
- - Splash is not a nanny.
50
- - If you create an infinite update loop, that’s your bug.
51
- - Architectural mistakes are visible immediately.
13
+ ## Core ideas
52
14
 
53
- ---
15
+ ### One root element per component
16
+
17
+ Every component owns one root DOM element. This gives the component a clear position in the document and allows it to be mounted, moved, or removed as a single unit.
18
+
19
+ Conditional content is normally handled through slots. When lower-level control is needed, elements can also be accessed through refs or the component's root node.
20
+
21
+ ### Direct DOM rendering
22
+
23
+ Splash stores and updates the rendered view directly in the DOM. It does not maintain a virtual representation or perform reconciliation.
24
+
25
+ Reactive directives update only the elements bound to the state field that changed. Splash does not add its own scheduling layer, so update timing remains explicit and follows normal browser DOM behavior.
26
+
27
+ ### A small directive set
28
+
29
+ Splash provides a limited set of template directives:
30
+
31
+ - `data-ref` — stores a reference to an element.
32
+ - `data-slot` — declares a mount point for child components.
33
+ - `data-text` — binds a state value to `innerText`.
34
+ - `data-html` — binds a state value to `innerHTML`.
35
+ - `data-class` — binds an array of CSS classes.
36
+ - `data-style` — binds an object of inline styles.
37
+ - `data-prop-*` — binds a value to an HTML attribute such as `src` or `href`.
38
+
39
+ Directive values are identifiers, not JavaScript expressions. Application logic remains in the component class.
40
+
41
+ ### Component lifecycle
42
+
43
+ Components can respond to three lifecycle stages:
44
+
45
+ - `mounted()` — called after the component is inserted into the DOM.
46
+ - `transitionReady()` — called after mounting and initial rendering, allowing CSS transitions to begin.
47
+ - `unmounted()` — called when the component is unmounted.
54
48
 
55
- ## Philosophy
49
+ ### One component state object
56
50
 
57
- - **No extra layers.** DOM already does everything, no need to reinvent it.
58
- - **Simplicity over magic.** Minimal directives, maximum predictability.
59
- - **Trust the developer.** If you want to work directly with DOM go ahead.
60
- - **Efficiency is natural.** Browsers already batch repaints, no extra abstraction needed.
51
+ Each component receives an `options: Record<string, any>` object, which is used to initialize its state. Splash does not create separate props and local-state systems.
52
+
53
+ State fields can be updated individually or as a group. Only directives bound to changed fields are updated.
54
+
55
+ ### Explicit behavior
56
+
57
+ Splash keeps its update model intentionally small. State changes, DOM access, and component relationships remain visible in application code. The library provides lifecycle and rendering primitives while leaving broader architecture decisions to the application.
61
58
 
62
59
  ---
63
60
 
64
61
  ## API
65
62
 
66
- ### 1. Component
63
+ ### 1. Components
64
+
65
+ A component combines a template, a state object, lifecycle hooks, and one root DOM element. The root is available through `component.DOMNode` when direct DOM access is required.
67
66
 
68
- Each component is a visible entity. You may think of it as an extension of a DOM node. Each component exposes its root DOM element via the component.DOMNode property, so you can directly manipulate it if needed.
69
- A component must have a **template** — plain HTML with several helper directives.
67
+ A component template is regular HTML with optional Splash directives.
68
+
69
+ **Example template (`myComponent.html`):**
70
70
 
71
- **Example template (myComponent.html):**
72
71
  ```html
73
72
  <div data-ref="container">
74
73
  <h1 data-text="header-text"></h1>
75
74
  <p>Static paragraph</p>
76
75
  <!--slot:conditionalPart-->
77
- <p data-style="conclusion-style">This is finishing paragraph</p>
76
+ <p data-style="conclusion-style">This is the final paragraph.</p>
78
77
  </div>
79
78
  ```
80
79
 
81
- **Component definition (myComponent.ts):**
80
+ **Component definition (`myComponent.ts`):**
81
+
82
82
  ```ts
83
- import { Component } from "@tripod311/splash"
84
- import View from "./myComponent.html?raw"
85
- import ChildComponent from "./childComponent.js"
83
+ import { Component } from "@tripod311/splash";
84
+ import View from "./myComponent.html?raw";
85
+ import ChildComponent from "./childComponent.js";
86
86
 
87
87
  export default class MyComponent extends Component {
88
88
  private static componentName: string = "MyComponent";
@@ -91,22 +91,24 @@ export default class MyComponent extends Component {
91
91
  constructor (options: Record<string, any>) {
92
92
  super(options);
93
93
 
94
- // Initialize state
94
+ // Update one state field.
95
95
  this.state.setProp("header-text", "My component title");
96
96
 
97
- // Subscribe to state changes
97
+ // Observe a state field.
98
98
  this.state.on("src", (newValue: any, oldValue: any) => {
99
- /* inner logic */
99
+ // Component-specific logic.
100
100
  });
101
101
 
102
- // Batch update
102
+ // Update several fields together.
103
103
  this.state.update({
104
104
  diffVar1: "someValue",
105
105
  diffVar2: "someOtherValue"
106
106
  });
107
107
 
108
- // Fill slots
109
- this.slots["conditionalPart"].push(new ChildComponent({ var1: 1 }));
108
+ // Add a child component to a slot.
109
+ this.slots.conditionalPart.push(
110
+ new ChildComponent({ var1: 1 })
111
+ );
110
112
  }
111
113
 
112
114
  mounted () {
@@ -114,7 +116,7 @@ export default class MyComponent extends Component {
114
116
  }
115
117
 
116
118
  transitionReady () {
117
- // Called after mounted + rendered → safe for CSS animations
119
+ // The component is mounted and ready for CSS transitions.
118
120
  }
119
121
 
120
122
  unmounted () {
@@ -123,60 +125,63 @@ export default class MyComponent extends Component {
123
125
  }
124
126
  ```
125
127
 
126
- **Events:**
128
+ Each component class must have a distinct `componentName`. Splash uses this name as the key for its shared template cache.
129
+
130
+ #### Component events
131
+
132
+ Components can emit and subscribe to application-level events:
133
+
127
134
  ```ts
128
- // Child → Parent
135
+ // Child component
129
136
  this.emit("click", { some: "payload" });
130
137
 
131
- // Parent listens
138
+ // Parent component
132
139
  childComponent.on("click", payload => {
133
140
  console.log(payload);
134
141
  });
135
142
  ```
136
143
 
137
- **Updating child from parent:**
144
+ #### Updating a child component
145
+
138
146
  ```ts
139
- childComponent.update({ color: "newColor" });
147
+ childComponent.update({
148
+ color: "newColor"
149
+ });
140
150
  ```
141
151
 
142
- ⚠️ Note: Each component must have a distinct `componentName`.
143
- Splash caches and shares templates between components with the same names.
144
-
145
- ---
146
-
147
152
  ### 2. Slots
148
153
 
149
- Slots manage child components declaratively. They behave like arrays with lifecycle tracking.
154
+ Slots are ordered collections of child components associated with mount points in a template. They manage insertion, removal, and the corresponding component lifecycle calls.
150
155
 
151
- **Example:**
152
156
  ```ts
153
- const child = new ChildComponent({ ...state });
154
- this.slots["mySlot"].setContent([child]);
157
+ const child = new ChildComponent({
158
+ title: "Child component"
159
+ });
160
+
161
+ this.slots.mySlot.setContent([child]);
155
162
  ```
156
163
 
157
- **API:**
158
- - `setContent(Component[])` — replace slot contents.
159
- - `clear(): Component[]` — unmount all and return them.
160
- - `push(c: Component)` — append component.
161
- - `pop(): Component | undefined` — remove last.
162
- - `unshift(c: Component)` — prepend component.
163
- - `shift(): Component | undefined` — remove first.
164
- - `inject(pos: number, c: Component)` — insert at position.
165
- - `remove(pos: number): Component | undefined` — remove at position.
166
- - `getByIndex(index: number): Component | undefined` — access without unmounting.
167
- - `length: number` — number of components in slot.
164
+ Slot API:
168
165
 
169
- ---
166
+ - `setContent(components: Component[])` — replaces the current contents.
167
+ - `clear(): Component[]` — unmounts and returns all components.
168
+ - `push(component: Component)` — appends a component.
169
+ - `pop(): Component | undefined` — removes the last component.
170
+ - `unshift(component: Component)` — prepends a component.
171
+ - `shift(): Component | undefined` — removes the first component.
172
+ - `inject(position: number, component: Component)` — inserts a component at a specified position.
173
+ - `remove(position: number): Component | undefined` — removes a component at a specified position.
174
+ - `getByIndex(index: number): Component | undefined` — returns a component without removing it.
175
+ - `length: number` — returns the number of components in the slot.
170
176
 
171
177
  ### 3. Drops
172
178
 
173
- Drops are lightweight HTML snippets stored in the `TemplateCache`.
174
- They are not components: no state, no lifecycle, no reactivity.
175
- Instead, they allow you to register small reusable pieces of HTML with the same directives (`data-ref`, `data-text`, `data-html`, `data-class`, `data-style`, `data-prop-*`).
179
+ Drops are lightweight reusable HTML fragments stored in `TemplateCache`. They support the same element-binding directives as component templates but do not have component state, events, or lifecycle hooks.
176
180
 
177
- A drop can be **instantiated** at any moment, filled with values, and mounted into the DOM as a regular element.
181
+ A drop can be created, populated with initial values, and inserted into the DOM as a regular node.
182
+
183
+ #### Registering a drop
178
184
 
179
- **Example template registration:**
180
185
  ```ts
181
186
  TemplateCache.registerDrop("chatMessage", `
182
187
  <div class="msg">
@@ -186,122 +191,146 @@ TemplateCache.registerDrop("chatMessage", `
186
191
  `);
187
192
  ```
188
193
 
189
- **Creating a drop**
194
+ #### Creating a drop
195
+
196
+ The second argument is optional and can be used to populate the drop's directives:
197
+
190
198
  ```ts
191
- // second parameter is optional, use it when you want to fill drop with some content.
192
199
  const drop = TemplateCache.createDrop("chatMessage", {
193
200
  author: "Alice",
194
201
  text: "<b>Hello!</b>"
195
202
  });
196
203
 
197
- // Access refs
198
- console.log(drop.refs.author.innerText); // "Alice"
204
+ console.log(drop.refs.author.innerText);
199
205
 
200
- // Insert into DOM
201
206
  document.body.appendChild(drop.node);
202
207
  ```
203
208
 
204
- **Drop interface**
209
+ Drop interface:
210
+
205
211
  ```ts
206
212
  export interface Drop {
207
- node: Node; // the root DOM node
208
- refs: Record<string, HTMLElement>; // all elements with data-ref
213
+ node: Node;
214
+ refs: Record<string, HTMLElement>;
209
215
  }
210
216
  ```
211
217
 
212
- ### 4. Generic Components
218
+ Values passed to `data-html` are assigned through `innerHTML`. Do not use unsanitized user-provided content with this directive.
219
+
220
+ ### 4. Generic components
213
221
 
214
- Generic components are a middle ground between **drops** and **regular components**.
215
- They are useful when you need something that behaves like a component (can be mounted into a slot, updated, unmounted), but is too simple to justify creating a separate `.ts` + `.html` pair.
222
+ Generic components provide component lifecycle and slot compatibility without requiring a dedicated component class and template file.
216
223
 
217
- A common use case is when you want to render either a real component or a simple piece of UI (like an error message) into the same slot. A drop alone cannot be mounted into a slot, but a generic component can wrap it.
224
+ They are useful when a slot may contain either a regular component or a small piece of interface such as an empty-state or error message.
218
225
 
219
- **Example:**
220
226
  ```ts
221
227
  const result = await someAsyncRequest();
222
228
 
223
229
  if (!result.error) {
224
- this.slots.content.push(new MyRegularComponent({}));
230
+ this.slots.content.push(
231
+ new MyRegularComponent({})
232
+ );
225
233
  } else {
226
- // Wrap a drop (or any HTML node) into a generic component
227
234
  const errorDrop = TemplateCache.createDrop("errorMessage");
228
- this.slots.content.push(Component.generic({ text: result.details }, errorDrop.node));
235
+
236
+ this.slots.content.push(
237
+ Component.generic(
238
+ { text: result.details },
239
+ errorDrop.node
240
+ )
241
+ );
229
242
  }
230
243
  ```
231
244
 
232
- In this way, you can take drops or any ad-hoc HTML node created in code and treat it as a real component.
233
- This keeps your slot API consistent — you always mount components, whether they are full, generic, or lightweight wrappers around static HTML.
245
+ This keeps the slot API consistent: every slot contains components, while simple DOM fragments can be wrapped only when lifecycle-aware mounting is needed.
234
246
 
235
- ---
247
+ ### 5. Mounting and unmounting
236
248
 
237
- ### 4. Mounting & Unmounting
238
-
239
- To place a component on the page you can use the `mount` method:
249
+ Use `mount()` to append a component to a DOM container:
240
250
 
241
251
  ```ts
242
252
  import MyComponent from "./MyComponent.js";
243
253
 
244
- const app = new MyComponent({ title: "Hello Splash!" });
254
+ const app = new MyComponent({
255
+ title: "Hello Splash!"
256
+ });
257
+
245
258
  app.mount(document.body);
246
259
  ```
247
260
 
248
- This will append the components DOM node into the provided container and call its `mounted()` lifecycle hook.
261
+ Mounting inserts the component's root node and calls its `mounted()` lifecycle hook.
249
262
 
250
- To remove a component from the page, call:
263
+ Use `unmount()` to remove it:
251
264
 
252
265
  ```ts
253
266
  app.unmount();
254
267
  ```
255
268
 
256
- This will remove the DOM node and call its `unmounted()` lifecycle hook.
257
-
258
- ⚠️ **Important:**
259
- Unmounting a component does **not** destroy its state or DOM nodes.
260
- The component can be mounted again into another container without losing any data:
269
+ Unmounting does not destroy the component's state or DOM tree. The same instance can later be mounted into another container:
261
270
 
262
271
  ```ts
263
- // move component from one place to another
264
272
  app.unmount();
265
- app.mount(document.getElementById("new-container")!);
273
+ app.mount(
274
+ document.getElementById("new-container")!
275
+ );
266
276
  ```
267
277
 
268
- This behavior allows implementing modals, window managers, tab systems and other features where components may be parked and reinserted freely.
278
+ This behavior is useful for interfaces such as modal systems, tabs, movable panels, and window managers.
279
+
280
+ When lifecycle hooks matter, prefer `mount()`, `unmount()`, and slots over manipulating `component.DOMNode` directly.
269
281
 
270
282
  ---
271
283
 
272
- ## Template Directives
284
+ ## Template directives
285
+
286
+ Directive values are string identifiers. Splash binds them to state fields but does not evaluate them as expressions.
287
+
288
+ - `data-ref` — exposes an element through `this.refs`.
289
+ - `data-text` — assigns a value through `innerText`.
290
+ - `data-html` — assigns a value through `innerHTML`.
291
+ - `data-class` — applies an array of CSS class names.
292
+ - `data-style` — applies an object in the form `{ [property]: value }`.
293
+ - `data-prop-*` — binds another HTML attribute, such as `data-prop-src` or `data-prop-href`.
273
294
 
274
- All directives work with **string-based identifiers**. They are not evaluated only bound.
275
- Initial HTML values are used to initialize state.
295
+ Initial directive values declared in the template are used to initialize the corresponding state fields.
276
296
 
277
- - `data-ref` reference in `this.refs`.
278
- - `data-text` — reactive `innerText`.
279
- - `data-html` — reactive `innerHTML`.
280
- - `data-class` — array of strings for CSS classes.
281
- - `data-style` — object `{ [prop]: value }` for styles.
282
- - `data-prop-*` — any other attribute (e.g. `data-prop-src` for `<img>`).
297
+ The current value of a reactive field can be read with:
283
298
 
284
- Current reactive values can be accessed via:
285
299
  ```ts
286
- this.state.getProp("reactive-var-name")
300
+ this.state.getProp("reactive-variable-name");
287
301
  ```
288
302
 
289
303
  ---
290
304
 
291
- ## How it Differs from React/Vue
305
+ ## Design choices
292
306
 
293
- - No virtual DOM simpler and faster.
294
- - No props/state/context zoo → just `state`.
295
- - No hidden batching or reconciliation updates are explicit.
296
- - No “child restrictions” developers freely manipulate the DOM.
297
- - Tiny core, only a few kilobytes.
307
+ Splash differs from virtual-DOM and compiler-based UI frameworks in several deliberate ways:
308
+
309
+ - The DOM is the only rendered tree.
310
+ - Each component corresponds to one root DOM element.
311
+ - Reactivity is opt-in through template directives.
312
+ - Component inputs and local values share one state interface.
313
+ - DOM access remains available when an application needs it.
314
+ - Update scheduling and broader application architecture remain under developer control.
315
+
316
+ These choices reduce the amount of machinery between component code and the browser, while placing more responsibility on the application to manage update patterns and DOM interactions carefully.
298
317
 
299
318
  ---
300
319
 
301
- ## Example Scenarios
320
+ ## Example scenarios
302
321
 
303
- - **Form:** use `data-ref` for `<input>` and plain native events. No `v-model` or controlled/uncontrolled hacks.
304
- - **Mount animations:** use `transitionReady()` for CSS transitions.
305
- - **Conditional rendering:** mount/unmount via `slots`, no phantom nodes.
322
+ ### Forms
306
323
 
307
- ---
324
+ Use `data-ref` to access native form controls and attach regular DOM event listeners. Splash does not introduce a separate form model.
325
+
326
+ ### Mount transitions
327
+
328
+ Use `transitionReady()` to apply classes or styles after the component has been mounted and initially rendered.
329
+
330
+ ### Conditional content
331
+
332
+ Use slots to add, replace, or remove child components while preserving their lifecycle behavior.
333
+
334
+ ### Direct DOM integration
335
+
336
+ Use refs or `component.DOMNode` when integrating browser APIs or third-party code that expects regular DOM elements.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tripod311/splash",
3
- "version": "0.0.16",
3
+ "version": "0.1.0",
4
4
  "description": "simple and flexible ui framework",
5
5
  "keywords": [
6
6
  "js",