├── .gitignore ├── .npmignore ├── LICENSE ├── Readme.md ├── modules ├── anonymous.mjs ├── shadow.mjs ├── state.mjs └── utils.mjs ├── package.json ├── rollup.config.mjs ├── wc-helpers-main.mjs └── wc-helpers.mjs /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /package-lock.json 3 | /wc-helpers-bundle.min.mjs 4 | /wc-helpers-bundle.min.mjs.map 5 | *.tgz 6 | *.tar.gz 7 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | *.tgz 2 | *.tar.gz 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Muhammad Faiz 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. 22 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # `wc-helpers v2` 2 | 3 | `wc-helpers v2` help you create your custom elements. 4 | 5 | ## Install 6 | 7 | ### NPM 8 | 9 | ``` 10 | npm i wc-helpers 11 | ``` 12 | 13 | ```js 14 | // example of named imports 15 | import { WCHelpers, anonymousBase, html } from "wc-helpers"; 16 | // example of importing all 17 | import * as _ from "wc-helpers"; 18 | ``` 19 | 20 | ### CDN 21 | 22 | ```js 23 | // jsdlivr 24 | import * as _ from "https://cdn.jsdelivr.net/npm/wc-helpers@2/wc-helpers.mjs"; 25 | // unpkg 26 | import * as _ from "https://unpkg.com/wc-helpers@2/wc-helpers.mjs"; 27 | // relative CDN 28 | import * as _ from "../wc-helpers@2/wc-helpers.mjs"; 29 | ``` 30 | 31 | ## API 32 | 33 | ### `Anonymous` 34 | 35 | `Anonymous` mixin creates anonymous custom element that is registered automatically when constructed or 36 | when `toString` method is called. It won't be anonymous if manual registration is called before 37 | construction or `toString`. 38 | 39 | #### `anonymousBase` 40 | 41 | An optional static field symbol for `Anonymous` mixin as basename for anonymous name. 42 | 43 | Example: 44 | 45 | ```js 46 | import { Anonymous, anonymousBase } from "wc-helpers"; 47 | import { LitElement } from "lit"; 48 | import { html, unsafeStatic } from "lit/static-html.js"; 49 | 50 | class ElementA extends Anonymous(HTMLElement) { } 51 | class ElementB extends Anonymous(LitElement) { 52 | render() { 53 | return html`<${unsafeStatic(ElementA)}>`; 54 | } 55 | } 56 | class ElementC extends Anonymous(HTMLElement) { } 57 | customElements.define("element-c", ElementC); 58 | 59 | class ElementD extends Anonymous(HTMLElement) { 60 | static [anonymousBase] = "element-a"; 61 | } 62 | 63 | const b = new ElementB; 64 | document.body.appendChild(b); 65 | console.log(b); 66 | console.log(new ElementC); 67 | console.log(`${ElementD}`); 68 | ``` 69 | 70 | ### `Shadow` 71 | 72 | `Shadow` mixin creates automatic `shadowRoot`. 73 | 74 | #### `shadowOptions` 75 | 76 | An optional static field symbol for `Shadow` mixin as options for `attachShadow`. 77 | Note that the default is `{ mode: "open" }`. 78 | 79 | #### `shadowHTML` 80 | 81 | An optional static field symbol for `Shadow` mixin as HTML template for `shadowRoot`. 82 | It must be `html` literal or `HTMLTemplateElement` instance. 83 | 84 | #### `shadowCSS` 85 | 86 | An optional static field symbol for `Shadow` mixin as array of CSS stylesheet for `shadowRoot`. 87 | It must be an array of `css` literal or `CSSStyleSheet` instance. 88 | 89 | #### `shadowRoot` 90 | 91 | A symbol for `Shadow` mixin instance to access `shadowRoot`. It is useful when `mode` is closed. 92 | 93 | #### `shadowElements` 94 | 95 | A symbol for `Shadow` mixin instance as object to access elements that have `id` attribute. 96 | 97 | Example: 98 | 99 | ```js 100 | import { 101 | Shadow, Anonymous, chain, 102 | shadowOptions, shadowHTML, shadowCSS, 103 | shadowRoot, shadowElements, 104 | html, css, cssDisplayContents 105 | } from "wc-helpers"; 106 | 107 | class ElementA extends chain(Anonymous, Shadow, HTMLElement) { 108 | static [shadowOptions] = { mode: "closed" }; 109 | static [shadowHTML] = html`

Hello World

