arrmatura 5.0.1 → 5.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,36 +1,40 @@
1
1
  # Arrmatura
2
2
 
3
- `Arrmatura` is a ultimate `specification-as-a-code` component framework.
3
+ `Arrmatura` is a mimimalistic component framework that designed to be:
4
4
 
5
5
  - declarative and codeless
6
+ - reactive and functional
6
7
  - non-obtrusive, platform-independent
7
- - reactive and functional oriented
8
8
  - easy to learn and get started.
9
9
 
10
10
  # Get Started
11
11
 
12
12
  npm i arrmatura
13
13
 
14
- ```javascript
15
- import { launchWeb } from 'arrmatura';
16
-
17
- const components = `
14
+ ./templates.xml
18
15
 
16
+ ```xml
19
17
  <component id="Application">
20
18
  <Main name="Joe"/>
21
19
  </component>
22
20
 
23
21
  <component id="Main">
24
- <Button title="{:greeting}, {name|upper}">
22
+ <Button title="{:greeting}, {name|upper}!">
25
23
  </component>
26
- `;
24
+ ```
25
+
26
+ index.ts
27
+
28
+ ```javascript
29
+ import { arrmatura } from 'arrmatura';
30
+ import templates from './templates.xml';
27
31
 
28
- launchWeb('<Application />', {
29
- types: [components],
32
+ arrmatura('<Application />', {
33
+ types: [templates],
30
34
  resources: {
31
- greeting: 'Hello!',
35
+ greeting: 'Hello',
32
36
  },
33
- pipes: {
37
+ functions: {
34
38
  upper: (x) => x.toUpperCase(),
35
39
  },
36
40
  });
@@ -44,6 +48,7 @@ launchWeb('<Application />', {
44
48
 
45
49
  # Samples
46
50
 
47
- - [TodoMVC](https://alitskevich.github.io/arrmatura/todo.html)
48
- - [Tasks](https://alitskevich.github.io/arrmatura/task.html)
49
- - [TicTacToe](https://alitskevich.github.io/arrmatura/tictactoe.html)
51
+ - [TodoMVC](https://alitskevich.github.io/arrmatura/todo.html) ([source](https://github.com/alitskevich/arrmatura/blob/master/docs/todo.html))
52
+ - [Tasks list](https://alitskevich.github.io/arrmatura/task.html) ([source](https://github.com/alitskevich/arrmatura/tree/master/app-task))
53
+ - [Game Solver](https://dlitskevich.github.io/solver/) ([source](https://github.com/dlitskevich/solver/tree/master/app))
54
+ - [Input Templates](https://alitskevich.github.io/templain/) ([source](https://github.com/alitskevich/templain/tree/master/app))
@@ -19,7 +19,7 @@
19
19
  <component id="Sidebar">
20
20
  <div class="off-canvas off-canvas-sidebar-show">
21
21
  <a class="off-canvas-toggle btn btn-primary btn-action show-lg" href="#sidebar">
22
- <Icon type="bars" />
22
+ <Icon type="menu" />
23
23
  </a>
24
24
  <div id="sidebar" class="off-canvas-sidebar">
25
25
  <Aside caption="{caption}">
@@ -1,4 +1,4 @@
1
- export const showCounts = (counts: Obj, nicNema = '⭕ Nic nema', neZnojdzeno = '⭕ Ne znojdzena') => {
1
+ export const showCounts = (counts: Obj, nicNema = '⭕', neZnojdzeno = '⭕') => {
2
2
  if (!counts) {
3
3
  return '…';
4
4
  }
@@ -0,0 +1,25 @@
1
+ import { DomElement, DomTextElement } from './elements';
2
+ import { reflow } from './reflow';
3
+
4
+ export class WebPlatform implements IPlatform {
5
+ rootElement: HTMLElement;
6
+
7
+ constructor(root: HTMLElement | string = 'root') {
8
+ if (typeof root === 'string') {
9
+ root = document.getElementById(root) || window.document.body;
10
+ }
11
+ this.rootElement = root || window.document.body;
12
+ }
13
+
14
+ createTextElement(initials: Hash, _: IComponent): IComponentImplementation {
15
+ return new DomTextElement(initials);
16
+ }
17
+
18
+ createElement(tag: string, initials: Hash, owner: IComponent): IComponentImplementation {
19
+ return new DomElement(tag, initials, owner);
20
+ }
21
+
22
+ reflow(root: IComponent) {
23
+ reflow(root, this.rootElement);
24
+ }
25
+ }
@@ -0,0 +1,197 @@
1
+ import { DomNode, IElement } from './type';
2
+
3
+ const camelize = (key: string, sep = '_', jn = ' ') =>
4
+ ('' + key)
5
+ .split(sep)
6
+ .map((s, i) => (i ? s[0].toUpperCase() + s.slice(1) : s))
7
+ .join(jn);
8
+
9
+ const setListener = ($: IElement, key: string, e: DomNode, v: unknown) => {
10
+ const fnKey = key.split(':')[0];
11
+ if (!v) {
12
+ setAttribute($, key, null);
13
+ } else {
14
+ setAttribute($, key, (ev: Event) => {
15
+ $.$attributes[fnKey]({ ...e.$dataset }, ev);
16
+ return false;
17
+ });
18
+ }
19
+ };
20
+ export function setAttribute($: IElement, key: string, value: null | string | Function) {
21
+ if (value != null) {
22
+ if (typeof value === 'function') {
23
+ const fnValue = (...args: unknown[]) => {
24
+ if (!$.isDone) {
25
+ value(...args);
26
+ }
27
+ };
28
+ if (!$.listeners) {
29
+ $.listeners = new Map();
30
+ }
31
+ if (!$.listeners.has(key)) {
32
+ const [akey, ekey = akey] = key.split(':');
33
+ $.element.addEventListener(ekey, fnValue, false);
34
+ $.listeners.set(key, fnValue);
35
+ }
36
+ } else {
37
+ $.element.setAttribute(key, value);
38
+ }
39
+ } else {
40
+ if ($.listeners?.has(key)) {
41
+ const [akey, ekey = akey] = key.split(':');
42
+ $.element.removeEventListener(ekey, $.listeners.get(key));
43
+ $.listeners.delete(key);
44
+ } else {
45
+ $.element.removeAttribute(key);
46
+ }
47
+ }
48
+ }
49
+
50
+ // DOM
51
+ export const ATTR_SETTERS: Hash<($: IElement, e: DomNode, v: unknown) => void> = {
52
+ disabled: (_, e, v) => (e.disabled = v ? true : null),
53
+ class: (_, e, v) => {
54
+ e.className = String(v || '');
55
+ },
56
+ selected: (_, e, v) => (e.selected = v ? true : null),
57
+ value: (_, e, v) => (e.value = v == null ? '' : v),
58
+ checked: (_, e, v) => (e.checked = !!v),
59
+
60
+ init: ($, e, v) => {
61
+ const fn = v as Function;
62
+ $.init = () => fn(e, $);
63
+ },
64
+ data: function (_, e, u) {
65
+ const v = u as Hash;
66
+ if (v && e.$dataset) {
67
+ Object.keys(e.$dataset).forEach((k) => {
68
+ if (v[k] == null) {
69
+ e.dataset[camelize(k, '-', '')] = null;
70
+ }
71
+ });
72
+ e.$dataset = { ...e.$dataset, ...v };
73
+ } else {
74
+ e.$dataset = v ? { ...v } : e.$dataset || {};
75
+ }
76
+ if (v) {
77
+ Object.keys(v).forEach((k) => {
78
+ e.dataset[camelize(k, '-', '')] = v[k];
79
+ });
80
+ }
81
+ },
82
+
83
+ click: ($, e, v) => setListener($, 'click:click', e, v),
84
+ ['bubble-click']: ($, e, v) => setListener($, 'bubble-click:click', e, v),
85
+ blur: ($, e, v) => setListener($, 'blur:blur', e, v),
86
+ dblclick: ($, e, v) => setListener($, 'dblclick:dblclick', e, v),
87
+ scroll: ($, e, v) => setListener($, 'scroll:scroll', e, v),
88
+
89
+ touchstart: function ($, e, v) {
90
+ const h = !v
91
+ ? null
92
+ : (ev: TouchEvent & { pageX: number; pageY: number }) => {
93
+ $.$attributes.touchstart(
94
+ {
95
+ ...e.$dataset,
96
+ x: ev.pageX || ev.changedTouches[0].screenX,
97
+ y: ev.pageY || ev.changedTouches[0].screenY,
98
+ },
99
+ ev
100
+ );
101
+ return false;
102
+ };
103
+ setAttribute($, 'touchstart:touchstart', h);
104
+ setAttribute($, 'touchstart:mousedown', h);
105
+ },
106
+ touch: function ($, e, v) {
107
+ const data = { ...e.$dataset };
108
+ const hs = !v
109
+ ? null
110
+ : (ev: TouchEvent & { pageX: number; pageY: number }) => {
111
+ data.active = true;
112
+ data.x = ev.pageX || ev.changedTouches[0].screenX;
113
+ data.y = ev.pageY || ev.changedTouches[0].screenY;
114
+ return false;
115
+ };
116
+ setAttribute($, 'touch:touchstart', hs);
117
+ setAttribute($, 'touch:mousedown', hs);
118
+ const h = !v
119
+ ? () => null
120
+ : (stop: boolean = false) =>
121
+ (ev: TouchEvent & { pageX: number; pageY: number }) => {
122
+ if (data.active) {
123
+ data.active = !stop;
124
+
125
+ data.xx = ev.pageX || ev.changedTouches[0].screenX;
126
+ data.yy = ev.pageY || ev.changedTouches[0].screenY;
127
+ data.dx = data.xx - data.x;
128
+ data.dy = data.yy - data.y;
129
+ $.$attributes.touch(data, ev);
130
+ }
131
+
132
+ return false;
133
+ };
134
+ setAttribute($, 'touch:touchcancel', h(true));
135
+ setAttribute($, 'touch:touchend', h(true));
136
+ setAttribute($, 'touch:mouseup', h(true));
137
+ setAttribute($, 'touch:touchmove', h(false));
138
+ setAttribute($, 'touch:mousemove', h(false));
139
+ },
140
+ keypress: function ($, e, v) {
141
+ setAttribute(
142
+ $,
143
+ 'keypress:keyup',
144
+ !v
145
+ ? null
146
+ : (ev: KeyboardEvent) => {
147
+ if (ev.keyCode !== 13 && ev.keyCode !== 27) {
148
+ const fn = $.$attributes.keypress;
149
+ fn && fn({ value: e.value, ...e.$dataset }, ev);
150
+ setTimeout(() => e.focus(), 0);
151
+ }
152
+ return false;
153
+ }
154
+ );
155
+ },
156
+ enter: function ($, e, v) {
157
+ setAttribute(
158
+ $,
159
+ 'enter:keyup',
160
+ !v
161
+ ? null
162
+ : (ev: KeyboardEvent) => {
163
+ if (ev.keyCode === 13) {
164
+ $.$attributes.enter({ value: e.value, ...e.$dataset }, ev);
165
+ }
166
+ if (ev.keyCode === 13 || ev.keyCode === 27) {
167
+ e.blur();
168
+ }
169
+ return false;
170
+ }
171
+ );
172
+ },
173
+ change: function ($, e, v) {
174
+ setAttribute(
175
+ $,
176
+ 'change:change',
177
+ !v
178
+ ? null
179
+ : (ev: Event) => {
180
+ $.$attributes.change({ value: e.value, ...e.$dataset }, ev);
181
+ return false;
182
+ }
183
+ );
184
+ },
185
+ toggle: function ($, e, v) {
186
+ setAttribute(
187
+ $,
188
+ 'toggle:change',
189
+ !v
190
+ ? null
191
+ : (ev: Event) => {
192
+ $.$attributes.toggle({ value: e.checked, ...e.$dataset }, ev);
193
+ return false;
194
+ }
195
+ );
196
+ },
197
+ };
@@ -0,0 +1,64 @@
1
+ import { ATTR_SETTERS, setAttribute } from './attributes';
2
+ import { DomNode, IElement } from './type';
3
+
4
+ export class DomElement implements IComponentImplementation, IElement {
5
+ $: IComponent;
6
+ $attributes?: Hash;
7
+ element: DomNode;
8
+ [key: string]: unknown;
9
+ isDone: boolean = false;
10
+ listeners: any;
11
+ init?: () => any;
12
+
13
+ constructor(tag: string, _: Hash, $: IComponent) {
14
+ this.$ = $;
15
+ this.element = document.createElement(tag) as DomNode;
16
+ this.element.impl = this;
17
+ }
18
+
19
+ done() {
20
+ this.element?.parentElement?.removeChild(this.element);
21
+ this.$attributes = undefined;
22
+ }
23
+
24
+ stateChanged(changes: Map<string, any>) {
25
+ const attrs: Hash = this.$attributes || (this.$attributes = {});
26
+ const e = this.element;
27
+ // this.$.log('changes', changes)
28
+ changes.forEach((value, key) => {
29
+ if (value !== attrs[key]) {
30
+ const setter = ATTR_SETTERS[key];
31
+ if (setter) {
32
+ setter(this, e, value);
33
+ } else if (key.slice(0, 5) === 'data-') {
34
+ ATTR_SETTERS.data(this, e, {
35
+ ...e.$dataset,
36
+ [key.slice(5)]: value,
37
+ });
38
+ } else {
39
+ setAttribute(this, key, value);
40
+ }
41
+ }
42
+ attrs[key] = value;
43
+ });
44
+ }
45
+ }
46
+
47
+ export class DomTextElement implements IComponentImplementation {
48
+ element: Text;
49
+ [key: string]: unknown;
50
+
51
+ constructor(attrs: Hash) {
52
+ this.element = document.createTextNode(String(attrs.text || ''));
53
+ }
54
+
55
+ done() {
56
+ this.element.parentElement?.removeChild(this.element);
57
+ }
58
+
59
+ stateChanged(changes: Map<string, any>) {
60
+ if (changes.has('text')) {
61
+ this.element.textContent = changes.get('text');
62
+ }
63
+ }
64
+ }
@@ -0,0 +1,19 @@
1
+ import { CArrmaturaNode, Platform } from '../core';
2
+ import { WebPlatform } from './WebPlatform';
3
+
4
+ export type ArrmaturaOptionsForWeb = ArrmaturaOptions & {
5
+ rootElement?: HTMLElement;
6
+ };
7
+
8
+ export function arrmatura(
9
+ template: string,
10
+ { rootElement, pipes, functions = pipes, types, resources }: ArrmaturaOptionsForWeb
11
+ ) {
12
+ Platform.instance = new WebPlatform(rootElement);
13
+
14
+ const node = new CArrmaturaNode(template, { functions, types, resources });
15
+
16
+ node.launch();
17
+ }
18
+
19
+ Object.defineProperty(window, 'arrmatura', { value: arrmatura });
@@ -0,0 +1,30 @@
1
+ import { DomNode } from './type';
2
+
3
+ function appendElt(e: DomNode, p: DomNode | HTMLElement, cursor?: DomNode): DomNode {
4
+ const before = cursor ? cursor.nextSibling : p.firstChild;
5
+ if (!before) {
6
+ if (p !== e.parentElement) {
7
+ p.appendChild(e);
8
+ }
9
+ } else if (e !== before) {
10
+ p.insertBefore(e, before);
11
+ }
12
+ return e;
13
+ }
14
+
15
+ export function reflow($: IComponent, parent: DomNode | HTMLElement, cursor?: DomNode) {
16
+ if ($.children) {
17
+ for (let p of $.children.values()) {
18
+ const e = p.impl.element as DomNode;
19
+ if (e) {
20
+ if (e) {
21
+ reflow(p, e);
22
+ cursor = appendElt(e, parent, cursor);
23
+ }
24
+ } else {
25
+ cursor = reflow(p, parent, cursor);
26
+ }
27
+ }
28
+ }
29
+ return cursor;
30
+ }
@@ -0,0 +1,19 @@
1
+ export interface DomNode extends HTMLElement {
2
+ disabled: boolean | null;
3
+ selected: boolean | null;
4
+ value: unknown;
5
+ checked: boolean;
6
+ dataset: any;
7
+ focus(): void;
8
+ blur(): void;
9
+ $dataset: any;
10
+ impl?: IElement;
11
+ }
12
+
13
+ export interface IElement {
14
+ $attributes?: any;
15
+ init?: () => any;
16
+ isDone: boolean;
17
+ listeners: any;
18
+ element: DomNode;
19
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arrmatura",
3
- "version": "5.0.1",
3
+ "version": "5.0.2",
4
4
  "description": "framework",
5
5
  "scripts": {
6
6
  "start": "npx webpack-dev-server --hot --contentBase docs",
@@ -18,14 +18,15 @@
18
18
  "files": [
19
19
  "lib",
20
20
  "core",
21
- "commons",
22
- "web"
21
+ "core-web",
22
+ "commons"
23
23
  ],
24
24
  "devDependencies": {
25
25
  "@babel/cli": "^7.12.1",
26
26
  "@babel/core": "^7.12.3",
27
27
  "@babel/preset-env": "^7.12.1",
28
28
  "babel-loader": "^8.2.1",
29
+ "babel-plugin-module-resolver": "^4.1.0",
29
30
  "eslint": "^7.30.0",
30
31
  "@typescript-eslint/eslint-plugin": "^2.27.0",
31
32
  "@typescript-eslint/parser": "^2.27.0",
@@ -36,14 +37,11 @@
36
37
  "eslint-plugin-import": "^2.22.1",
37
38
  "eslint-plugin-node": "^11.1.0",
38
39
  "eslint-plugin-promise": "^4.2.1",
40
+ "metro-react-native-babel-preset": "^0.66.2",
39
41
  "raw-loader": "^4.0.2",
40
42
  "typescript": "^4.4.4",
41
43
  "webpack": "^4.44.2",
42
44
  "webpack-cli": "^3.3.12",
43
45
  "webpack-dev-server": "^3.11.0"
44
- },
45
- "dependencies": {
46
- "babel-plugin-module-resolver": "^4.1.0",
47
- "metro-react-native-babel-preset": "^0.66.2"
48
46
  }
49
47
  }