nbhi 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Roy Niels
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,264 @@
1
+ # What?
2
+
3
+ **HTML includes and reactive rendering, all without any build tools**
4
+
5
+ d--b is a simple **4KB** library that allows you to include html files into other html files and turns them into [web components](https://developer.mozilla.org/en-US/docs/Web/API/Web_components). Lazy initialization of content (when entering the viewport) and a one line call to update/hydrate content in your html pages. No build tools required, no `package.json` needed.
6
+
7
+ # Why?
8
+
9
+ d--b is developed to create real-time data driven web sites. It has lower complexity than typical SPA applications but still offers more interactivy and flexibility than server side rendered web sites. _It works well with serverless application and a pub/sub data model_.
10
+
11
+ d--b is designed to be composable with other packages to build web sites. It takes the Lego approach, you select the packages for your needs, no lock-in into a single framework with a potential steep learning curve. d--b is build on standard browser supported technologies which means that you only need to read MDN as the source of knowledge.
12
+
13
+ d--b minimizes abstractions, just standard HTML, CSS and Javascript. Migrating from a SPA to d--b's multi page application (MPA) approach should mean you can do this one page at the time not having to migrate your whole system at once and pray that everything keeps working.
14
+
15
+ # Benefits
16
+
17
+ - Buildless web applications (minification is still possible of course) 👍
18
+ - Lightweight: **~4KB** 👍
19
+ - Benefit from standard web component encapsulation 👍
20
+ - Lazy load page content, when about to enter the viewport 👍
21
+ - Just standard web technologies, little abstractions 👍
22
+ - Composable with other packages
23
+ - Support for web component form fields out of the box 👍
24
+ - Multi Page Application (MPA) design
25
+ - The standard method how the web communicates between server and client 👍
26
+ - Normal native routing 👍
27
+ - Better SEO 👍
28
+ - Each page loads what it needs and no more 👍
29
+ - Fast initial draw 👍
30
+
31
+ # Examples
32
+
33
+ Please look in the **examples.html** for working examples.
34
+
35
+ # How?
36
+
37
+ ## Templates
38
+
39
+ Templates let you build complex systems and reuse components accross multiple pages, you can define templates in separate files or use them inline as a one-of if you just need the benefits of web components.
40
+
41
+ ### External templates
42
+
43
+ index.html
44
+
45
+ ```html
46
+
47
+ <html>
48
+ <body>
49
+ <my-component>Overwritten text</my-component>
50
+
51
+ <script type="module">
52
+ import initialize from '/d--b.js';
53
+ await initialize(); // await if loading external templates
54
+ </script>
55
+ </body>
56
+ </html>
57
+
58
+ ```
59
+
60
+ /components/my-component.html
61
+
62
+ ```html
63
+
64
+ <div>
65
+ <slot>Default text</slot>
66
+ </div>
67
+
68
+ <style>/* Scoped to the web component */</style>
69
+
70
+ ```
71
+
72
+ ### Internal templates
73
+
74
+ index.html
75
+
76
+ ```html
77
+
78
+ <html>
79
+ <body>
80
+ <template id="my-component">
81
+ <slot>Default</slot>
82
+ <style>/* Scoped to the web component */</style>
83
+ </template>
84
+
85
+ <my-component>Overwrite</my-component>
86
+
87
+ <script type="module">
88
+ import initialize from '/d--b.js';
89
+ initialize(); // No await needed for internal templates
90
+ </script>
91
+ </body>
92
+ </html>
93
+
94
+ ```
95
+
96
+ _Note: When you use an internal `<template>` you need to define an id, which will be the name of the tag._
97
+
98
+ ## Options
99
+
100
+ ```js
101
+
102
+ /**
103
+ * Options for initiating an instance
104
+ * @param {string} [prefix=db]
105
+ * @param {string} [source=/components]
106
+ **/
107
+
108
+ import initialize from 'd--b.js'
109
+ initialize({
110
+ // Prefix to use, ie <db-input> or <db-nav>
111
+ prefix: 'db',
112
+ // Where to find external web components, if string it will be a director
113
+ // when using an object the key is the name (without prefix) of the component
114
+ // and the value the path to the web-component
115
+ directory: '/components',
116
+ });
117
+
118
+ ```
119
+
120
+ ## Methods
121
+
122
+ ### .setSlot(value, [slotName], [htmlSelector])
123
+
124
+ ```js
125
+ /**
126
+ * Update data in a <slot> tag
127
+ * @param {string} value - What you want to assign to a <slot>
128
+ * @param {string} [slotName] - If not given value is assigned to default <slot>
129
+ * @param {string} [htmlSelector] - Target a html tag in a complex <slot>
130
+ **/
131
+
132
+ const element = document.querySelector('my-component');
133
+
134
+ // To default slot
135
+ element.setSlot('Some text');
136
+
137
+ // To slot named title
138
+ element.setSlot('Some text', 'title');
139
+
140
+ // To slot named title and withing the <em> tag
141
+ element.setSlot('Some text', 'title', 'em');
142
+
143
+ ```
144
+
145
+ ### .setChildren(data, [wrapperTag])
146
+
147
+ ```js
148
+ /**
149
+ * Updates one or more records in the child template
150
+ * @param {object|object[]} data - The data to use
151
+ **/
152
+
153
+ const element = document.querySelector('my-component');
154
+
155
+ // Update a single child, with data-slot named "title"
156
+ element.setChildren({ title: 'Some value' });
157
+
158
+ ```
159
+
160
+ _Note: Child templates always need to extend an existing tag, like `<tr>`, `<option>`, `<div>`, etc. This is because some parents like `<tbody>`, `<select>` will not render regular non extended web components._
161
+
162
+ ### .onceVisible(callback)
163
+
164
+ ```js
165
+ /**
166
+ * Runs one time when the web component comes into view
167
+ * @param {function} callback - Callback to run
168
+ **/
169
+ document.querySelector('my-component').onceVisible(element => {
170
+ subscribeToData('someCollection', data => element.setChildren(data));
171
+ });
172
+
173
+ ```
174
+
175
+ ### .checkValidity() (Only if the webcomponent includes a form element)
176
+
177
+ ```js
178
+ /**
179
+ * Checks if the field is valid
180
+ * @returns boolean
181
+ **/
182
+ document.querySelector('my-form-field').checkValidity();
183
+
184
+ ```
185
+
186
+ ### .validity (Only if the webcomponent includes a form element)
187
+
188
+ ```js
189
+ /**
190
+ * Get the standard validity object
191
+ * @returns ValidityState
192
+ **/
193
+ document.querySelector('my-form-field').validity;
194
+
195
+ ```
196
+
197
+ ### .validationMessage() (Only if the webcomponent includes a form element)
198
+
199
+ ```js
200
+ /**
201
+ * Gets the message for an invalid field, handy for bespoke themes
202
+ * @returns string
203
+ **/
204
+ document.querySelector('my-form-field').validationMessage();
205
+
206
+ ```
207
+
208
+ ### Attributes
209
+
210
+ It is possible to set attributes on a web component instance tag like `<my-component>` and they will automatically be assigned to the correct nodes in the underlying `<template>`. Any changes you make to the `<my-component>` attributes will automatically update the underlying component data.
211
+
212
+ index.html
213
+
214
+ ```html
215
+
216
+ <my-email data-id="1" value="me@me.com" readonly>User Email</my-email>
217
+
218
+ ```
219
+
220
+ my-component.html
221
+
222
+ ```html
223
+ <!--
224
+ Custom attributes like data-id need to be defined in the template, otherwise
225
+ d--b does not know where to assign the value
226
+ -->
227
+ <label data-id="">
228
+ <slot></slot>
229
+ <!-- d--b knows to add value and read only to the input field --->
230
+ <input type="email" placeholder="email">
231
+ </div>
232
+
233
+ ```
234
+
235
+ ```js
236
+
237
+ // Calling from JS is also easier, you don't need to find the shadowRoot
238
+ const element = document.querySelector('my-component')
239
+ element.setAttribute('value', 'Some value');
240
+ element.disabled = true;
241
+
242
+ ```
243
+
244
+ _Note: If there are multiple elements that can have an attribute that has been defined on the component instance it will assign it to all instances. You can assign attributes to the `<template>` tag but only if it extends an existing tag_
245
+
246
+ # FAQ
247
+
248
+ ### How to pronounce d--b?
249
+ However you like.
250
+
251
+ ### No build steps is nice, but I want to use TypeScript
252
+ If you want to use TS you have a couple of options. You can either add a build step, nothing is stopping you, the library supports it. If you want to stay buildless but want to add some sort of type checking and hinting you can consider using JSDoc, it is [supported](https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html#author) in TS as well.
253
+
254
+ ### Creating interactivity is verbose with native js DOM manipulation
255
+ d--b is not designed to be a single solution for everything, if you want to make DOM manipulation for interactivity easier I would suggest using dedicated libraries for that. You can consider [Alpine.js](https://alpinejs.dev/) or [Umbrella.js](https://umbrellajs.com/) for example. d--b is designed with composibility in mind so you decide what to add.
256
+
257
+ ### External npm packages require me the add build steps again
258
+ You could load them from a CDN if you want to stay 100% buildless, otherwise you could just add a simple minifier and bundler. This will stay very lightweight but probably is more suitable for production environments.
259
+
260
+ ### Anybody using web component technology?
261
+ Just check out the source code of github.com or youtube.com.
262
+
263
+ ### We are a team of multiple developers, using a framework ensures we write similar code
264
+ You are correct, when you choose a more opinionated framework it will most likely give more cohesive code overall when you work with multiple developers. However it also gives you lock-in. It is a trade-off that you have to make, depending on the team size and how agile your codebase needs to remain.
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ import{getAttributeMapping as A,updateAttributes as E}from"./attributes.js";export default async({prefix:d="db",source:i="/components"}={})=>{const m=new Set,p=new Set;if(typeof i!="string"||!Object.keys(i).length)throw new Error("source option needs to be a string or an object");if(typeof d!="string"||d==="")throw new Error("prefix option needs to be a string of at least one character");const g=C();h(document.querySelector("body")),await y(document.querySelector("body"));function h(n,o){n.querySelectorAll("template").forEach(t=>{if(!t.id&&o&&(t.id=`${o}-child`),!t.id)throw console.log(t,o),new Error("<template> is missing a mandatory id field");h(t.content,t.id);const e=t.id;S({name:e,template:t})})}function y(n){const o=[...n.querySelectorAll(":not(:defined)")].filter(t=>{const e=t.localName;return t.nodeType===1&&e.includes("-")&&e.startsWith(d)}).map(async t=>{const e=t.localName;if(!p.has(e)){let l=function(u){const f=u.content.querySelector("script");return new Promise(async b=>{if(f){const x=new Blob([f.textContent],{type:"text/javascript"}),w=URL.createObjectURL(x);b((await import(w)).default),URL.revokeObjectURL(w)}else b({})})};p.add(e);const s=await a(e),r=document.createElement("template");r.content.append(...s.children);const c=await l(r);await y(r.content),h(r.content,e),S({name:e,template:r,script:c})}async function a(s){const r=s.split("-").slice(1).join("-"),c=typeof i=="string"?`${i}/${r}.html`:i[r],l=await fetch(c);if(!l.ok)throw new Error(`Could not find component @ ${c}`);const u=await l.text();return new DOMParser().parseFromString(u,"text/html").querySelector("body")}});return Promise.all(o)}function S({name:n,template:o,script:t}){if(m.has(n))return;m.add(n);const e=!!o.content.querySelector("input, select, textarea"),a=A(o.content);customElements.define(n,class extends HTMLElement{static observedAttributes=Object.keys(a);static formAssociated=e;#t=null;constructor(){super();const s=document.importNode(o.content,!0);this.#t=this.attachShadow({mode:"open"}),this.#t.adoptedStyleSheets=g,this.#t.appendChild(s),e&&(this.elementInternals=this.attachInternals(),this.formElement=this.#t.querySelector("input, select, textarea"))}connectedCallback(){typeof t=="function"&&t(this)}attributeChangedCallback(s,r,c){r!==c&&E({name:s,value:c,attributeMapping:a,root:this.#t})}})}function C(){return Array.from(document.styleSheets).filter(n=>n?.ownerNode?.dataset?.inherit==="true").map(n=>{const o=new CSSStyleSheet;try{const t=Array.from(n.cssRules).map(e=>e.cssText).join(`
2
+ `);o.replaceSync(t)}catch(t){console.error("Permission denied for sheet:",n.href)}return o})}};
@@ -0,0 +1 @@
1
+ import o from"./element.js";export default(r,t)=>{const e=new IntersectionObserver(n=>{n[0].isIntersecting&&(t(),e.disconnect())});e.observe(o(r))};
package/dist/update.js ADDED
@@ -0,0 +1,2 @@
1
+ import{getAttributeMapping as $,updateAttributes as O}from"./attributes.js";import P from"./element.js";export default N=>{const f=P(N),d=new WeakMap,b=new WeakMap;let g=new Map,S=1,h;return l=>{if(j(l))throw new Error("Please convert date to string, ie. toISOString()");w(l)?q(l):m(l)?y(f,l):C(l)&&x(f,l);function j(t){return t?.getTime!==void 0}function m(t){return!w(t)&&!C(t)}function C(t){return!w(t)&&t!==null&&typeof t=="object"&&Object.keys(t).length}function w(t){return Array.isArray(t)}function x(t,r){Object.entries(r).forEach(([n,i])=>{if(n.startsWith("$")){const u=n.slice(1);if(t.shadowRoot)i===!1?t.removeAttribute(u):i===!0?t.setAttribute(u,""):t.setAttribute(u,i);else{b.has(t)||b.set(t,$(t));const A=b.get(t);O({name:u,value:i,attributeMapping:A,root:t})}}else y(t,i,n)})}function y(t,r,n){if(t.localName==="option")t.textContent=r;else if(t.localName==="tr"||t.localName==="li"||t.hasAttribute("child")&&!t.shadowRoot)if(t.hasAttribute("data-slot")&&t.dataset.slot===n)t.textContent=r;else{const i=t.querySelector(`[data-slot="${n}"]`);i&&(i.textContent=r)}else if(!n&&t.textContent!==r)t.textContent=r;else{let i=t.querySelector(`[slot="${n}"]`);i||(i=document.createElement("span"),i.setAttribute("slot",n),t.append(i)),i.textContent!==r&&(i.textContent=r)}}function q(t){const r=A();if(!h)throw new Error(`Cannot update ${N}, cannot find child element`);const n=new Map;t.forEach((e,a)=>{const p=u(e,a);let s=g.get(p);s||(s=h.cloneNode(!0),r.append(s)),s.cachedData!==e&&(C(e)?x(s,e):y(s,e),s.cachedData=e),n.set(p,s)}),g.forEach((e,a)=>{n.has(a)||e.remove()});let i;n.forEach(e=>{e!==i?.nextSibling&&r.insertBefore(e,i?i.nextSibling:r.firstChild),i=e}),g=n;function u(e,a){return e&&typeof e=="object"?(d.has(e)||d.set(e,S++),d.get(e)):`p:${e}:${a}`}function A(){const e=M();if(e.localName==="table")return a("tbody","tr");if(e.localName==="ol")return a("ol","li");if(e.localName==="ul")return a("ul","li");if(e.localName==="select")return a("select","option");if(e.querySelector("[child]")){const o=s(e.querySelector("[child]")).parentElement;return p("[child]",o),o.localName==="slot"&&o.hasAttribute("name")&&(h.slot=o.getAttribute("name")),o.localName==="slot"?f:o}throw new Error(`Could not find a parent to assign children to, valid
2
+ options are table, ul, ol, select or a structure with a child attribute`);function a(o,c){const E=s(o);return p(c,E),E}function p(o,c){h||(h=c.querySelector(o).cloneNode(!0),c.replaceChildren())}function s(o){const c=typeof o=="string"?e.querySelector(o):o;if(!c)throw new Error("Cannot update table, missing tbody");return c}function M(){return f.tagName.includes("-")?[...f.shadowRoot.children].find(o=>o.localName!=="script"&&o.localName!=="style"):f}}}}};
@@ -0,0 +1 @@
1
+ import o from"./element.js";export default s=>{const e=o(s);if(e.elementInternals){let r=function(){return l.tooShort?`The text needs to be at least ${a} characters long`:l.tooLong?`The text needs to be at most ${i} characters long`:t.validationMessage?t.validationMessage:"The value is invalid"};const t=e.formElement,n=t.value,a=t.getAttribute("minlength"),i=t.getAttribute("maxlength"),l={valueMissing:t.validity.valueMissing,typeMismatch:t.validity.typeMismatch,patternMismatch:t.validity.patternMismatch,rangeUnderflow:t.validity.rangeUnderflow,rangeOverflow:t.validity.rangeOverflow,stepMismatch:t.validity.stepMismatch,badInput:t.validity.badInput,customError:t.validity.customError,tooShort:a?n.length<parseInt(a):!1,tooLong:i?n.length>parseInt(i):!1};return e.elementInternals.setFormValue(n),e.elementInternals.setValidity(l,r(),t),e.elementInternals}};
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "nbhi",
3
+ "version": "0.5.0",
4
+ "description": "No-Build HTML Includes (NBHI) is a simple library that allows you to include html files into other html files and turns them into [web components](https://developer.mozilla.org/en-US/docs/Web/API/Web_components). Lazy initialization of content (when entering the viewport) and a one line call to update/hydrate content in your html pages. No build tools required, no `package.json` needed.",
5
+ "keywords": [
6
+ "web-components",
7
+ "html-includes",
8
+ "no-build",
9
+ "hydration",
10
+ "form-validation",
11
+ "lazy-loading",
12
+ "mpa",
13
+ "multi-page-application",
14
+ "composable"
15
+ ],
16
+ "homepage": "https://github.com/royniels/d--b#readme",
17
+ "bugs": {
18
+ "url": "https://github.com/royniels/d--b/issues"
19
+ },
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "git+https://github.com/royniels/d--b.git"
23
+ },
24
+ "files": [
25
+ "./dist/**"
26
+ ],
27
+ "exports": {
28
+ ".": "./dist/index.js",
29
+ "./update.js": "./dist/update.js",
30
+ "./onceVisible": "./dist/onceVisible.js",
31
+ "./validate": "./dist/validate.js"
32
+ },
33
+ "license": "MIT",
34
+ "author": "Roy Niels",
35
+ "type": "module",
36
+ "scripts": {
37
+ "test": "echo \"Error: no test specified\" && exit 1"
38
+ }
39
+ }