`; 110 | static [shadowCSS] = [ cssDisplayContents, css`p { background-color: #ffff00; }` ]; 111 | 112 | constructor() { 113 | super(); 114 | console.log(this[shadowRoot]); 115 | console.log(this[shadowElements]); 116 | } 117 | } 118 | 119 | document.body.appendChild(new ElementA); 120 | ``` 121 | 122 | ### `State` 123 | 124 | A simple state management class. 125 | 126 | #### `new State(initialValue, optionalName)` 127 | 128 | Constuctor for `State` instance. 129 | 130 | #### `State.value instance field` 131 | 132 | Current value of `State` instance. 133 | 134 | #### `State.name instance field` 135 | 136 | Optional `name` for `State` instance. 137 | 138 | #### `State.prototype.set(value)` 139 | 140 | Set current value of state and call listeners. 141 | 142 | #### `State.prototype.cset(value)` 143 | 144 | Set current value of state. It doesn't call listeners when `value` equal to `oldValue`. 145 | It returns `true` when listeners are called, otherwise `false`. 146 | 147 | #### `State function listener(value, oldValue) callback` 148 | 149 | Callback for `listen` method. 150 | 151 | #### `State.prototype.listen(listener)` 152 | 153 | Register `listener` to listen state change and call it for initialization. 154 | 155 | #### `State.prototype.listenNoInit(listener)` 156 | 157 | Register `listener` to listen state change but do not call it for initialization. 158 | 159 | #### `State.prototype.unlisten(listener)` 160 | 161 | Unregister `listener` from state instance. 162 | 163 | #### `State.prototype.unlistenAll()` 164 | 165 | Unregister all listeners from state instance. 166 | 167 | #### `State.listen(state0, ... , stateN, listener)` 168 | 169 | Register `listener` to listen state change of `state0, ... , stateN` and call it once for initialization. 170 | 171 | #### `State.listenNoInit(state0, ... , stateN, listener)` 172 | 173 | Register `listener` to listen state change of `state0, ... , stateN` but do not call it for initialization. 174 | 175 | #### `State.listenInitAll(state0, ... , stateN, listener)` 176 | 177 | Register `listener` to listen state change of `state0, ... , stateN` and call it for initialization 178 | for every `state0, ... , stateN`. 179 | 180 | #### `State.unlisten(listener, state0, ... , stateN)` 181 | 182 | Unregister `listener` from `state0, ... , stateN`. 183 | 184 | #### `State.unlistenAll(state0, ... , stateN)` 185 | 186 | Unregister all listeners from `state0, ... , stateN`. 187 | 188 | Example: 189 | 190 | ```js 191 | import { State } from "wc-helpers"; 192 | 193 | const stateA = new State("hello"); 194 | const stateB = new State(10); 195 | const stateC = new State(null, "stateC"); 196 | 197 | stateA.listen((value, oldValue) => console.log(value, oldValue)); 198 | stateB.listenNoInit(() => console.log(stateB.value)); 199 | stateC.listen(function(value) { console.log(this.name, this.value); }); 200 | 201 | State.listen(stateA, stateB, stateC, () => console.log(stateA.value, stateB.value, stateC.value)); 202 | State.listenInitAll(stateA, stateB, stateC, (value) => console.log(value)); 203 | 204 | stateA.set("world"); 205 | stateB.cset(10); 206 | stateC.set("stateC"); 207 | 208 | State.unlistenAll(stateA, stateB, stateC); 209 | ``` 210 | 211 | ### `Attribute` 212 | 213 | `Attribute` mixin automatically creates `State` instances from 214 | `observedAttributes` that can be listened. 215 | 216 | #### `attributeState` 217 | 218 | A symbol to access attribute `State` instances. 219 | 220 | Example: 221 | 222 | ```js 223 | import { State, Attribute, attributeState, Anonymous } from "wc-helpers"; 224 | 225 | class ElementA extends Anonymous(Attribute(HTMLElement)) { 226 | static observedAttributes = [ "data-src", "data-active", "data-message" ]; 227 | 228 | constructor() { 229 | super(); 230 | const at = this[attributeState]; 231 | State.listen(...Object.values(at), function() { console.log(this.name, this.value); }); 232 | at["data-src"].listen(src => console.log(src)); 233 | } 234 | } 235 | 236 | const a = new ElementA; 237 | a.setAttribute("data-src", "/home/"); 238 | a.setAttribute("data-active", ""); 239 | a.setAttribute("data-message", "Hello World"); 240 | a.removeAttribute("data-active"); // send null 241 | ``` 242 | 243 | ### Utility 244 | 245 | #### `abstract` 246 | 247 | An optional static field symbol for `Anonymous` mixin to prevent registration. 248 | 249 | ```js 250 | import { abstract, Anonymous } from "wc-helpers"; 251 | 252 | class ElementA extends Anonymous(HTMLElement) { 253 | static [abstract]; 254 | } 255 | 256 | console.log(`${ElementA}`); // do not throw 257 | new ElementA; // throw 258 | ``` 259 | 260 | #### `once` 261 | 262 | Run function once when argument is equal. Only support single object parameter. 263 | 264 | #### `chain` 265 | 266 | Flatten mixin. 267 | 268 | ```js 269 | import { chain, Anonymous, Attribute, Shadow } from "wc-helpers"; 270 | 271 | console.log(Anonymous(Attribute(Shadow(HTMLElement))) == chain(Anonymous, Attribute, Shadow, HTMLElement)); 272 | ``` 273 | 274 | #### `html` 275 | 276 | `html` tag wraps string literals into object. It escape `&<>'"=` characters from literals' arguments 277 | unless that argument is also another `html` tag or result of `htmlUnsafeString` function. When an 278 | argument is `Array`, its item will be evaluated according to the escape rules and they will be concancenated. 279 | 280 | #### `htmlUnsafeString` 281 | `htmlUnsafeString` allows string to be included in `html` tag argument without escaping. 282 | 283 | #### `css` 284 | 285 | `css` tag wraps string literals into object. No escaping performed. 286 | 287 | #### `htmlCache` 288 | 289 | Get `HTMLTemplateElement` instance from `html` tag. 290 | 291 | #### `cssCache` 292 | 293 | Get `CSSStyleSheet` instance from `css` tag. 294 | 295 | #### `cssDisplayBlock` 296 | 297 | #### `cssDisplayInline` 298 | 299 | #### `cssDisplayInlineBlock` 300 | 301 | #### `cssDisplayNone` 302 | 303 | #### `cssDisplayContents` 304 | 305 | Default `css` tag for `:host` element `display` set to `block`, `inline`, 306 | `inline-block`, `none`, or `contents` respectively. 307 | 308 | #### `htmlFragment` 309 | 310 | Create `DocumentFragment` instance from `html` tag or `HTMLTemplateElement` instance. 311 | 312 | Example: 313 | 314 | ```js 315 | import * as _ from "wc-helpers"; 316 | 317 | class ElementA extends _.chain(_.Anonymous, _.Shadow, HTMLElement) { 318 | static [_.shadowHTML] = _.html``; 319 | static [_.shadowCSS] = [ _.cssDisplayBlock, _.css`:host { color: #00ff00; }` ]; 320 | } 321 | 322 | const message = " 'Hello World' \"Hello World\" &="; 323 | const htmlA = _.html `<${ElementA}>${message}`; 324 | const templateA = _.htmlCache(htmlA); 325 | document.body.appendChild(_.htmlFragment(htmlA)); 326 | document.body.appendChild(_.htmlFragment(templateA)); 327 | document.body.insertAdjacentHTML("beforeend", htmlA); 328 | ``` 329 | 330 | #### `getGlobalCSS` 331 | 332 | `getGlobalCSS` gets constructed `CSSStyleSheet` from `link` and `style` elements which have 333 | `data-wc-global-css` attribute. 334 | 335 | #### `updateGlobalCSS` 336 | 337 | `updateGlobalCSS` update the constructed `CSSStyleSheet` for `getGlobalCSS` dynamically. 338 | 339 | Example: 340 | 341 | ```html 342 | 343 | 344 | 345 | Global CSS 346 | 351 | 369 | 372 | 373 | 374 |

Outside shadow DOM

375 | 376 | 377 | 378 | ``` 379 | 380 | ### `WCHelpers` 381 | 382 | `Anonymous`, `Attribute`, and `Shadow` mixin chained together. 383 | 384 | Here is an example adopted from [react.dev](https://react.dev/learn/scaling-up-with-reducer-and-context), 385 | with approximately 50% reduction of lines. 386 | 387 | ```html 388 | 389 | 390 | 391 | WCHelpers 392 | 397 | 471 | 476 | 479 | 480 | 481 | 482 | Philosopher's Path 483 | Visit the temple 484 | Drink matcha 485 | 486 | 487 | 488 | ``` 489 | -------------------------------------------------------------------------------- /modules/anonymous.mjs: -------------------------------------------------------------------------------- 1 | 2 | import {once, abstract} from "./utils.mjs"; 3 | 4 | export const anonymousBase = Symbol.for("wc-helpers/anonymous-base"); 5 | 6 | export const Anonymous = once(Base => class _Anonymous extends Base { 7 | static [abstract]; 8 | static [anonymousBase] = "anonymous-element"; 9 | 10 | constructor(...args) { 11 | _Anonymous.#define(new.target); 12 | super(...args); 13 | } 14 | 15 | static toString() { 16 | return _Anonymous.#define(this); 17 | } 18 | 19 | static #define(Target) { 20 | if (Object.hasOwn(Target, abstract)) 21 | return "anonymous-element---what-are-you-doing"; 22 | 23 | let name = customElements.getName(Target); 24 | if (name) 25 | return name; 26 | 27 | let suffix = "---"; 28 | for (;;) { 29 | do { 30 | suffix += Math.floor(Math.random() * 36).toString(36); 31 | } while (suffix.length < 6); 32 | 33 | name = Target[anonymousBase] + suffix; 34 | if (customElements.get(name)) 35 | continue; 36 | 37 | customElements.define(name, Target); 38 | return name; 39 | } 40 | } 41 | }); 42 | -------------------------------------------------------------------------------- /modules/shadow.mjs: -------------------------------------------------------------------------------- 1 | 2 | import {once, abstract, html, htmlFragment, cssCache} from "./utils.mjs"; 3 | 4 | export const shadowOptions = Symbol.for("wc-helpers/shadow-options"); 5 | export const shadowHTML = Symbol.for("wc-helpers/shadow-html"); 6 | export const shadowCSS = Symbol.for("wc-helpers/shadow-css"); 7 | export const shadowElements = Symbol.for("wc-helpers/shadow-elements"); 8 | export const shadowRoot = Symbol.for("wc-helpers/shadow-root"); 9 | 10 | export const Shadow = once(Base => class _Shadow extends Base { 11 | static [abstract]; 12 | static [shadowHTML] = html``; 13 | static [shadowCSS] = []; 14 | 15 | constructor(...args) { 16 | super(...args); 17 | const shadow = this[shadowRoot] = this.attachShadow({ mode: "open", ...new.target[shadowOptions] }); 18 | shadow.appendChild(htmlFragment(new.target[shadowHTML])); 19 | 20 | for (const css of new.target[shadowCSS]) 21 | shadow.adoptedStyleSheets.push(cssCache(css)); 22 | 23 | for (const elem of shadow.querySelectorAll('[id]')) 24 | this[shadowElements][elem.id] = elem; 25 | } 26 | 27 | [shadowElements] = Object.create(null); 28 | }); 29 | -------------------------------------------------------------------------------- /modules/state.mjs: -------------------------------------------------------------------------------- 1 | 2 | import {once, abstract} from "./utils.mjs"; 3 | 4 | export class State { 5 | constructor(value, name) { 6 | this.value = value; 7 | this.name = name ?? ""; 8 | } 9 | 10 | set(value) { 11 | const listeners = this.#listeners; 12 | const old = this.value; 13 | this.value = value; 14 | for (let k = 0, len = listeners.length; k < len; k++) 15 | listeners[k].call(this, value, old); 16 | } 17 | 18 | cset(value) { 19 | return (value !== this.value) ? (this.set(value), true) : false; 20 | } 21 | 22 | listen(func) { 23 | this.#listeners.push(func); 24 | func.call(this, this.value); 25 | } 26 | 27 | listenNoInit(func) { 28 | this.#listeners.push(func); 29 | } 30 | 31 | unlisten(func) { 32 | this.#listeners = this.#listeners.filter(v => func !== v); 33 | } 34 | 35 | unlistenAll() { 36 | this.#listeners = []; 37 | } 38 | 39 | static listen(...args) { 40 | const len = args.length - 1; 41 | const func = args[len]; 42 | args[0].listen(func); 43 | for (let k = 1; k < len; k++) 44 | args[k].listenNoInit(func); 45 | } 46 | 47 | static listenNoInit(...args) { 48 | const len = args.length - 1; 49 | const func = args[len]; 50 | for (let k = 0; k < len; k++) 51 | args[k].listenNoInit(func); 52 | } 53 | 54 | static listenInitAll(...args) { 55 | const len = args.length - 1; 56 | const func = args[len]; 57 | for (let k = 0; k < len; k++) 58 | args[k].listen(func); 59 | } 60 | 61 | static unlisten(func, ...args) { 62 | for (let k = 0; k < args.length; k++) 63 | args[k].unlisten(func); 64 | } 65 | 66 | static unlistenAll(...args) { 67 | for (let k = 0; k < args.length; k++) 68 | args[k].unlistenAll(); 69 | } 70 | 71 | #listeners = []; 72 | } 73 | 74 | export const attributeState = Symbol.for("wc-helpers/attribute-state"); 75 | 76 | export const Attribute = once(Base => class _Attribute extends Base { 77 | static [abstract]; 78 | 79 | [attributeState] = Object.create(null); 80 | 81 | constructor(...args) { 82 | super(...args); 83 | for (const name of new.target.observedAttributes || []) { 84 | const state = this[attributeState][name] = new State(null, name); 85 | } 86 | } 87 | 88 | attributeChangedCallback(name, old, value) { 89 | const state = this[attributeState][name]; 90 | if (state.value !== old) 91 | console.warn(`mismatched old value of '${name}' on attributeChangedCallback`); 92 | state.set(value); 93 | } 94 | }); 95 | -------------------------------------------------------------------------------- /modules/utils.mjs: -------------------------------------------------------------------------------- 1 | 2 | export const abstract = Symbol.for("wc-helpers/abstract"); 3 | 4 | export const once = (def) => { 5 | const map = new WeakMap(); 6 | return (key) => { 7 | let val = map.get(key); 8 | if (!val && !map.has(key)) 9 | map.set(key, val = def(key)); 10 | return val; 11 | }; 12 | }; 13 | 14 | export const chain = (...args) => { 15 | const len = args.length; 16 | let val = args[len - 1]; 17 | for (let k = len - 2; k >= 0; k--) 18 | val = args[k](val); 19 | return val; 20 | }; 21 | 22 | const htmlInternalTag = Symbol.for("wc-helpers/html-tag"); 23 | let setHTMLInternal; 24 | if (globalThis.trustedTypes) { 25 | const policy = trustedTypes.createPolicy("wc-helpers", { createHTML: s => s }); 26 | setHTMLInternal = str => { const r = policy.createHTML(str); r[htmlInternalTag] = true; return r; }; 27 | } else { 28 | setHTMLInternal = str => ({str, toString(){ return this.str; }, [htmlInternalTag]: true }); 29 | } 30 | 31 | const htmlRegexpInternal = /[&<=>'"]/g; 32 | const htmlReplaceInternal = t => `&#${t.charCodeAt(0)};`; 33 | export const html = (strings, ...args) => { 34 | for (let k = 0, len = args.length; k < len; k++) { 35 | if (args[k]?.[htmlInternalTag]) 36 | continue; 37 | 38 | if (args[k] instanceof Array) 39 | args[k] = args[k].map(v => v?.[htmlInternalTag] ? v : String(v).replace(htmlRegexpInternal, htmlReplaceInternal)).join(""); 40 | else 41 | args[k] = String(args[k]).replace(htmlRegexpInternal, htmlReplaceInternal); 42 | } 43 | return setHTMLInternal(String.raw({raw: strings}, ...args)); 44 | }; 45 | export const htmlUnsafeString = (str) => setHTMLInternal(String(str)); 46 | 47 | const cssInternalTag = Symbol.for("wc-helpers/css-tag"); 48 | class CSSLiteralInternal { 49 | constructor(str) { this.str = str; } 50 | toString() { return this.str; } 51 | [cssInternalTag] = true; 52 | } 53 | 54 | export const css = (strings, ...args) => new CSSLiteralInternal(String.raw({raw: strings}, ...args)); 55 | 56 | export const cssDisplayBlock = css`:host { display: block; }`; 57 | export const cssDisplayInline = css`:host { display: inline; }`; 58 | export const cssDisplayInlineBlock = css`:host { display: inline-block; }`; 59 | export const cssDisplayNone = css`:host { display: none; }`; 60 | export const cssDisplayContents = css`:host { display: contents; }`; 61 | 62 | const htmlCacheInternal = once((src) => { 63 | if (!src[htmlInternalTag]) 64 | throw Error("invalid html type"); 65 | 66 | const dst = document.createElement("template"); 67 | dst.innerHTML = src; 68 | return dst; 69 | }); 70 | 71 | const cssCacheInternal = once((src) => { 72 | if (!src[cssInternalTag]) 73 | throw Error("invalid css type"); 74 | 75 | const dst = new CSSStyleSheet; 76 | dst.replaceSync(src); 77 | return dst; 78 | }); 79 | 80 | export const htmlCache = (src) => src instanceof HTMLTemplateElement ? src : htmlCacheInternal(src); 81 | export const cssCache = (src) => src instanceof CSSStyleSheet ? src : cssCacheInternal(src); 82 | export const htmlFragment = (src) => document.importNode(htmlCache(src).content, true); 83 | 84 | const globalCSS = Symbol.for("wc-helpers/global-css"); 85 | 86 | export function getGlobalCSS() { 87 | return globalThis[globalCSS] ?? updateGlobalCSS(); 88 | } 89 | 90 | export function updateGlobalCSS() { 91 | if (!globalThis[globalCSS]) 92 | globalThis[globalCSS] = new CSSStyleSheet(); 93 | 94 | const rules = []; 95 | const css = document.styleSheets; 96 | 97 | for (let k = 0, len = css.length; k < len; k++) { 98 | try { 99 | if (!css[k].ownerNode?.hasAttribute("data-wc-global-css")) 100 | continue; 101 | 102 | const nested = []; 103 | cssAppendRules(css[k], nested); 104 | let str = nested.join("\n"); 105 | 106 | if(css[k].ownerNode.media) 107 | str = `@media ${css[k].ownerNode.media} {\n${str}\n}`; 108 | 109 | rules.push(str); 110 | } catch (e) { 111 | console.error(e); 112 | } 113 | } 114 | 115 | globalThis[globalCSS].replaceSync(rules.join("\n")); 116 | return globalThis[globalCSS]; 117 | } 118 | 119 | function cssAppendRules(css, rules) { 120 | if (!css?.cssRules) 121 | return; 122 | 123 | for (const rule of css.cssRules) { 124 | if (!(rule instanceof CSSImportRule)) { 125 | rules.push(rule.cssText); 126 | continue; 127 | } 128 | 129 | const nested = []; 130 | cssAppendRules(rule.styleSheet, nested); 131 | let str = nested.join("\n"); 132 | 133 | if (rule.media.mediaText) 134 | str = `@media ${rule.media.mediaText} {\n${str}\n}`; 135 | 136 | if (rule.layerName != null) 137 | str = `@layer ${rule.layerName} {\n${str}\n}`; 138 | 139 | rules.push(str); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wc-helpers", 3 | "version": "2.0.1", 4 | "description": "helpers for web components", 5 | "main": "wc-helpers-main.mjs", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "build": "rollup -c", 9 | "prepare": "npm run build" 10 | }, 11 | "keywords": [ 12 | "custom-elements", 13 | "web-components" 14 | ], 15 | "author": "Muhammad Faiz ", 16 | "license": "MIT", 17 | "devDependencies": { 18 | "@rollup/plugin-terser": "^0.4.4", 19 | "rollup": "^4.18.1" 20 | }, 21 | "repository": "github:mfcc64/wc-helpers", 22 | "homepage": "https://github.com/mfcc64/wc-helpers#readme", 23 | "bugs": "https://github.com/mfcc64/wc-helpers/issues" 24 | } 25 | -------------------------------------------------------------------------------- /rollup.config.mjs: -------------------------------------------------------------------------------- 1 | 2 | import terser from "@rollup/plugin-terser"; 3 | 4 | const config = { 5 | input: "wc-helpers-main.mjs", 6 | output: { 7 | file: "wc-helpers-bundle.min.mjs", 8 | format: "es", 9 | sourcemap: true, 10 | sourcemapExcludeSources: true, 11 | plugins: [terser()] 12 | } 13 | }; 14 | 15 | export default config; 16 | -------------------------------------------------------------------------------- /wc-helpers-main.mjs: -------------------------------------------------------------------------------- 1 | 2 | import {once, chain} from "./modules/utils.mjs"; 3 | import {Anonymous} from "./modules/anonymous.mjs"; 4 | import {Attribute} from "./modules/state.mjs"; 5 | import {Shadow} from "./modules/shadow.mjs"; 6 | 7 | export const version = "2.0.1"; 8 | export const WCHelpers = once(Base => chain(Anonymous, Attribute, Shadow, Base)); 9 | 10 | export * from "./modules/utils.mjs"; 11 | export * from "./modules/anonymous.mjs"; 12 | export * from "./modules/state.mjs"; 13 | export * from "./modules/shadow.mjs"; 14 | -------------------------------------------------------------------------------- /wc-helpers.mjs: -------------------------------------------------------------------------------- 1 | 2 | export * from "../wc-helpers@2.0.1/wc-helpers-bundle.min.mjs"; 3 | --------------------------------------------------------------------------------