mirror of
				https://github.com/jaandrle/deka-dom-el
				synced 2025-10-31 05:49:15 +01:00 
			
		
		
		
	Compare commits
	
		
			1 Commits
		
	
	
		
			3301a6600c
			...
			v0.9.3-alp
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 5076771410 | 
							
								
								
									
										18
									
								
								.github/workflows/npm-publish.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								.github/workflows/npm-publish.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | |||||||
|  | name: Publish Package to npmjs | ||||||
|  | on: | ||||||
|  |   workflow_dispatch: | ||||||
|  |   release: | ||||||
|  |     types: [created] | ||||||
|  | jobs: | ||||||
|  |   build: | ||||||
|  |     runs-on: ubuntu-latest | ||||||
|  |     steps: | ||||||
|  |       - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | ||||||
|  |       - uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0 | ||||||
|  |         with: | ||||||
|  |           node-version: '20.16' | ||||||
|  |           registry-url: 'https://registry.npmjs.org' | ||||||
|  |       - run: npm ci | ||||||
|  |       - run: npm publish | ||||||
|  |         env: | ||||||
|  |           NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} | ||||||
							
								
								
									
										3
									
								
								.npmrc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								.npmrc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | |||||||
|  | //registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN} | ||||||
|  | registry=https://registry.npmjs.org/ | ||||||
|  | always-auth=true | ||||||
							
								
								
									
										134
									
								
								CODE_OF_CONDUCT.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										134
									
								
								CODE_OF_CONDUCT.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,134 @@ | |||||||
