├── .editorconfig ├── .gitignore ├── .prettierignore ├── .prettierrc ├── build.config.js ├── license ├── package.json ├── readme.md ├── src ├── Element.js ├── component.js ├── css.js ├── customEvent.js ├── event.js ├── index.js ├── property.js ├── query.js ├── queryAll.js ├── styles.js ├── test │ ├── all-query │ │ ├── all-query.js │ │ └── index.js │ ├── app-main │ │ ├── app-main.css.js │ │ ├── app-main.js │ │ └── index.js │ ├── condition-example │ │ ├── condition-example.js │ │ └── index.js │ ├── cool-property │ │ ├── cool-property.js │ │ └── index.js │ ├── easy-event │ │ ├── easy-event.js │ │ └── index.js │ ├── element-example │ │ ├── element-example.js │ │ └── index.js │ ├── global-style.css.js │ ├── hey-internet │ │ ├── hey-internet.js │ │ └── index.js │ ├── index.js │ ├── loop-example │ │ ├── index.js │ │ └── loop-example.js │ ├── other-component │ │ ├── index.js │ │ └── other-component.js │ ├── shared-style │ │ ├── index.js │ │ ├── shared-style.css.js │ │ └── shared-style.js │ ├── simple-query │ │ ├── index.js │ │ └── simple-query.js │ ├── slot-example │ │ ├── index.js │ │ └── slot-example.js │ └── some-styles │ │ ├── index.js │ │ ├── some-styles.css.js │ │ └── some-styles.js └── utils │ ├── convertAttribute.js │ ├── index.js │ ├── kebabCase.js │ ├── methodName.js │ ├── prototypeMethod.js │ └── toObject.js └── test ├── favicon.ico ├── images ├── apple-touch-icon.png ├── favicon.svg ├── google-touch-icon.png └── mask-icon.svg ├── index.html └── manifest.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | indent_size = 4 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.yml] 12 | indent_style = space 13 | indent_size = 2 14 | 15 | [*.md] 16 | indent_style = space 17 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /index.js 2 | /index.js.map 3 | /test/index.js 4 | /test/index.js.map 5 | node_modules 6 | npm-debug.log 7 | package-lock.json 8 | yarn.lock 9 | .DS_Store 10 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | *.html 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "none", 4 | "arrowParens": "avoid", 5 | "printWidth": 90 6 | } 7 | -------------------------------------------------------------------------------- /build.config.js: -------------------------------------------------------------------------------- 1 | import build from '@nativeweb/build'; 2 | 3 | build( 4 | { 5 | entryPoints: ['src/index.js', 'src/test/index.js'], 6 | outdir: '.', 7 | outfile: '', 8 | target: 'esnext' 9 | }, 10 | { 11 | serve: { 12 | root: 'test' 13 | } 14 | } 15 | ); 16 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | ISC License 2 | 3 | Copyright (c) Antoine Boulanger (https://github.com/ABXlink) 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nativeweb", 3 | "version": "2.1.0", 4 | "description": "🤳 Tiny library for simple web components. [1kB]", 5 | "homepage": "nativeweb.dev", 6 | "repository": "nativew/nativeweb", 7 | "author": "Antoine Boulanger (https://github.com/antoineboulanger)", 8 | "license": "ISC", 9 | "exports": "./index.js", 10 | "main": "index.js", 11 | "module": "index.js", 12 | "type": "module", 13 | "scripts": { 14 | "format": "prettier --write --ignore-unknown '**/*'", 15 | "start": "node build.config.js -w", 16 | "build": "npm run format && node build.config.js" 17 | }, 18 | "devDependencies": { 19 | "@nativeweb/build": "^1.3.1", 20 | "prettier": "^2.2.1" 21 | }, 22 | "files": [ 23 | "index.js" 24 | ], 25 | "keywords": [ 26 | "native web", 27 | "web components", 28 | "components", 29 | "library", 30 | "framework", 31 | "decorators", 32 | "js", 33 | "javascript", 34 | "starter", 35 | "boilerplate" 36 | ] 37 | } 38 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 |
2 |
3 |

4 | 5 | Native Web 6 | 7 |

8 |

9 | Tiny library for simple web components.
10 | 1kB 11 |

12 |
13 |
14 | 15 |
16 | 17 | ```js 18 | import { component, property, Element } from 'nativeweb'; 19 | 20 | @component('hey-internet') 21 | class Component extends Element { 22 | @property() emoji; 23 | 24 | render() { 25 | return ` 26 |

Hey Internet ${this.emoji}

27 | `; 28 | } 29 | } 30 | ``` 31 | 32 | ```html 33 | 34 | ``` 35 | 36 |
37 | 38 | ### Native web components 39 | 40 | ### Encapsulated styles and scripts 41 | 42 | ### Simplified with decorators 43 | 44 | ### Tiny footprint 45 | 46 |
47 | 48 | ### One command to [start](https://github.com/nativew/start) 49 | 50 | ```zsh 51 | npm init nativeweb 52 | ``` 53 | 54 |
55 | 56 | ### Or add to your existing project 57 | 58 | ```zsh 59 | npm install nativeweb 60 | ``` 61 | 62 |
63 | 64 | ### Decorators 65 | 66 | [`@component`](#component) 67 | 68 | [`@property`](#property) 69 | 70 | [`@event`](#event) 71 | 72 | [`@customEvent`](#customevent) 73 | 74 | [`@query`](#query) 75 | 76 | [`@queryAll`](#queryall) 77 | 78 |
79 | 80 | ### `@component` 81 | 82 | Define a custom element and add styles. From an external file or the same file. `styles` can be an `array` of styles. 83 | 84 | ```js 85 | import { component, Element } from 'nativeweb'; 86 | import styles from './hey-styles.css.js'; 87 | 88 | @component('some-styles', styles) 89 | class Component extends Element { 90 | render() { 91 | return ` 92 |

Some Styles

93 | `; 94 | } 95 | } 96 | ``` 97 | 98 | ```js 99 | import { css } from 'nativeweb'; 100 | 101 | export default css` 102 | h1 { 103 | color: orange; 104 | } 105 | `; 106 | ``` 107 | 108 | ```html 109 | 110 | ``` 111 | 112 |
113 | 114 | ### `@property` 115 | 116 | Get an attribute converted to the specified type or define a property with an optional default value. 117 | `String`, `Boolean`, `Number`, `Array` or `Object`. 118 | 119 | ```js 120 | import { component, property, Element } from 'nativeweb'; 121 | 122 | @component('cool-property') 123 | class Component extends Element { 124 | @property() cool = 'Cool Prop'; 125 | @property(String) title = 'Default Title'; 126 | @property(Number) multiplier; 127 | 128 | render() { 129 | return ` 130 |

${this.title}

131 |

${this.cool} ➡️ ${2 * this.multiplier}

132 | `; 133 | } 134 | } 135 | ``` 136 | 137 | ```html 138 | 139 | ``` 140 | 141 |
142 | 143 | ### `@event` 144 | 145 | Add an event listener to a component, a child element named `@name` or an external component event. 146 | 147 | ```js 148 | import { component, event, Element } from 'nativeweb'; 149 | 150 | @component('easy-event') 151 | class Component extends Element { 152 | @event() mouseenter = this.onHover(); 153 | @event() click = { 154 | '@title': this.onClick(), 155 | '@button': this.onClick() 156 | }; 157 | @event() ready = { 158 | 'other-component': this.onReady() 159 | }; 160 | 161 | onHover() { 162 | console.log('Hover Component'); 163 | } 164 | onClick(e) { 165 | console.log(e.currentTarget); 166 | } 167 | onReady({ detail }) { 168 | console.log(detail); 169 | } 170 | 171 | render() { 172 | return ` 173 |

Easy Event

174 | 175 | `; 176 | } 177 | } 178 | ``` 179 | 180 | ```html 181 | 182 | ``` 183 | 184 |
185 | 186 | ### `@customEvent` 187 | 188 | Create a custom global event, namespaced with the component name. Ready to be dispatched. The listener is in the [component above](#event). 189 | 190 | ```js 191 | import { component, customEvent, Element } from 'nativeweb'; 192 | 193 | @component('other-component') 194 | class Component extends Element { 195 | @customEvent() ready = 'Ready 🚀'; 196 | 197 | connected() { 198 | dispatchEvent(this.ready); 199 | } 200 | 201 | render() { 202 | return ` 203 |

Other Component

204 | `; 205 | } 206 | } 207 | ``` 208 | 209 | ```html 210 | 211 | ``` 212 | 213 |
214 | 215 | ### `@query` 216 | 217 | Query selector an `@name` child element. 218 | 219 | ```js 220 | import { component, query, Element } from 'nativeweb'; 221 | 222 | @component('simple-query') 223 | class Component extends Element { 224 | @query() title; 225 | 226 | connected() { 227 | this.title.innerText = 'Better Title 💯'; 228 | } 229 | 230 | render() { 231 | return ` 232 |

Good Title

233 | `; 234 | } 235 | } 236 | ``` 237 | 238 | ```html 239 | 240 | ``` 241 | 242 |
243 | 244 | ### `@queryAll` 245 | 246 | Query selector all `@name` child elements. 247 | 248 | ```js 249 | import { component, queryAll, Element } from 'nativeweb'; 250 | 251 | @component('all-query') 252 | class Component extends Element { 253 | @queryAll() title; 254 | 255 | connected() { 256 | this.title.forEach(el => (el.style.color = 'lightgreen')); 257 | } 258 | 259 | render() { 260 | return ` 261 |

One Title

262 |

Other Title

263 | `; 264 | } 265 | } 266 | ``` 267 | 268 | ```html 269 | 270 | ``` 271 | 272 |
273 | 274 | ### Lifecycle 275 | 276 | `render()`   →   Renders the component. 277 | 278 | `connected()`   →   Called when the component is inserted in the DOM. 279 | 280 | `disconnected()`   →   Called when the component is removed from the DOM. 281 | 282 | `adopted()`   →   Called when the component is moved to a new document. 283 | 284 | `attributeChanged()`   →   Called when an observed attribute changes. 285 | 286 |
287 | 288 | ### Methods 289 | 290 | `this.update()`   →   Rerenders the component. 291 | 292 |
293 | 294 | ### Properties 295 | 296 | `this.properties`   →   Get all attributes. 297 | 298 |
299 | 300 | ### Tips 301 | 302 | [Shared style](#shared-style) 303 | 304 | [Composition](#composition) 305 | 306 | [Conditional](#conditional) 307 | 308 | [Loop](#loop) 309 | 310 | [Variable element](#variable-element) 311 | 312 |
313 | 314 | ### Shared style 315 | 316 | Include global styles in a component. 317 | 318 | ```js 319 | import { css } from 'nativeweb'; 320 | import global from '../global-style.css.js'; 321 | 322 | export default [ 323 | global, 324 | css` 325 | h1 { 326 | color: orange; 327 | } 328 | ` 329 | ]; 330 | ``` 331 | 332 |
333 | 334 | ### Composition 335 | 336 | Component composition with default slot and named slot. 337 | 338 | ```js 339 | import { component, Element } from 'nativeweb'; 340 | 341 | @component('slot-example') 342 | class Component extends Element { 343 | render() { 344 | return ` 345 |
346 |

347 | 348 |
349 | `; 350 | } 351 | } 352 | ``` 353 | 354 | ```html 355 | 356 | Named slot 357 |

Default slot

358 |
359 | ``` 360 | 361 |
362 | 363 | ### Conditional 364 | 365 | Conditional rendering in vanilla JS. 366 | 367 | ```js 368 | import { component, property, Element } from 'nativeweb'; 369 | 370 | @component('condition-example') 371 | class Component extends Element { 372 | @property() isGood = false; 373 | 374 | render() { 375 | return ` 376 | ${this.isGood ? `

Good

` : ``} 377 | `; 378 | } 379 | } 380 | ``` 381 | 382 |
383 | 384 | ### Loop 385 | 386 | Render loop in vanilla JS. 387 | 388 | ```js 389 | import { component, property, Element } from 'nativeweb'; 390 | 391 | @component('loop-example') 392 | class Component extends Element { 393 | @property() emojis = ['🤳', '🧨', '🧱']; 394 | 395 | render() { 396 | return ` 397 | ${this.emojis.map(item => `${item}`).join('')} 398 | `; 399 | } 400 | } 401 | ``` 402 | 403 |
404 | 405 | ### Variable element 406 | 407 | Render an element from a property. 408 | 409 | ```js 410 | import { component, property, Element } from 'nativeweb'; 411 | 412 | @component('element-example') 413 | class Component extends Element { 414 | @property() as = 'p'; 415 | 416 | render() { 417 | return ` 418 | <${this.as}>Heading 1 419 | `; 420 | } 421 | } 422 | ``` 423 | 424 | ```html 425 | 426 | ``` 427 | 428 |
429 | 430 |
431 |
432 |

433 | 434 | Native Web 435 | 436 |

437 |
438 | 439 |
440 | -------------------------------------------------------------------------------- /src/Element.js: -------------------------------------------------------------------------------- 1 | export class Element extends HTMLElement { 2 | constructor() { 3 | super(); 4 | 5 | this.attachShadow({ mode: 'open' }); 6 | } 7 | 8 | _setStyles(styles) { 9 | styles = [].concat(styles); 10 | 11 | this.shadowRoot.adoptedStyleSheets 12 | ? (this.shadowRoot.adoptedStyleSheets = styles) 13 | : (this._css = styles.join('')); 14 | } 15 | 16 | _insertRender() { 17 | const css = this._css ? `` : ''; 18 | 19 | this.shadowRoot.innerHTML = css + this.render(); 20 | } 21 | 22 | connectedCallback() { 23 | this._insertRender(); 24 | this.connected(); 25 | } 26 | 27 | disconnectedCallback() { 28 | this.disconnected(); 29 | } 30 | 31 | adoptedCallback() { 32 | this.adopted(); 33 | } 34 | 35 | attributeChangedCallback(name, oldValue, newValue) { 36 | this.attributeChanged(name, oldValue, newValue); 37 | } 38 | 39 | update() { 40 | this._insertRender(); 41 | } 42 | 43 | render() {} 44 | 45 | connected() {} 46 | 47 | disconnected() {} 48 | 49 | adopted() {} 50 | 51 | attributeChanged(name, oldValue, newValue) {} 52 | 53 | get properties() { 54 | return Object.values(this.attributes) 55 | .map(a => `${a.name}="${a.value}"`) 56 | .join(' '); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/component.js: -------------------------------------------------------------------------------- 1 | import { styles } from './styles'; 2 | 3 | export const component = (name, css) => descriptor => { 4 | const { kind, elements } = descriptor; 5 | 6 | css && elements.push(styles(css)); 7 | 8 | return { 9 | kind, 10 | elements, 11 | finisher(cla) { 12 | cla.prototype._name = name; 13 | window.customElements.define(name, cla); 14 | } 15 | }; 16 | }; 17 | -------------------------------------------------------------------------------- /src/css.js: -------------------------------------------------------------------------------- 1 | export const css = styles => { 2 | try { 3 | const sheet = new CSSStyleSheet(); 4 | 5 | sheet.replaceSync(styles); 6 | 7 | return sheet; 8 | } catch { 9 | return styles[0]; 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /src/customEvent.js: -------------------------------------------------------------------------------- 1 | import { prototypeMethod } from './utils'; 2 | 3 | export const customEvent = () => ({ key, initializer }) => { 4 | let value = null; 5 | 6 | const descriptor = { 7 | get() { 8 | const detail = value ? value : initializer && initializer(); 9 | 10 | return new CustomEvent(`${this._name}.${key}`, { detail }); 11 | }, 12 | set(newValue) { 13 | value = newValue; 14 | } 15 | }; 16 | 17 | return prototypeMethod(key, descriptor); 18 | }; 19 | -------------------------------------------------------------------------------- /src/event.js: -------------------------------------------------------------------------------- 1 | import { methodName, toObject } from './utils'; 2 | 3 | export const event = () => descriptor => { 4 | const { key, initializer } = descriptor; 5 | const string = initializer.toString(); 6 | const value = string.slice(string.indexOf('return') + 6, string.lastIndexOf(';')); 7 | const externalEvents = []; 8 | 9 | let hasChild = false; 10 | let object; 11 | let self; 12 | 13 | const callFunction = (e, func) => { 14 | const name = methodName(func); 15 | 16 | return self[name](e); 17 | }; 18 | 19 | const callValue = e => callFunction(e, value); 20 | 21 | const callChild = (e, name, value) => { 22 | const func = object[name]; 23 | 24 | if (!func) return; 25 | 26 | Object.defineProperty(e, 'currentTarget', { value }); 27 | callFunction(e, func); 28 | }; 29 | 30 | const callExternal = e => { 31 | const event = e.type.split('.'); 32 | const func = object[event[0]]; 33 | 34 | callFunction(e, func); 35 | }; 36 | 37 | const setNormalEvent = () => self.addEventListener(key, callValue); 38 | 39 | const setChildEvent = () => self.shadowRoot.addEventListener(key, checkTarget); 40 | 41 | const setExternalEvent = (name, remove) => { 42 | const eKey = `${name}.${key}`; 43 | 44 | remove 45 | ? window.removeEventListener(eKey, callExternal) 46 | : window.addEventListener(eKey, callExternal); 47 | }; 48 | 49 | const removeExternalEvents = () => 50 | externalEvents.forEach(e => setExternalEvent(e, true)); 51 | 52 | const checkTarget = e => { 53 | const targets = e.composedPath(); 54 | 55 | for (const el of targets) { 56 | if (el == self.shadowRoot) break; 57 | 58 | if (el.attributes?.length) { 59 | const attrs = Array.from(el.attributes).filter(({ name }) => 60 | name.startsWith('@') 61 | ); 62 | 63 | attrs.forEach(attr => callChild(e, attr.name, el)); 64 | } 65 | } 66 | }; 67 | 68 | const checkType = name => { 69 | if (name.startsWith('@')) return (hasChild = true); 70 | 71 | externalEvents.push(name); 72 | setExternalEvent(name); 73 | }; 74 | 75 | const checkChildEvents = () => { 76 | object = toObject(value); 77 | 78 | Object.keys(object).forEach(k => checkType(k)); 79 | hasChild && setChildEvent(); 80 | }; 81 | 82 | return { 83 | ...descriptor, 84 | initializer() { 85 | self = this; 86 | 87 | if (value.includes(':')) { 88 | checkChildEvents(); 89 | 90 | return; 91 | } 92 | 93 | setNormalEvent(); 94 | }, 95 | finisher(cla) { 96 | const disconnected = cla.prototype.disconnectedCallback; 97 | 98 | cla.prototype.disconnectedCallback = function () { 99 | disconnected.call(this); 100 | removeExternalEvents(); 101 | }; 102 | } 103 | }; 104 | }; 105 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | export { component } from './component.js'; 2 | export { css } from './css.js'; 3 | export { customEvent } from './customEvent.js'; 4 | export { Element } from './Element.js'; 5 | export { event } from './event.js'; 6 | export { property } from './property.js'; 7 | export { query } from './query.js'; 8 | export { queryAll } from './queryAll.js'; 9 | export { styles } from './styles.js'; 10 | -------------------------------------------------------------------------------- /src/property.js: -------------------------------------------------------------------------------- 1 | import { convertAttribute, kebabCase, prototypeMethod } from './utils'; 2 | 3 | export const property = type => ({ key, initializer }) => { 4 | const descriptor = { 5 | get() { 6 | const attr = this.getAttribute(kebabCase(key)); 7 | 8 | return attr || attr == '' 9 | ? convertAttribute(attr, type) 10 | : initializer && initializer(); 11 | }, 12 | set(val) { 13 | initializer = () => val; 14 | } 15 | }; 16 | 17 | return prototypeMethod(key, descriptor); 18 | }; 19 | -------------------------------------------------------------------------------- /src/query.js: -------------------------------------------------------------------------------- 1 | import { kebabCase, prototypeMethod } from './utils'; 2 | 3 | export const query = () => ({ key }) => { 4 | const descriptor = { 5 | get() { 6 | return this.shadowRoot.querySelector(`[\\@${kebabCase(key)}]`); 7 | } 8 | }; 9 | 10 | return prototypeMethod(key, descriptor); 11 | }; 12 | -------------------------------------------------------------------------------- /src/queryAll.js: -------------------------------------------------------------------------------- 1 | import { kebabCase, prototypeMethod } from './utils'; 2 | 3 | export const queryAll = () => ({ key }) => { 4 | const descriptor = { 5 | get() { 6 | return this.shadowRoot.querySelectorAll(`[\\@${kebabCase(key)}]`); 7 | } 8 | }; 9 | 10 | return prototypeMethod(key, descriptor); 11 | }; 12 | -------------------------------------------------------------------------------- /src/styles.js: -------------------------------------------------------------------------------- 1 | export const styles = css => ({ 2 | kind: 'field', 3 | placement: 'own', 4 | key: '_styles', 5 | descriptor: {}, 6 | initializer() { 7 | this._setStyles(css); 8 | } 9 | }); 10 | -------------------------------------------------------------------------------- /src/test/all-query/all-query.js: -------------------------------------------------------------------------------- 1 | import { component, queryAll, Element } from '../..'; 2 | 3 | @component('all-query') 4 | class Component extends Element { 5 | @queryAll() title; 6 | 7 | connected() { 8 | this.title.forEach(el => (el.style.color = 'lightgreen')); 9 | } 10 | 11 | render() { 12 | return ` 13 |

