├── .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 |
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 |
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${this.as}>
419 | `;
420 | }
421 | }
422 | ```
423 |
424 | ```html
425 |
426 | ```
427 |
428 |
429 |
430 |
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${this.as}>
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 |
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 |
--------------------------------------------------------------------------------