|  |  | ||||||
|  | # Contributor Covenant Code of Conduct | ||||||
|  |  | ||||||
|  | ## Our Pledge | ||||||
|  |  | ||||||
|  | We as members, contributors, and leaders pledge to make participation in our | ||||||
|  | community a harassment-free experience for everyone, regardless of age, body | ||||||
|  | size, visible or invisible disability, ethnicity, sex characteristics, gender | ||||||
|  | identity and expression, level of experience, education, socio-economic status, | ||||||
|  | nationality, personal appearance, race, caste, color, religion, or sexual | ||||||
|  | identity and orientation. | ||||||
|  |  | ||||||
|  | We pledge to act and interact in ways that contribute to an open, welcoming, | ||||||
|  | diverse, inclusive, and healthy community. | ||||||
|  |  | ||||||
|  | ## Our Standards | ||||||
|  |  | ||||||
|  | Examples of behavior that contributes to a positive environment for our | ||||||
|  | community include: | ||||||
|  |  | ||||||
|  | * Demonstrating empathy and kindness toward other people | ||||||
|  | * Being respectful of differing opinions, viewpoints, and experiences | ||||||
|  | * Giving and gracefully accepting constructive feedback | ||||||
|  | * Accepting responsibility and apologizing to those affected by our mistakes, | ||||||
|  | 	and learning from the experience | ||||||
|  | * Focusing on what is best not just for us as individuals, but for the overall | ||||||
|  | 	community | ||||||
|  |  | ||||||
|  | Examples of unacceptable behavior include: | ||||||
|  |  | ||||||
|  | * The use of sexualized language or imagery, and sexual attention or advances of | ||||||
|  | 	any kind | ||||||
|  | * Trolling, insulting or derogatory comments, and personal or political attacks | ||||||
|  | * Public or private harassment | ||||||
|  | * Publishing others' private information, such as a physical or email address, | ||||||
|  | 	without their explicit permission | ||||||
|  | * Other conduct which could reasonably be considered inappropriate in a | ||||||
|  | 	professional setting | ||||||
|  |  | ||||||
|  | ## Enforcement Responsibilities | ||||||
|  |  | ||||||
|  | Community leaders are responsible for clarifying and enforcing our standards of | ||||||
|  | acceptable behavior and will take appropriate and fair corrective action in | ||||||
|  | response to any behavior that they deem inappropriate, threatening, offensive, | ||||||
|  | or harmful. | ||||||
|  |  | ||||||
|  | Community leaders have the right and responsibility to remove, edit, or reject | ||||||
|  | comments, commits, code, wiki edits, issues, and other contributions that are | ||||||
|  | not aligned to this Code of Conduct, and will communicate reasons for moderation | ||||||
|  | decisions when appropriate. | ||||||
|  |  | ||||||
|  | ## Scope | ||||||
|  |  | ||||||
|  | This Code of Conduct applies within all community spaces, and also applies when | ||||||
|  | an individual is officially representing the community in public spaces. | ||||||
|  | Examples of representing our community include using an official e-mail address, | ||||||
|  | posting via an official social media account, or acting as an appointed | ||||||
|  | representative at an online or offline event. | ||||||
|  |  | ||||||
|  | ## Enforcement | ||||||
|  |  | ||||||
|  | Instances of abusive, harassing, or otherwise unacceptable behavior may be | ||||||
|  | reported to the community leaders responsible for enforcement at | ||||||
|  | andrle.jan@centrum.cz. | ||||||
|  | All complaints will be reviewed and investigated promptly and fairly. | ||||||
|  |  | ||||||
|  | All community leaders are obligated to respect the privacy and security of the | ||||||
|  | reporter of any incident. | ||||||
|  |  | ||||||
|  | ## Enforcement Guidelines | ||||||
|  |  | ||||||
|  | Community leaders will follow these Community Impact Guidelines in determining | ||||||
|  | the consequences for any action they deem in violation of this Code of Conduct: | ||||||
|  |  | ||||||
|  | ### 1. Correction | ||||||
|  |  | ||||||
|  | **Community Impact**: Use of inappropriate language or other behavior deemed | ||||||
|  | unprofessional or unwelcome in the community. | ||||||
|  |  | ||||||
|  | **Consequence**: A private, written warning from community leaders, providing | ||||||
|  | clarity around the nature of the violation and an explanation of why the | ||||||
|  | behavior was inappropriate. A public apology may be requested. | ||||||
|  |  | ||||||
|  | ### 2. Warning | ||||||
|  |  | ||||||
|  | **Community Impact**: A violation through a single incident or series of | ||||||
|  | actions. | ||||||
|  |  | ||||||
|  | **Consequence**: A warning with consequences for continued behavior. No | ||||||
|  | interaction with the people involved, including unsolicited interaction with | ||||||
|  | those enforcing the Code of Conduct, for a specified period of time. This | ||||||
|  | includes avoiding interactions in community spaces as well as external channels | ||||||
|  | like social media. Violating these terms may lead to a temporary or permanent | ||||||
|  | ban. | ||||||
|  |  | ||||||
|  | ### 3. Temporary Ban | ||||||
|  |  | ||||||
|  | **Community Impact**: A serious violation of community standards, including | ||||||
|  | sustained inappropriate behavior. | ||||||
|  |  | ||||||
|  | **Consequence**: A temporary ban from any sort of interaction or public | ||||||
|  | communication with the community for a specified period of time. No public or | ||||||
|  | private interaction with the people involved, including unsolicited interaction | ||||||
|  | with those enforcing the Code of Conduct, is allowed during this period. | ||||||
|  | Violating these terms may lead to a permanent ban. | ||||||
|  |  | ||||||
|  | ### 4. Permanent Ban | ||||||
|  |  | ||||||
|  | **Community Impact**: Demonstrating a pattern of violation of community | ||||||
|  | standards, including sustained inappropriate behavior, harassment of an | ||||||
|  | individual, or aggression toward or disparagement of classes of individuals. | ||||||
|  |  | ||||||
|  | **Consequence**: A permanent ban from any sort of public interaction within the | ||||||
|  | community. | ||||||
|  |  | ||||||
|  | ## Attribution | ||||||
|  |  | ||||||
|  | This Code of Conduct is adapted from the [Contributor Covenant][homepage], | ||||||
|  | version 2.1, available at | ||||||
|  | [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. | ||||||
|  |  | ||||||
|  | Community Impact Guidelines were inspired by | ||||||
|  | [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. | ||||||
|  |  | ||||||
|  | For answers to common questions about this code of conduct, see the FAQ at | ||||||
|  | [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at | ||||||
|  | [https://www.contributor-covenant.org/translations][translations]. | ||||||
|  |  | ||||||
|  | [homepage]: https://www.contributor-covenant.org | ||||||
|  | [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html | ||||||
|  | [Mozilla CoC]: https://github.com/mozilla/diversity | ||||||
|  | [FAQ]: https://www.contributor-covenant.org/faq | ||||||
|  | [translations]: https://www.contributor-covenant.org/translations | ||||||
|  |  | ||||||
							
								
								
									
										50
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										50
									
								
								README.md
									
									
									
									
									
								
							| @@ -1,4 +1,4 @@ | |||||||
| **WIP** (the experimentation phase) | **Alpha** | ||||||
| | [source code on GitHub](https://github.com/jaandrle/deka-dom-el) | | [source code on GitHub](https://github.com/jaandrle/deka-dom-el) | ||||||
| | [*mirrored* on Gitea](https://gitea.jaandrle.cz/jaandrle/deka-dom-el) | | [*mirrored* on Gitea](https://gitea.jaandrle.cz/jaandrle/deka-dom-el) | ||||||
| | [](https://robinpokorny.github.io/git3moji/) <!-- editorconfig-checker-disable-line --> | | [](https://robinpokorny.github.io/git3moji/) <!-- editorconfig-checker-disable-line --> | ||||||
| @@ -40,7 +40,7 @@ function EmojiCounter({ initial }) { | |||||||
| 			on("click", () => count.set(count.get() + 1)) | 			on("click", () => count.set(count.get() + 1)) | ||||||
| 		), | 		), | ||||||
|  |  | ||||||
| 		el("select", null, on.host(el=> el.value= initial), | 		el("select", null, on.defer(el=> el.value= initial), | ||||||
| 			on("change", e => emoji.set(e.target.value)) | 			on("change", e => emoji.set(e.target.value)) | ||||||
| 		).append( | 		).append( | ||||||
| 			el(Option, "🎉"), | 			el(Option, "🎉"), | ||||||
| @@ -61,40 +61,30 @@ Creating reactive elements, components, and Web Components using the native | |||||||
| ## Features at a Glance | ## Features at a Glance | ||||||
|  |  | ||||||
| - ✅ **No build step required** — use directly in browsers or Node.js | - ✅ **No build step required** — use directly in browsers or Node.js | ||||||
| - ☑️ **Lightweight** — ~10-15kB minified (original goal 10kB) with zero/minimal dependencies | - ☑️ **Lightweight** — ~10-15kB minified (original goal 10kB) with **zero**/minimal dependencies | ||||||
| - ✅ **Declarative & functional approach** for clean, maintainable code | - ✅ **Declarative & functional approach** for clean, maintainable code | ||||||
| - ✅ **Signals and events** for reactive UI | - ✅ **Signals and events** for reactive UI | ||||||
| - ✅ **Memoization for performance** — optimize rendering with intelligent caching | - ✅ **Memoization for performance** — optimize rendering with intelligent caching | ||||||
| - ✅ **Optional build-in signals** with support for custom reactive implementations | - ✅ **Optional build-in signals** with support for custom reactive implementations (#39) | ||||||
| - ✅ **Server-side rendering** support via [jsdom](https://github.com/jsdom/jsdom) | - ✅ **Server-side rendering** support via [jsdom](https://github.com/jsdom/jsdom) | ||||||
| - ✅ **TypeScript support** (work in progress) | - ✅ **TypeScript support** | ||||||
| - ☑️ **Support for debugging with browser DevTools** without extensions | - ☑️ **Support for debugging with browser DevTools** without extensions | ||||||
| - ☑️ **Enhanced Web Components** support (work in progress) | - ☑️ **Enhanced Web Components** support | ||||||
|  |  | ||||||
| ## Why Another Library? |  | ||||||
|  |  | ||||||
| This library bridges the gap between minimal solutions like van/hyperscript and more comprehensive frameworks like |  | ||||||
| [solid-js](https://github.com/solidjs/solid), offering a balanced trade-off between size, complexity, and usability. |  | ||||||
|  |  | ||||||
| Following functional programming principles, dd\<el\> starts with pure JavaScript (DOM API) and gradually adds |  | ||||||
| auxiliary functions. These range from minor improvements to advanced features for building complete declarative |  | ||||||
| reactive UI templates. |  | ||||||
|  |  | ||||||
| A key advantage: any internal function (`assign`, `classListDeclarative`, `on`, `dispatchEvent`, `S`, etc.) can be used |  | ||||||
| independently while also working seamlessly together. This modular approach makes it easier to integrate the library |  | ||||||
| into existing projects. |  | ||||||
|  |  | ||||||
| ## Getting Started | ## Getting Started | ||||||
|  |  | ||||||
|  | ### Documentation | ||||||
|  |  | ||||||
|  | - [**Documentation and Guide**](https://jaandrle.github.io/deka-dom-el) | ||||||
|  | - [**Examples**](https://jaandrle.github.io/deka-dom-el/p15-examples.html) | ||||||
|  |  | ||||||
| ### Installation | ### Installation | ||||||
|  |  | ||||||
| #### npm |  | ||||||
| ```bash | ```bash | ||||||
| # TBD | npm install deka-dom-el --save | ||||||
| # npm install deka-dom-el |  | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| #### CDN / Direct Script | …or via CDN / Direct Script: | ||||||
|  |  | ||||||
| For CDN links and various build formats (ESM/IIFE, with/without signals, minified/unminified), see the [interactive | For CDN links and various build formats (ESM/IIFE, with/without signals, minified/unminified), see the [interactive | ||||||
| format selector](https://jaandrle.github.io/deka-dom-el/) on the documentation site. | format selector](https://jaandrle.github.io/deka-dom-el/) on the documentation site. | ||||||
| @@ -114,10 +104,18 @@ format selector](https://jaandrle.github.io/deka-dom-el/) on the documentation s | |||||||
| </script> | </script> | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| ### Documentation | ## Why Another Library? | ||||||
|  |  | ||||||
| - [**Interactive Guide**](https://jaandrle.github.io/deka-dom-el) | This library bridges the gap between minimal solutions like van/hyperscript and more comprehensive frameworks like | ||||||
| - [**Examples**](https://jaandrle.github.io/deka-dom-el/p15-examples.html) | [solid-js](https://github.com/solidjs/solid), offering a balanced trade-off between size, complexity, and usability. | ||||||
|  |  | ||||||
|  | Following functional programming principles, dd\<el\> starts with pure JavaScript (DOM API) and gradually adds | ||||||
|  | auxiliary functions. These range from minor improvements to advanced features for building complete declarative | ||||||
|  | reactive UI templates. | ||||||
|  |  | ||||||
|  | A key advantage: any internal function (`assign`, `classListDeclarative`, `on`, `dispatchEvent`, `S`, etc.) can be used | ||||||
|  | independently while also working seamlessly together. This modular approach makes it easier to integrate the library | ||||||
|  | into existing projects. | ||||||
|  |  | ||||||
| ## Understanding Signals | ## Understanding Signals | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										43
									
								
								dist/esm-with-signals.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										43
									
								
								dist/esm-with-signals.js
									
									
									
									
										vendored
									
									
								
							| @@ -41,19 +41,6 @@ function observedAttributes(instance, observedAttribute2) { | |||||||
| function kebabToCamel(name) { | function kebabToCamel(name) { | ||||||
| 	return name.replace(/-./g, (x) => x[1].toUpperCase()); | 	return name.replace(/-./g, (x) => x[1].toUpperCase()); | ||||||
| } | } | ||||||
| var Defined = class extends Error { |  | ||||||
| 	constructor() { |  | ||||||
| 		super(); |  | ||||||
| 		const [curr, ...rest] = this.stack.split("\n"); |  | ||||||
| 		const curr_file = curr.slice(curr.indexOf("@"), curr.indexOf(".js:") + 4); |  | ||||||
| 		const curr_lib = curr_file.includes("src/helpers.js") ? "src/" : curr_file; |  | ||||||
| 		this.stack = rest.find((l) => !l.includes(curr_lib)) || curr; |  | ||||||
| 	} |  | ||||||
| 	get compact() { |  | ||||||
| 		const { stack } = this; |  | ||||||
| 		return stack.slice(0, stack.indexOf("@") + 1) + "\u2026" + stack.slice(stack.lastIndexOf("/")); |  | ||||||
| 	} |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| // src/dom-lib/common.js | // src/dom-lib/common.js | ||||||
| var enviroment = { | var enviroment = { | ||||||
| @@ -303,7 +290,7 @@ var store_abort = /* @__PURE__ */ new WeakMap(); | |||||||
| var scope = { | var scope = { | ||||||
| 	/** | 	/** | ||||||
| 	* Gets the current scope | 	* Gets the current scope | ||||||
| 	* @returns {Object} Current scope context | 	* @returns {typeof scopes[number]} Current scope context | ||||||
| 	*/ | 	*/ | ||||||
| 	get current() { | 	get current() { | ||||||
| 		return scopes[scopes.length - 1]; | 		return scopes[scopes.length - 1]; | ||||||
| @@ -663,9 +650,9 @@ function memo(key, generator) { | |||||||
| memo.isScope = function(obj) { | memo.isScope = function(obj) { | ||||||
| 	return obj[memoMark]; | 	return obj[memoMark]; | ||||||
| }; | }; | ||||||
| memo.scope = function memoScope(fun, { signal: signal2, onlyLast } = {}) { | memo.scope = function memoScopeCreate(fun, { signal: signal2, onlyLast } = {}) { | ||||||
| 	let cache = oCreate(); | 	let cache = oCreate(); | ||||||
| 	function memoScope2(...args) { | 	function memoScope(...args) { | ||||||
| 		if (signal2 && signal2.aborted) | 		if (signal2 && signal2.aborted) | ||||||
| 			return fun.apply(this, args); | 			return fun.apply(this, args); | ||||||
| 		let cache_local = onlyLast ? cache : oCreate(); | 		let cache_local = onlyLast ? cache : oCreate(); | ||||||
| @@ -680,10 +667,10 @@ memo.scope = function memoScope(fun, { signal: signal2, onlyLast } = {}) { | |||||||
| 		cache = cache_local; | 		cache = cache_local; | ||||||
| 		return out; | 		return out; | ||||||
| 	} | 	} | ||||||
| 	memoScope2[memoMark] = true; | 	memoScope[memoMark] = true; | ||||||
| 	memoScope2.clear = () => cache = oCreate(); | 	memoScope.clear = () => cache = oCreate(); | ||||||
| 	if (signal2) signal2.addEventListener("abort", memoScope2.clear); | 	if (signal2) signal2.addEventListener("abort", memoScope.clear); | ||||||
| 	return memoScope2; | 	return memoScope; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| // src/signals-lib/helpers.js | // src/signals-lib/helpers.js | ||||||
| @@ -800,17 +787,17 @@ signal.clear = function(...signals2) { | |||||||
| }; | }; | ||||||
| var key_reactive = "__dde_reactive"; | var key_reactive = "__dde_reactive"; | ||||||
| signal.el = function(s, map) { | signal.el = function(s, map) { | ||||||
| 	map = memo.isScope(map) ? map : memo.scope(map, { onlyLast: true }); | 	const mapScoped = memo.isScope(map) ? map : memo.scope(map, { onlyLast: true }); | ||||||
| 	const mark_start = createElement.mark({ type: "reactive", source: new Defined().compact }, true); | 	const { current } = scope, { scope: sc } = current; | ||||||
|  | 	const mark_start = createElement.mark({ type: "reactive", component: sc && sc.name || "" }, true); | ||||||
| 	const mark_end = mark_start.end; | 	const mark_end = mark_start.end; | ||||||
| 	const out = enviroment.D.createDocumentFragment(); | 	const out = enviroment.D.createDocumentFragment(); | ||||||
| 	out.append(mark_start, mark_end); | 	out.append(mark_start, mark_end); | ||||||
| 	const { current } = scope; |  | ||||||
| 	const reRenderReactiveElement = (v) => { | 	const reRenderReactiveElement = (v) => { | ||||||
| 		if (!mark_start.parentNode || !mark_end.parentNode) | 		if (!mark_start.parentNode || !mark_end.parentNode) | ||||||
| 			return removeSignalListener(s, reRenderReactiveElement); | 			return removeSignalListener(s, reRenderReactiveElement); | ||||||
| 		scope.push(current); | 		scope.push(current); | ||||||
| 		let els = map(v); | 		let els = mapScoped(v); | ||||||
| 		scope.pop(); | 		scope.pop(); | ||||||
| 		if (!Array.isArray(els)) | 		if (!Array.isArray(els)) | ||||||
| 			els = [els]; | 			els = [els]; | ||||||
| @@ -830,7 +817,7 @@ signal.el = function(s, map) { | |||||||
| 	current.host(on.disconnected( | 	current.host(on.disconnected( | ||||||
| 		() => ( | 		() => ( | ||||||
| 			/*! Clears cached elements for reactive element `S.el` */ | 			/*! Clears cached elements for reactive element `S.el` */ | ||||||
| 			map.clear() | 			mapScoped.clear() | ||||||
| 		) | 		) | ||||||
| 	)); | 	)); | ||||||
| 	return out; | 	return out; | ||||||
| @@ -902,9 +889,8 @@ var signals_config = { | |||||||
| function removeSignalsFromElements(s, listener, ...notes) { | function removeSignalsFromElements(s, listener, ...notes) { | ||||||
| 	const { current } = scope; | 	const { current } = scope; | ||||||
| 	current.host(function(element) { | 	current.host(function(element) { | ||||||
| 		if (element[key_reactive]) | 		if (!element[key_reactive]) element[key_reactive] = []; | ||||||
| 			return element[key_reactive].push([[s, listener], ...notes]); | 		element[key_reactive].push([[s, listener], ...notes]); | ||||||
| 		element[key_reactive] = []; |  | ||||||
| 		if (current.prevent) return; | 		if (current.prevent) return; | ||||||
| 		on.disconnected( | 		on.disconnected( | ||||||
| 			() => ( | 			() => ( | ||||||
| @@ -949,7 +935,6 @@ function toSignal(s, value, actions, readonly = false) { | |||||||
| 			onclear, | 			onclear, | ||||||
| 			host, | 			host, | ||||||
| 			listeners: /* @__PURE__ */ new Set(), | 			listeners: /* @__PURE__ */ new Set(), | ||||||
| 			defined: new Defined().stack, |  | ||||||
| 			readonly | 			readonly | ||||||
| 		}), | 		}), | ||||||
| 		enumerable: false, | 		enumerable: false, | ||||||
|   | |||||||
							
								
								
									
										7
									
								
								dist/esm-with-signals.min.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								dist/esm-with-signals.min.js
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										14
									
								
								dist/esm.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										14
									
								
								dist/esm.js
									
									
									
									
										vendored
									
									
								
							| @@ -274,7 +274,7 @@ var store_abort = /* @__PURE__ */ new WeakMap(); | |||||||
| var scope = { | var scope = { | ||||||
| 	/** | 	/** | ||||||
| 	* Gets the current scope | 	* Gets the current scope | ||||||
| 	* @returns {Object} Current scope context | 	* @returns {typeof scopes[number]} Current scope context | ||||||
| 	*/ | 	*/ | ||||||
| 	get current() { | 	get current() { | ||||||
| 		return scopes[scopes.length - 1]; | 		return scopes[scopes.length - 1]; | ||||||
| @@ -634,9 +634,9 @@ function memo(key, generator) { | |||||||
| memo.isScope = function(obj) { | memo.isScope = function(obj) { | ||||||
| 	return obj[memoMark]; | 	return obj[memoMark]; | ||||||
| }; | }; | ||||||
| memo.scope = function memoScope(fun, { signal, onlyLast } = {}) { | memo.scope = function memoScopeCreate(fun, { signal, onlyLast } = {}) { | ||||||
| 	let cache = oCreate(); | 	let cache = oCreate(); | ||||||
| 	function memoScope2(...args) { | 	function memoScope(...args) { | ||||||
| 		if (signal && signal.aborted) | 		if (signal && signal.aborted) | ||||||
| 			return fun.apply(this, args); | 			return fun.apply(this, args); | ||||||
| 		let cache_local = onlyLast ? cache : oCreate(); | 		let cache_local = onlyLast ? cache : oCreate(); | ||||||
| @@ -651,10 +651,10 @@ memo.scope = function memoScope(fun, { signal, onlyLast } = {}) { | |||||||
| 		cache = cache_local; | 		cache = cache_local; | ||||||
| 		return out; | 		return out; | ||||||
| 	} | 	} | ||||||
| 	memoScope2[memoMark] = true; | 	memoScope[memoMark] = true; | ||||||
| 	memoScope2.clear = () => cache = oCreate(); | 	memoScope.clear = () => cache = oCreate(); | ||||||
| 	if (signal) signal.addEventListener("abort", memoScope2.clear); | 	if (signal) signal.addEventListener("abort", memoScope.clear); | ||||||
| 	return memoScope2; | 	return memoScope; | ||||||
| }; | }; | ||||||
| export { | export { | ||||||
| 	assign, | 	assign, | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								dist/esm.min.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								dist/esm.min.js
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										43
									
								
								dist/iife-with-signals.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										43
									
								
								dist/iife-with-signals.js
									
									
									
									
										vendored
									
									
								
							| @@ -86,19 +86,6 @@ var DDE = (() => { | |||||||
| 	function kebabToCamel(name) { | 	function kebabToCamel(name) { | ||||||
| 		return name.replace(/-./g, (x) => x[1].toUpperCase()); | 		return name.replace(/-./g, (x) => x[1].toUpperCase()); | ||||||
| 	} | 	} | ||||||
| 	var Defined = class extends Error { |  | ||||||
| 		constructor() { |  | ||||||
| 			super(); |  | ||||||
| 			const [curr, ...rest] = this.stack.split("\n"); |  | ||||||
| 			const curr_file = curr.slice(curr.indexOf("@"), curr.indexOf(".js:") + 4); |  | ||||||
| 			const curr_lib = curr_file.includes("src/helpers.js") ? "src/" : curr_file; |  | ||||||
| 			this.stack = rest.find((l) => !l.includes(curr_lib)) || curr; |  | ||||||
| 		} |  | ||||||
| 		get compact() { |  | ||||||
| 			const { stack } = this; |  | ||||||
| 			return stack.slice(0, stack.indexOf("@") + 1) + "\u2026" + stack.slice(stack.lastIndexOf("/")); |  | ||||||
| 		} |  | ||||||
| 	}; |  | ||||||
|  |  | ||||||
| 	// src/dom-lib/common.js | 	// src/dom-lib/common.js | ||||||
| 	var enviroment = { | 	var enviroment = { | ||||||
| @@ -348,7 +335,7 @@ var DDE = (() => { | |||||||
| 	var scope = { | 	var scope = { | ||||||
| 		/** | 		/** | ||||||
| 		* Gets the current scope | 		* Gets the current scope | ||||||
| 		* @returns {Object} Current scope context | 		* @returns {typeof scopes[number]} Current scope context | ||||||
| 		*/ | 		*/ | ||||||
| 		get current() { | 		get current() { | ||||||
| 			return scopes[scopes.length - 1]; | 			return scopes[scopes.length - 1]; | ||||||
| @@ -708,9 +695,9 @@ var DDE = (() => { | |||||||
| 	memo.isScope = function(obj) { | 	memo.isScope = function(obj) { | ||||||
| 		return obj[memoMark]; | 		return obj[memoMark]; | ||||||
| 	}; | 	}; | ||||||
| 	memo.scope = function memoScope(fun, { signal: signal2, onlyLast } = {}) { | 	memo.scope = function memoScopeCreate(fun, { signal: signal2, onlyLast } = {}) { | ||||||
| 		let cache = oCreate(); | 		let cache = oCreate(); | ||||||
| 		function memoScope2(...args) { | 		function memoScope(...args) { | ||||||
| 			if (signal2 && signal2.aborted) | 			if (signal2 && signal2.aborted) | ||||||
| 				return fun.apply(this, args); | 				return fun.apply(this, args); | ||||||
| 			let cache_local = onlyLast ? cache : oCreate(); | 			let cache_local = onlyLast ? cache : oCreate(); | ||||||
| @@ -725,10 +712,10 @@ var DDE = (() => { | |||||||
| 			cache = cache_local; | 			cache = cache_local; | ||||||
| 			return out; | 			return out; | ||||||
| 		} | 		} | ||||||
| 		memoScope2[memoMark] = true; | 		memoScope[memoMark] = true; | ||||||
| 		memoScope2.clear = () => cache = oCreate(); | 		memoScope.clear = () => cache = oCreate(); | ||||||
| 		if (signal2) signal2.addEventListener("abort", memoScope2.clear); | 		if (signal2) signal2.addEventListener("abort", memoScope.clear); | ||||||
| 		return memoScope2; | 		return memoScope; | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| 	// src/signals-lib/helpers.js | 	// src/signals-lib/helpers.js | ||||||
| @@ -845,17 +832,17 @@ var DDE = (() => { | |||||||
| 	}; | 	}; | ||||||
| 	var key_reactive = "__dde_reactive"; | 	var key_reactive = "__dde_reactive"; | ||||||
| 	signal.el = function(s, map) { | 	signal.el = function(s, map) { | ||||||
| 		map = memo.isScope(map) ? map : memo.scope(map, { onlyLast: true }); | 		const mapScoped = memo.isScope(map) ? map : memo.scope(map, { onlyLast: true }); | ||||||
| 		const mark_start = createElement.mark({ type: "reactive", source: new Defined().compact }, true); | 		const { current } = scope, { scope: sc } = current; | ||||||
|  | 		const mark_start = createElement.mark({ type: "reactive", component: sc && sc.name || "" }, true); | ||||||
| 		const mark_end = mark_start.end; | 		const mark_end = mark_start.end; | ||||||
| 		const out = enviroment.D.createDocumentFragment(); | 		const out = enviroment.D.createDocumentFragment(); | ||||||
| 		out.append(mark_start, mark_end); | 		out.append(mark_start, mark_end); | ||||||
| 		const { current } = scope; |  | ||||||
| 		const reRenderReactiveElement = (v) => { | 		const reRenderReactiveElement = (v) => { | ||||||
| 			if (!mark_start.parentNode || !mark_end.parentNode) | 			if (!mark_start.parentNode || !mark_end.parentNode) | ||||||
| 				return removeSignalListener(s, reRenderReactiveElement); | 				return removeSignalListener(s, reRenderReactiveElement); | ||||||
| 			scope.push(current); | 			scope.push(current); | ||||||
| 			let els = map(v); | 			let els = mapScoped(v); | ||||||
| 			scope.pop(); | 			scope.pop(); | ||||||
| 			if (!Array.isArray(els)) | 			if (!Array.isArray(els)) | ||||||
| 				els = [els]; | 				els = [els]; | ||||||
| @@ -875,7 +862,7 @@ var DDE = (() => { | |||||||
| 		current.host(on.disconnected( | 		current.host(on.disconnected( | ||||||
| 			() => ( | 			() => ( | ||||||
| 				/*! Clears cached elements for reactive element `S.el` */ | 				/*! Clears cached elements for reactive element `S.el` */ | ||||||
| 				map.clear() | 				mapScoped.clear() | ||||||
| 			) | 			) | ||||||
| 		)); | 		)); | ||||||
| 		return out; | 		return out; | ||||||
| @@ -947,9 +934,8 @@ var DDE = (() => { | |||||||
| 	function removeSignalsFromElements(s, listener, ...notes) { | 	function removeSignalsFromElements(s, listener, ...notes) { | ||||||
| 		const { current } = scope; | 		const { current } = scope; | ||||||
| 		current.host(function(element) { | 		current.host(function(element) { | ||||||
| 			if (element[key_reactive]) | 			if (!element[key_reactive]) element[key_reactive] = []; | ||||||
| 				return element[key_reactive].push([[s, listener], ...notes]); | 			element[key_reactive].push([[s, listener], ...notes]); | ||||||
| 			element[key_reactive] = []; |  | ||||||
| 			if (current.prevent) return; | 			if (current.prevent) return; | ||||||
| 			on.disconnected( | 			on.disconnected( | ||||||
| 				() => ( | 				() => ( | ||||||
| @@ -994,7 +980,6 @@ var DDE = (() => { | |||||||
| 				onclear, | 				onclear, | ||||||
| 				host, | 				host, | ||||||
| 				listeners: /* @__PURE__ */ new Set(), | 				listeners: /* @__PURE__ */ new Set(), | ||||||
| 				defined: new Defined().stack, |  | ||||||
| 				readonly | 				readonly | ||||||
| 			}), | 			}), | ||||||
| 			enumerable: false, | 			enumerable: false, | ||||||
|   | |||||||
							
								
								
									
										7
									
								
								dist/iife-with-signals.min.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								dist/iife-with-signals.min.js
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										14
									
								
								dist/iife.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										14
									
								
								dist/iife.js
									
									
									
									
										vendored
									
									
								
							| @@ -316,7 +316,7 @@ var DDE = (() => { | |||||||
| 	var scope = { | 	var scope = { | ||||||
| 		/** | 		/** | ||||||
| 		* Gets the current scope | 		* Gets the current scope | ||||||
| 		* @returns {Object} Current scope context | 		* @returns {typeof scopes[number]} Current scope context | ||||||
| 		*/ | 		*/ | ||||||
| 		get current() { | 		get current() { | ||||||
| 			return scopes[scopes.length - 1]; | 			return scopes[scopes.length - 1]; | ||||||
| @@ -676,9 +676,9 @@ var DDE = (() => { | |||||||
| 	memo.isScope = function(obj) { | 	memo.isScope = function(obj) { | ||||||
| 		return obj[memoMark]; | 		return obj[memoMark]; | ||||||
| 	}; | 	}; | ||||||
| 	memo.scope = function memoScope(fun, { signal, onlyLast } = {}) { | 	memo.scope = function memoScopeCreate(fun, { signal, onlyLast } = {}) { | ||||||
| 		let cache = oCreate(); | 		let cache = oCreate(); | ||||||
| 		function memoScope2(...args) { | 		function memoScope(...args) { | ||||||
| 			if (signal && signal.aborted) | 			if (signal && signal.aborted) | ||||||
| 				return fun.apply(this, args); | 				return fun.apply(this, args); | ||||||
| 			let cache_local = onlyLast ? cache : oCreate(); | 			let cache_local = onlyLast ? cache : oCreate(); | ||||||
| @@ -693,10 +693,10 @@ var DDE = (() => { | |||||||
| 			cache = cache_local; | 			cache = cache_local; | ||||||
| 			return out; | 			return out; | ||||||
| 		} | 		} | ||||||
| 		memoScope2[memoMark] = true; | 		memoScope[memoMark] = true; | ||||||
| 		memoScope2.clear = () => cache = oCreate(); | 		memoScope.clear = () => cache = oCreate(); | ||||||
| 		if (signal) signal.addEventListener("abort", memoScope2.clear); | 		if (signal) signal.addEventListener("abort", memoScope.clear); | ||||||
| 		return memoScope2; | 		return memoScope; | ||||||
| 	}; | 	}; | ||||||
| 	return __toCommonJS(index_exports); | 	return __toCommonJS(index_exports); | ||||||
| })(); | })(); | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								dist/iife.min.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								dist/iife.min.js
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @@ -23,14 +23,6 @@ export function DataDashboard() { | |||||||
| 		conversion: [2.9, 3.5, 3.7, 2.6, 3.4, 3.5, 2.8, 2.8, 2.8, 3.1, 3.0, 2.7], | 		conversion: [2.9, 3.5, 3.7, 2.6, 3.4, 3.5, 2.8, 2.8, 2.8, 3.1, 3.0, 2.7], | ||||||
| 		months: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] | 		months: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| 	// Application state |  | ||||||
| 	const selectedYear = S(2024); |  | ||||||
| 	const selectedDataType = S(/** @type {'sales' | 'visitors' | 'conversion'} */ ('sales')); |  | ||||||
| 	const isLoading = S(false); |  | ||||||
| 	const error = S(null); |  | ||||||
|  |  | ||||||
| 	// Filter options |  | ||||||
| 	const years = [2022, 2023, 2024]; | 	const years = [2022, 2023, 2024]; | ||||||
| 	const dataTypes = [ | 	const dataTypes = [ | ||||||
| 		{ id: 'sales', label: 'Sales', unit: 'K' }, | 		{ id: 'sales', label: 'Sales', unit: 'K' }, | ||||||
| @@ -38,42 +30,32 @@ export function DataDashboard() { | |||||||
| 		{ id: 'conversion', label: 'Conversion Rate', unit: '%' } | 		{ id: 'conversion', label: 'Conversion Rate', unit: '%' } | ||||||
| 	]; | 	]; | ||||||
|  |  | ||||||
| 	// Computed values | 	// Filter options | ||||||
| 	const selectedData = S(() => { | 	const selectedYear = S(2024); | ||||||
| 		return DATA[selectedDataType.get()]; |  | ||||||
| 	}); |  | ||||||
|  |  | ||||||
| 	const currentDataType = S(() => { |  | ||||||
| 		return dataTypes.find(type => type.id === selectedDataType.get()); |  | ||||||
| 	}); |  | ||||||
|  |  | ||||||
| 	const totalValue = S(() => { |  | ||||||
| 		const data = selectedData.get(); |  | ||||||
| 		return data.reduce((sum, value) => sum + value, 0); |  | ||||||
| 	}); |  | ||||||
|  |  | ||||||
| 	const averageValue = S(() => { |  | ||||||
| 		const data = selectedData.get(); |  | ||||||
| 		return data.reduce((sum, value) => sum + value, 0) / data.length; |  | ||||||
| 	}); |  | ||||||
|  |  | ||||||
| 	const highestValue = S(() => { |  | ||||||
| 		return Math.max(...selectedData.get()); |  | ||||||
| 	}); |  | ||||||
|  |  | ||||||
| 	// Event handlers |  | ||||||
| 	const onYearChange = on("change", e => { | 	const onYearChange = on("change", e => { | ||||||
| 		selectedYear.set(parseInt(/** @type {HTMLSelectElement} */(e.target).value)); | 		selectedYear.set(parseInt(/** @type {HTMLSelectElement} */(e.target).value)); | ||||||
| 		loadData(); | 		loadData(); | ||||||
| 	}); | 	}); | ||||||
|  | 	const selectedDataType = S(/** @type {'sales' | 'visitors' | 'conversion'} */ ('sales')); | ||||||
| 	const onDataTypeChange = on("click", e => { | 	const onDataTypeChange = on("click", e => { | ||||||
| 		const type = /** @type {'sales' | 'visitors' | 'conversion'} */( | 		const type = /** @type {'sales' | 'visitors' | 'conversion'} */( | ||||||
| 			/** @type {HTMLButtonElement} */(e.currentTarget).dataset.type); | 			/** @type {HTMLButtonElement} */(e.currentTarget).dataset.type); | ||||||
| 		selectedDataType.set(type); | 		selectedDataType.set(type); | ||||||
| 	}); | 	}); | ||||||
|  | 	const currentDataType = S(() => dataTypes.find(type => type.id === selectedDataType.get())); | ||||||
|  | 	const selectedData = S(() => DATA[selectedDataType.get()]); | ||||||
|  |  | ||||||
|  | 	// Values based on filters | ||||||
|  | 	const totalValue = S(() => selectedData.get().reduce((sum, value) => sum + value, 0)); | ||||||
|  | 	const averageValue = S(() => { | ||||||
|  | 		const data = selectedData.get(); | ||||||
|  | 		return data.reduce((sum, value) => sum + value, 0) / data.length; | ||||||
|  | 	}); | ||||||
|  | 	const highestValue = S(() => Math.max(...selectedData.get())); | ||||||
|  |  | ||||||
| 	// Simulate data loading | 	// Simulate data loading | ||||||
|  | 	const isLoading = S(false); | ||||||
|  | 	const error = S(null); | ||||||
| 	function loadData() { | 	function loadData() { | ||||||
| 		isLoading.set(true); | 		isLoading.set(true); | ||||||
| 		error.set(null); | 		error.set(null); | ||||||
| @@ -114,7 +96,7 @@ export function DataDashboard() { | |||||||
| 			// Draw grid labels | 			// Draw grid labels | ||||||
| 			ctx.fillStyle = '#999'; | 			ctx.fillStyle = '#999'; | ||||||
| 			ctx.font = '12px Arial'; | 			ctx.font = '12px Arial'; | ||||||
| 			ctx.fillText(Math.round(maxValue * (i / 5)), 20, y + 5); | 			ctx.fillText(Math.round(maxValue * (i / 5)).toString(), 20, y + 5); | ||||||
| 		} | 		} | ||||||
| 		ctx.stroke(); | 		ctx.stroke(); | ||||||
|  |  | ||||||
| @@ -154,7 +136,6 @@ export function DataDashboard() { | |||||||
| 			) | 			) | ||||||
| 		), | 		), | ||||||
|  |  | ||||||
| 		// Error message (only shown when there's an error) |  | ||||||
| 		S.el(error, errorMsg => !errorMsg | 		S.el(error, errorMsg => !errorMsg | ||||||
| 			? el() | 			? el() | ||||||
| 			: el("div", { className: "error-message" }).append( | 			: el("div", { className: "error-message" }).append( | ||||||
| @@ -163,7 +144,6 @@ export function DataDashboard() { | |||||||
| 				), | 				), | ||||||
| 		), | 		), | ||||||
|  |  | ||||||
| 		// Loading indicator |  | ||||||
| 		S.el(isLoading, loading => !loading | 		S.el(isLoading, loading => !loading | ||||||
| 			? el() | 			? el() | ||||||
| 			: el("div", { className: "loading-spinner" }) | 			: el("div", { className: "loading-spinner" }) | ||||||
|   | |||||||
| @@ -27,25 +27,23 @@ const imagesSample = (url=> [ | |||||||
|  * @returns {HTMLElement} Gallery element |  * @returns {HTMLElement} Gallery element | ||||||
|  */ |  */ | ||||||
| export function ImageGallery(images= imagesSample) { | export function ImageGallery(images= imagesSample) { | ||||||
|  |  | ||||||
| 	// Application state |  | ||||||
| 	const selectedImageId = S(null); |  | ||||||
| 	const filterTag = S('all'); | 	const filterTag = S('all'); | ||||||
| 	const imagesToDisplay = S(() => { | 	const imagesToDisplay = S(() => { | ||||||
| 		const tag = filterTag.get(); | 		const tag = filterTag.get(); | ||||||
| 		if (tag === 'all') return images; | 		if (tag === 'all') return images; | ||||||
| 		else return images.filter(img => img.alt.toLowerCase() === tag); | 		else return images.filter(img => img.alt.toLowerCase() === tag); | ||||||
| 	}) | 	}) | ||||||
|  | 	const onFilterChange = tag => on("click", () => { | ||||||
|  | 		filterTag.set(tag); | ||||||
|  | 	}); | ||||||
|  |  | ||||||
| 	// Derived state | 	// Lightbox | ||||||
|  | 	const selectedImageId = S(null); | ||||||
| 	const selectedImage = S(() => { | 	const selectedImage = S(() => { | ||||||
| 		const id = selectedImageId.get(); | 		const id = selectedImageId.get(); | ||||||
| 		return id ? images.find(img => img.id === id) : null; | 		return id ? images.find(img => img.id === id) : null; | ||||||
| 	}); | 	}); | ||||||
|  |  | ||||||
| 	const isLightboxOpen = S(() => selectedImage.get() !== null); | 	const isLightboxOpen = S(() => selectedImage.get() !== null); | ||||||
|  |  | ||||||
| 	// Event handlers |  | ||||||
| 	const onImageClick = id => on("click", () => { | 	const onImageClick = id => on("click", () => { | ||||||
| 		selectedImageId.set(id); | 		selectedImageId.set(id); | ||||||
| 		document.body.style.overflow = 'hidden'; // Prevent scrolling when lightbox is open | 		document.body.style.overflow = 'hidden'; // Prevent scrolling when lightbox is open | ||||||
| @@ -76,9 +74,6 @@ export function ImageGallery(images= imagesSample) { | |||||||
| 		const nextIndex = (currentIndex + 1) % images.length; | 		const nextIndex = (currentIndex + 1) % images.length; | ||||||
| 		selectedImageId.set(images[nextIndex].id); | 		selectedImageId.set(images[nextIndex].id); | ||||||
| 	}; | 	}; | ||||||
| 	const onFilterChange = tag => on("click", () => { |  | ||||||
| 		filterTag.set(tag); |  | ||||||
| 	}); |  | ||||||
|  |  | ||||||
| 	// Keyboard navigation handler | 	// Keyboard navigation handler | ||||||
| 	function handleKeyDown(e) { | 	function handleKeyDown(e) { | ||||||
|   | |||||||
| @@ -73,8 +73,17 @@ export function Form({ initial }) { | |||||||
| 			this.value[key] = value; | 			this.value[key] = value; | ||||||
| 		} | 		} | ||||||
| 	}); | 	}); | ||||||
|  | 	/** | ||||||
|  | 	 * Event handler for input events | ||||||
|  | 	 * @param {"value"|"checked"} prop | ||||||
|  | 	 * @returns {(ev: Event) => void} | ||||||
|  | 	 * */ | ||||||
|  | 	const onChange= prop => ev => { | ||||||
|  | 		const input = /** @type {HTMLInputElement} */(ev.target); | ||||||
|  | 		S.action(formState, "update", /** @type {keyof FormState} */(input.id), input[prop]); | ||||||
|  | 	}; | ||||||
|  |  | ||||||
| 	// Derived signals for validation | 	// Form validate state | ||||||
| 	const nameValid = S(() => formState.get().name.length >= 3); | 	const nameValid = S(() => formState.get().name.length >= 3); | ||||||
| 	const emailValid = S(() => { | 	const emailValid = S(() => { | ||||||
| 		const email = formState.get().email; | 		const email = formState.get().email; | ||||||
| @@ -89,8 +98,6 @@ export function Form({ initial }) { | |||||||
| 		return password === confirmPassword && confirmPassword !== ''; | 		return password === confirmPassword && confirmPassword !== ''; | ||||||
| 	}); | 	}); | ||||||
| 	const termsAgreed = S(() => formState.get().agreedToTerms); | 	const termsAgreed = S(() => formState.get().agreedToTerms); | ||||||
|  |  | ||||||
| 	// Overall form validity |  | ||||||
| 	const formValid = S(() => | 	const formValid = S(() => | ||||||
| 		nameValid.get() && | 		nameValid.get() && | ||||||
| 		emailValid.get() && | 		emailValid.get() && | ||||||
| @@ -99,16 +106,6 @@ export function Form({ initial }) { | |||||||
| 		termsAgreed.get() | 		termsAgreed.get() | ||||||
| 	); | 	); | ||||||
|  |  | ||||||
| 	// Event handlers |  | ||||||
| 	/** |  | ||||||
| 	 * Event handler for input events |  | ||||||
| 	 * @param {"value"|"checked"} prop |  | ||||||
| 	 * @returns {(ev: Event) => void} |  | ||||||
| 	 * */ |  | ||||||
| 	const onChange= prop => ev => { |  | ||||||
| 		const input = /** @type {HTMLInputElement} */(ev.target); |  | ||||||
| 		S.action(formState, "update", /** @type {keyof FormState} */(input.id), input[prop]); |  | ||||||
| 	}; |  | ||||||
| 	const dispatcSubmit = dispatchEvent("form:submit", host); | 	const dispatcSubmit = dispatchEvent("form:submit", host); | ||||||
| 	const onSubmit = on("submit", e => { | 	const onSubmit = on("submit", e => { | ||||||
| 		e.preventDefault(); | 		e.preventDefault(); | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| // Example of reactive element marker | // Example of reactive element marker | ||||||
| <!--<dde:mark type="reactive" source="...">--> | <!--<dde:mark type="reactive" component="<component-name>">--> | ||||||
| <!-- content that updates when signal changes --> | <!-- content that updates when signal changes --> | ||||||
| <!--</dde:mark>--> | <!--</dde:mark>--> | ||||||
|   | |||||||
| @@ -105,7 +105,7 @@ export function page({ pkg, info }){ | |||||||
| 			or directly include it from a CDN for quick prototyping. | 			or directly include it from a CDN for quick prototyping. | ||||||
| 		`), | 		`), | ||||||
| 		el("h4", "npm installation"), | 		el("h4", "npm installation"), | ||||||
| 		el(code, { content: "npm install deka-dom-el # Coming soon", language: "shell", page_id }), | 		el(code, { content: "npm install deka-dom-el --save", language: "shell", page_id }), | ||||||
| 		el("h4", "CDN / Direct Script Usage"), | 		el("h4", "CDN / Direct Script Usage"), | ||||||
| 		el("p").append(T` | 		el("p").append(T` | ||||||
| 			Use the interactive selector below to choose your preferred format: | 			Use the interactive selector below to choose your preferred format: | ||||||
| @@ -164,4 +164,4 @@ export function page({ pkg, info }){ | |||||||
| 			Let’s get started with the basics of creating elements! | 			Let’s get started with the basics of creating elements! | ||||||
| 		`), | 		`), | ||||||
| 	); | 	); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -81,7 +81,6 @@ export function page({ pkg, info }){ | |||||||
| 			el("li", t`actions: Custom actions that can be performed on the signal`), | 			el("li", t`actions: Custom actions that can be performed on the signal`), | ||||||
| 			el("li", t`onclear: Functions to run when the signal is cleared`), | 			el("li", t`onclear: Functions to run when the signal is cleared`), | ||||||
| 			el("li", t`host: Reference to the host element/scope in which the signal was created`), | 			el("li", t`host: Reference to the host element/scope in which the signal was created`), | ||||||
| 			el("li", t`defined: Stack trace information for debugging`), |  | ||||||
| 			el("li", t`readonly: Boolean flag indicating if the signal is read-only`) | 			el("li", t`readonly: Boolean flag indicating if the signal is read-only`) | ||||||
| 		), | 		), | ||||||
| 		el("p").append(T` | 		el("p").append(T` | ||||||
|   | |||||||
							
								
								
									
										4
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										4
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -1,12 +1,12 @@ | |||||||
| { | { | ||||||
| 	"name": "deka-dom-el", | 	"name": "deka-dom-el", | ||||||
| 	"version": "0.9.2-alpha", | 	"version": "0.9.3-alpha", | ||||||
| 	"lockfileVersion": 3, | 	"lockfileVersion": 3, | ||||||
| 	"requires": true, | 	"requires": true, | ||||||
| 	"packages": { | 	"packages": { | ||||||
| 		"": { | 		"": { | ||||||
| 			"name": "deka-dom-el", | 			"name": "deka-dom-el", | ||||||
| 			"version": "0.9.2-alpha", | 			"version": "0.9.3-alpha", | ||||||
| 			"license": "MIT", | 			"license": "MIT", | ||||||
| 			"devDependencies": { | 			"devDependencies": { | ||||||
| 				"@size-limit/preset-small-lib": "~11.2", | 				"@size-limit/preset-small-lib": "~11.2", | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| { | { | ||||||
| 	"name": "deka-dom-el", | 	"name": "deka-dom-el", | ||||||
| 	"version": "0.9.2-alpha", | 	"version": "0.9.3-alpha", | ||||||
| 	"description": "A low-code library that simplifies the creation of native DOM elements/components using small wrappers and tweaks.", | 	"description": "A low-code library that simplifies the creation of native DOM elements/components using small wrappers and tweaks.", | ||||||
| 	"author": "Jan Andrle <andrle.jan@centrum.cz>", | 	"author": "Jan Andrle <andrle.jan@centrum.cz>", | ||||||
| 	"license": "MIT", | 	"license": "MIT", | ||||||
| @@ -89,7 +89,7 @@ | |||||||
| 		"test": "echo \"Error: no tests yet\"", | 		"test": "echo \"Error: no tests yet\"", | ||||||
| 		"build": "bs/build.js", | 		"build": "bs/build.js", | ||||||
| 		"lint": "bs/lint.sh", | 		"lint": "bs/lint.sh", | ||||||
| 		"docs": "bs/docs.sh" | 		"docs": "bs/docs.js" | ||||||
| 	}, | 	}, | ||||||
| 	"keywords": [ | 	"keywords": [ | ||||||
| 		"dom", | 		"dom", | ||||||
|   | |||||||
| @@ -19,7 +19,7 @@ const store_abort= new WeakMap(); | |||||||
| export const scope= { | export const scope= { | ||||||
| 	/** | 	/** | ||||||
| 	 * Gets the current scope | 	 * Gets the current scope | ||||||
| 	 * @returns {Object} Current scope context | 	 * @returns {typeof scopes[number]} Current scope context | ||||||
| 	 */ | 	 */ | ||||||
| 	get current(){ return scopes[scopes.length-1]; }, | 	get current(){ return scopes[scopes.length-1]; }, | ||||||
|  |  | ||||||
|   | |||||||
| @@ -68,21 +68,3 @@ export function observedAttributes(instance, observedAttribute){ | |||||||
|  * @returns {string} The camelCase string |  * @returns {string} The camelCase string | ||||||
|  */ |  */ | ||||||
| function kebabToCamel(name){ return name.replace(/-./g, x=> x[1].toUpperCase()); } | function kebabToCamel(name){ return name.replace(/-./g, x=> x[1].toUpperCase()); } | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Error class for definition tracking |  | ||||||
|  * Shows the correct stack trace for debugging (signal) creation |  | ||||||
|  */ |  | ||||||
| export class Defined extends Error{ |  | ||||||
| 	constructor(){ |  | ||||||
| 		super(); |  | ||||||
| 		const [ curr, ...rest ]= this.stack.split("\n"); |  | ||||||
| 		const curr_file= curr.slice(curr.indexOf("@"), curr.indexOf(".js:")+4); |  | ||||||
| 		const curr_lib= curr_file.includes("src/helpers.js") ? "src/" : curr_file; |  | ||||||
| 		this.stack= rest.find(l=> !l.includes(curr_lib)) || curr; |  | ||||||
| 	} |  | ||||||
| 	get compact(){ |  | ||||||
| 		const { stack }= this; |  | ||||||
| 		return stack.slice(0, stack.indexOf("@")+1)+"…"+stack.slice(stack.lastIndexOf("/")); |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|   | |||||||
| @@ -27,7 +27,7 @@ memo.isScope= function(obj){ return obj[memoMark]; }; | |||||||
|  * @param {AbortSignal} options.signal |  * @param {AbortSignal} options.signal | ||||||
|  * @param {boolean} [options.onlyLast=false] |  * @param {boolean} [options.onlyLast=false] | ||||||
|  * */ |  * */ | ||||||
| memo.scope= function memoScope(fun, { signal, onlyLast }= {}){ | memo.scope= function memoScopeCreate(fun, { signal, onlyLast }= {}){ | ||||||
| 	let cache= oCreate(); | 	let cache= oCreate(); | ||||||
| 	function memoScope(...args){ | 	function memoScope(...args){ | ||||||
| 		if(signal && signal.aborted) | 		if(signal && signal.aborted) | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| import { queueSignalWrite, mark } from "./helpers.js"; | import { queueSignalWrite, mark } from "./helpers.js"; | ||||||
| export { mark }; | export { mark }; | ||||||
| import { hasOwn, Defined, oCreate, oAssign } from "../helpers.js"; | import { hasOwn, oCreate, oAssign } from "../helpers.js"; | ||||||
|  |  | ||||||
| const Signal = oCreate(null, { | const Signal = oCreate(null, { | ||||||
| 	get: { value(){ return read(this); } }, | 	get: { value(){ return read(this); } }, | ||||||
| @@ -169,21 +169,21 @@ import { memo } from "../memo.js"; | |||||||
|  * Creates a reactive DOM element that re-renders when signal changes |  * Creates a reactive DOM element that re-renders when signal changes | ||||||
|  * |  * | ||||||
|  * @param {Object} s - Signal object to watch |  * @param {Object} s - Signal object to watch | ||||||
|  * @param {Function} map - Function mapping signal value to DOM elements |  * @param {Function} mapScoped - Function mapping signal value to DOM elements | ||||||
|  * @returns {DocumentFragment} Fragment containing reactive elements |  * @returns {DocumentFragment} Fragment containing reactive elements | ||||||
|  */ |  */ | ||||||
| signal.el= function(s, map){ | signal.el= function(s, map){ | ||||||
| 	map= memo.isScope(map) ? map : memo.scope(map, { onlyLast: true }); | 	const mapScoped= memo.isScope(map) ? map : memo.scope(map, { onlyLast: true }); | ||||||
| 	const mark_start= el.mark({ type: "reactive", source: new Defined().compact }, true); | 	const { current }= scope, { scope: sc }= current; | ||||||
|  | 	const mark_start= el.mark({ type: "reactive", component: sc && sc.name || "" }, true); | ||||||
| 	const mark_end= mark_start.end; | 	const mark_end= mark_start.end; | ||||||
| 	const out= env.D.createDocumentFragment(); | 	const out= env.D.createDocumentFragment(); | ||||||
| 	out.append(mark_start, mark_end); | 	out.append(mark_start, mark_end); | ||||||
| 	const { current }= scope; |  | ||||||
| 	const reRenderReactiveElement= v=> { | 	const reRenderReactiveElement= v=> { | ||||||
| 		if(!mark_start.parentNode || !mark_end.parentNode) // === `isConnected` or wasn’t yet rendered | 		if(!mark_start.parentNode || !mark_end.parentNode) // === `isConnected` or wasn’t yet rendered | ||||||
| 			return removeSignalListener(s, reRenderReactiveElement); | 			return removeSignalListener(s, reRenderReactiveElement); | ||||||
| 		scope.push(current); | 		scope.push(current); | ||||||
| 		let els= map(v); | 		let els= mapScoped(v); | ||||||
| 		scope.pop(); | 		scope.pop(); | ||||||
| 		if(!Array.isArray(els)) | 		if(!Array.isArray(els)) | ||||||
| 			els= [ els ]; | 			els= [ els ]; | ||||||
| @@ -202,7 +202,7 @@ signal.el= function(s, map){ | |||||||
| 	reRenderReactiveElement(s.get()); | 	reRenderReactiveElement(s.get()); | ||||||
| 	current.host(on.disconnected(()=> | 	current.host(on.disconnected(()=> | ||||||
| 		/*! Clears cached elements for reactive element `S.el` */ | 		/*! Clears cached elements for reactive element `S.el` */ | ||||||
| 		map.clear() | 		mapScoped.clear() | ||||||
| 	)); | 	)); | ||||||
| 	return out; | 	return out; | ||||||
| }; | }; | ||||||
| @@ -314,9 +314,8 @@ export const signals_config= { | |||||||
| function removeSignalsFromElements(s, listener, ...notes){ | function removeSignalsFromElements(s, listener, ...notes){ | ||||||
| 	const { current }= scope; | 	const { current }= scope; | ||||||
| 	current.host(function(element){ | 	current.host(function(element){ | ||||||
| 		if(element[key_reactive]) | 		if(!element[key_reactive]) element[key_reactive]= []; | ||||||
| 			return element[key_reactive].push([ [ s, listener ], ...notes ]); | 		element[key_reactive].push([ [ s, listener ], ...notes ]); | ||||||
| 		element[key_reactive]= []; |  | ||||||
| 		if(current.prevent) return; // typically document.body, doenst need auto-remove as it should happen on page leave | 		if(current.prevent) return; // typically document.body, doenst need auto-remove as it should happen on page leave | ||||||
| 		on.disconnected(()=> | 		on.disconnected(()=> | ||||||
| 			/*! Clears all Signals listeners added in the current scope/host (`S.el`, `assign`, …?). | 			/*! Clears all Signals listeners added in the current scope/host (`S.el`, `assign`, …?). | ||||||
| @@ -386,7 +385,6 @@ function toSignal(s, value, actions, readonly= false){ | |||||||
| 		value: oAssign(oCreate(protoSigal), { | 		value: oAssign(oCreate(protoSigal), { | ||||||
| 			value, actions, onclear, host, | 			value, actions, onclear, host, | ||||||
| 			listeners: new Set(), | 			listeners: new Set(), | ||||||
| 			defined: new Defined().stack, |  | ||||||
| 			readonly | 			readonly | ||||||
| 		}), | 		}), | ||||||
| 		enumerable: false, | 		enumerable: false, | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user