One Title

14 |

Other Title

15 | `; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/test/all-query/index.js: -------------------------------------------------------------------------------- 1 | export * from './all-query'; 2 | -------------------------------------------------------------------------------- /src/test/app-main/app-main.css.js: -------------------------------------------------------------------------------- 1 | import { css } from '../..'; 2 | 3 | export default css` 4 | :host { 5 | padding: 2rem 4rem; 6 | display: block; 7 | font-family: sans-serif; 8 | } 9 | `; 10 | -------------------------------------------------------------------------------- /src/test/app-main/app-main.js: -------------------------------------------------------------------------------- 1 | import { component, Element } from '../..'; 2 | import '../hey-internet'; 3 | import '../some-styles'; 4 | import '../cool-property'; 5 | import '../easy-event'; 6 | import '../other-component'; 7 | import '../simple-query'; 8 | import '../all-query'; 9 | import '../shared-style'; 10 | import '../slot-example'; 11 | import '../condition-example'; 12 | import '../loop-example'; 13 | import '../element-example'; 14 | import styles from '.'; 15 | 16 | @component('app-main', styles) 17 | class Component extends Element { 18 | render() { 19 | return ` 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | Named slot 30 |

Default slot

31 |
32 | 33 | 34 | 35 | `; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/test/app-main/index.js: -------------------------------------------------------------------------------- 1 | export { default } from './app-main.css'; 2 | export * from './app-main'; 3 | -------------------------------------------------------------------------------- /src/test/condition-example/condition-example.js: -------------------------------------------------------------------------------- 1 | import { component, property, Element } from '../..'; 2 | 3 | @component('condition-example') 4 | class Component extends Element { 5 | @property() isGood = false; 6 | 7 | render() { 8 | return ` 9 | ${this.isGood ? `

Good

` : ``} 10 | `; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/test/condition-example/index.js: -------------------------------------------------------------------------------- 1 | export * from './condition-example'; 2 | -------------------------------------------------------------------------------- /src/test/cool-property/cool-property.js: -------------------------------------------------------------------------------- 1 | import { component, property, Element } from '../..'; 2 | 3 | @component('cool-property') 4 | class Component extends Element { 5 | @property() cool = 'Cool Prop'; 6 | @property(String) title = 'Default Title'; 7 | @property(Number) multiplier; 8 | 9 | render() { 10 | return ` 11 |

