Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [v1.0.4] - 2026-03-15

### Changed
- Update `toString()` on signals to avoid need for hydration step

## [v1.0.3] - 2026-03-14

### Added
Expand Down
25 changes: 20 additions & 5 deletions attr.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { escapeHTML } from '@aegisjsproject/escape/html.js';
import { DisposableComputed, DisposableState } from './disposable.js';

export const SIGNAL_DATA_ATTR = 'data-attr-signal';
export const SIGNAL_DATA_ATTR_SELECTOR = `[${SIGNAL_DATA_ATTR}]`;
import { SIGNAL_DATA_ATTR } from './consts.js';

export class AttrState extends DisposableState {
#name;
Expand All @@ -25,7 +24,15 @@ export class AttrState extends DisposableState {
}

toString() {
return `${SIGNAL_DATA_ATTR}="${this.ref}"`;
const val = this.get();

if (Array.isArray(val)) {
return `${SIGNAL_DATA_ATTR}="${this.ref}" ${this.#name}="${escapeHTML(val.join(' '))}"`;
} else if (val === false) {
return `${SIGNAL_DATA_ATTR}="${this.ref}"`;
} else {
return `${SIGNAL_DATA_ATTR}="${this.ref}" ${this.#name}="${escapeHTML(val)}"`;
}
}
}

Expand All @@ -50,7 +57,15 @@ export class AttrComputed extends DisposableComputed {
}

toString() {
return `${SIGNAL_DATA_ATTR}="${this.ref}"`;
const val = this.get();

if (Array.isArray(val)) {
return `${SIGNAL_DATA_ATTR}="${this.ref}" ${this.#name}="${escapeHTML(val.join(' '))}"`;
} else if (val === false) {
return `${SIGNAL_DATA_ATTR}="${this.ref}"`;
} else {
return `${SIGNAL_DATA_ATTR}="${this.ref}" ${this.#name}="${escapeHTML(val)}"`;
}
}
}

Expand Down
4 changes: 4 additions & 0 deletions consts.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const NOOP = () => undefined;
export const ZERO_WIDTH_SPACE = '\u200B';
export const SIGNAL_DATA_ATTR = 'data-attr-signal';
export const SIGNAL_DATA_ATTR_SELECTOR = `[${SIGNAL_DATA_ATTR}]`;
2 changes: 1 addition & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ document.adoptedStyleSheets = [properties, theme, misc, forms, btn];
const stack = new DisposableStack();
const controller = stack.adopt(new AbortController(), controller => controller.abort());
const signal = registerSignal(controller.signal);
const $name = stack.use($text('Silly person'));
const $name = stack.use($text('Silly person <script>alert("1")</script>'));
const $nameAttr = stack.use($data('user-name', () => $name.get()));
const $isHidden = stack.use($hidden(false));
const $desc = stack.use($aria('description', () => `Description for ${$name.get()}.`));
Expand Down
53 changes: 53 additions & 0 deletions list.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { DisposableState, DisposableComputed } from './disposable.js';

const ZERO_WIDTH_SPACE = '\u200B';

export class ListComputed extends DisposableComputed {
static REF_PREFIX = '__list_signal';

constructor(callback, config) {
super(() => {
const value = callback();

if (Array.isArray(value)) {
return value;
} else if (typeof value[Symbol.iterator] === 'function') {
return Array.from(value);
} else {
return [value];
}
}, config);
}

map(cb) {
return new ListComputed(cb);
}

toString() {
return `${ZERO_WIDTH_SPACE}<!--${this.ref}-->`;
}
}

export class ListState extends DisposableState {
constructor(value, config) {
if (Array.isArray(value)) {
super(value, config);
} else if (typeof value[Symbol.iterator] === 'function') {
super(Array.from(value), config);
} else {
super([value], config);
}
}

map(cb) {
return new ListComputed(cb);
}

toString() {
return `${ZERO_WIDTH_SPACE}<!--${this.ref}-->`;
}
}

export const $list = (val, config) => typeof val === 'function'
? new ListComputed(val, config)
: new ListState(val, config);
43 changes: 34 additions & 9 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@aegisjsproject/iota",
"version": "1.0.3",
"version": "1.0.4",
"description": "A Signals-based reactivity library",
"keywords": [
"signals",
Expand Down Expand Up @@ -76,6 +76,7 @@
},
"homepage": "https://github.com/AegisJSProject/iota#readme",
"dependencies": {
"@aegisjsproject/escape": "^1.0.4",
"@shgysk8zer0/signals": "^0.0.3"
},
"devDependencies": {
Expand All @@ -84,7 +85,7 @@
"@rollup/plugin-terser": "^1.0.0",
"@shgysk8zer0/eslint-config": "^1.0.7",
"@shgysk8zer0/http-server": "^1.1.1",
"@shgysk8zer0/importmap": "^1.8.2",
"@shgysk8zer0/importmap": "^1.8.3",
"eslint": "^10.0.3",
"rollup": "^4.59.0"
}
Expand Down
Loading
Loading