${this.title}

12 |

${this.cool} ➡️ ${2 * this.multiplier}

13 | `; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/test/cool-property/index.js: -------------------------------------------------------------------------------- 1 | export * from './cool-property'; 2 | -------------------------------------------------------------------------------- /src/test/easy-event/easy-event.js: -------------------------------------------------------------------------------- 1 | import { component, event, Element } from '../..'; 2 | 3 | @component('easy-event') 4 | class Component extends Element { 5 | @event() mouseenter = this.onHover(); 6 | @event() click = { 7 | '@title': this.onClick(), 8 | '@button': this.onClick() 9 | }; 10 | @event() ready = { 11 | 'other-component': this.onReady() 12 | }; 13 | 14 | onHover() { 15 | console.log('Hover Component'); 16 | } 17 | onClick(e) { 18 | console.log(e.currentTarget); 19 | } 20 | onReady({ detail }) { 21 | console.log(detail); 22 | } 23 | 24 | render() { 25 | return ` 26 |

Easy Event

27 | 28 | `; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/test/easy-event/index.js: -------------------------------------------------------------------------------- 1 | export * from './easy-event'; 2 | -------------------------------------------------------------------------------- /src/test/element-example/element-example.js: -------------------------------------------------------------------------------- 1 | import { component, property, Element } from '../..'; 2 | 3 | @component('element-example') 4 | class Component extends Element { 5 | @property() as = 'p'; 6 | 7 | render() { 8 | return ` 9 | <${this.as}>Heading 1 10 | `; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/test/element-example/index.js: -------------------------------------------------------------------------------- 1 | export * from './element-example.js'; 2 | -------------------------------------------------------------------------------- /src/test/global-style.css.js: -------------------------------------------------------------------------------- 1 | import { css } from '..'; 2 | 3 | export default css` 4 | *, 5 | *::before, 6 | *::after, 7 | ::slotted(*) { 8 | box-sizing: border-box; 9 | } 10 | `; 11 | -------------------------------------------------------------------------------- /src/test/hey-internet/hey-internet.js: -------------------------------------------------------------------------------- 1 | import { component, property, Element } from '../..'; 2 | 3 | @component('hey-internet') 4 | class Component extends Element { 5 | @property() emoji; 6 | 7 | render() { 8 | return ` 9 |

Hey Internet ${this.emoji}

10 | `; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/test/hey-internet/index.js: -------------------------------------------------------------------------------- 1 | export * from './hey-internet'; 2 | -------------------------------------------------------------------------------- /src/test/index.js: -------------------------------------------------------------------------------- 1 | import './app-main'; 2 | -------------------------------------------------------------------------------- /src/test/loop-example/index.js: -------------------------------------------------------------------------------- 1 | export * from './loop-example'; 2 | -------------------------------------------------------------------------------- /src/test/loop-example/loop-example.js: -------------------------------------------------------------------------------- 1 | import { component, property, Element } from '../..'; 2 | 3 | @component('loop-example') 4 | class Component extends Element { 5 | @property() emojis = ['🤳', '🧨', '🧱']; 6 | 7 | render() { 8 | return ` 9 | ${this.emojis.map(item => `${item}`).join('')} 10 | `; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/test/other-component/index.js: -------------------------------------------------------------------------------- 1 | export * from './other-component'; 2 | -------------------------------------------------------------------------------- /src/test/other-component/other-component.js: -------------------------------------------------------------------------------- 1 | import { component, customEvent, Element } from '../..'; 2 | 3 | @component('other-component') 4 | class Component extends Element { 5 | @customEvent() ready = 'Ready 🚀'; 6 | 7 | connected() { 8 | dispatchEvent(this.ready); 9 | } 10 | 11 | render() { 12 | return ` 13 |

Other Component

14 | `; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/test/shared-style/index.js: -------------------------------------------------------------------------------- 1 | export * from './shared-style'; 2 | -------------------------------------------------------------------------------- /src/test/shared-style/shared-style.css.js: -------------------------------------------------------------------------------- 1 | import { css } from '../..'; 2 | import global from '../global-style.css.js'; 3 | 4 | export default [ 5 | global, 6 | css` 7 | h1 { 8 | color: blue; 9 | } 10 | ` 11 | ]; 12 | -------------------------------------------------------------------------------- /src/test/shared-style/shared-style.js: -------------------------------------------------------------------------------- 1 | import { component, Element } from '../..'; 2 | import styles from './shared-style.css.js'; 3 | 4 | @component('shared-style', styles) 5 | class Component extends Element { 6 | render() { 7 | return ` 8 |

Shared Style

9 | `; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/test/simple-query/index.js: -------------------------------------------------------------------------------- 1 | export * from './simple-query'; 2 | -------------------------------------------------------------------------------- /src/test/simple-query/simple-query.js: -------------------------------------------------------------------------------- 1 | import { component, query, Element } from '../..'; 2 | 3 | @component('simple-query') 4 | class Component extends Element { 5 | @query() title; 6 | 7 | connected() { 8 | this.title.innerText = 'Better Title 💯'; 9 | } 10 | 11 | render() { 12 | return ` 13 |

Good Title

14 | `; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/test/slot-example/index.js: -------------------------------------------------------------------------------- 1 | export * from './slot-example'; 2 | -------------------------------------------------------------------------------- /src/test/slot-example/slot-example.js: -------------------------------------------------------------------------------- 1 | import { component, Element } from '../..'; 2 | 3 | @component('slot-example') 4 | class Component extends Element { 5 | render() { 6 | return ` 7 |
8 |

9 | 10 |
11 | `; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/test/some-styles/index.js: -------------------------------------------------------------------------------- 1 | export * from './some-styles'; 2 | -------------------------------------------------------------------------------- /src/test/some-styles/some-styles.css.js: -------------------------------------------------------------------------------- 1 | import { css } from '../..'; 2 | 3 | export default css` 4 | h1 { 5 | color: orange; 6 | } 7 | `; 8 | -------------------------------------------------------------------------------- /src/test/some-styles/some-styles.js: -------------------------------------------------------------------------------- 1 | import { component, Element } from '../..'; 2 | import styles from './some-styles.css.js'; 3 | 4 | @component('some-styles', styles) 5 | class Component extends Element { 6 | render() { 7 | return ` 8 |

Some Styles

9 | `; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/utils/convertAttribute.js: -------------------------------------------------------------------------------- 1 | export const convertAttribute = (attr, type) => { 2 | switch (type) { 3 | case Boolean: 4 | return attr == 'true' || attr == '' ? true : false; 5 | case Number: 6 | return Number(attr); 7 | case Array: 8 | case Object: 9 | return JSON.parse(attr); 10 | default: 11 | return attr; 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /src/utils/index.js: -------------------------------------------------------------------------------- 1 | export { convertAttribute } from './convertAttribute.js'; 2 | export { kebabCase } from './kebabCase.js'; 3 | export { methodName } from './methodName.js'; 4 | export { prototypeMethod } from './prototypeMethod.js'; 5 | export { toObject } from './toObject.js'; 6 | -------------------------------------------------------------------------------- /src/utils/kebabCase.js: -------------------------------------------------------------------------------- 1 | export const kebabCase = name => 2 | name 3 | .split(/(?=[A-Z])/) 4 | .join('-') 5 | .toLowerCase(); 6 | -------------------------------------------------------------------------------- /src/utils/methodName.js: -------------------------------------------------------------------------------- 1 | export const methodName = method => { 2 | const index = method.indexOf('.'); 3 | const start = index > -1 ? index + 1 : 0; 4 | 5 | return method.slice(start, method.indexOf('(')); 6 | }; 7 | -------------------------------------------------------------------------------- /src/utils/prototypeMethod.js: -------------------------------------------------------------------------------- 1 | export const prototypeMethod = (key, descriptor) => ({ 2 | kind: 'method', 3 | placement: 'prototype', 4 | key, 5 | descriptor: { 6 | enumerable: true, 7 | configurable: true, 8 | ...descriptor 9 | } 10 | }); 11 | -------------------------------------------------------------------------------- /src/utils/toObject.js: -------------------------------------------------------------------------------- 1 | export const toObject = string => { 2 | const arr = string.split('\n').join('').split(' ').join('').slice(1, -1).split(','); 3 | const obj = {}; 4 | 5 | for (let k of arr) { 6 | k = k.split(':'); 7 | 8 | if (k[0].startsWith('"')) k[0] = k[0].slice(1, -1); 9 | 10 | obj[k[0]] = k[1]; 11 | } 12 | 13 | return obj; 14 | }; 15 | -------------------------------------------------------------------------------- /test/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nativew/nativeweb/baaf560715b1f5f30eb415a259432f7e6a18b448/test/favicon.ico -------------------------------------------------------------------------------- /test/images/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nativew/nativeweb/baaf560715b1f5f30eb415a259432f7e6a18b448/test/images/apple-touch-icon.png -------------------------------------------------------------------------------- /test/images/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /test/images/google-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nativew/nativeweb/baaf560715b1f5f30eb415a259432f7e6a18b448/test/images/google-touch-icon.png -------------------------------------------------------------------------------- /test/images/mask-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Native Web 🤳 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /test/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Start", 3 | "short_name": "Start", 4 | "icons": [ 5 | { 6 | "src": "images/google-touch-icon.png", 7 | "sizes": "512x512" 8 | } 9 | ], 10 | "background_color": "#ffffff", 11 | "theme_color": "#ffffff", 12 | "display": "fullscreen" 13 | } 14 | --------------------------------------------------------------------------------