arrmatura 4.3.2 → 5.0.4
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 +30 -25
- package/commons/auth/UserService.ts +107 -0
- package/commons/auth/index.ts +4 -0
- package/commons/auth/user.xml +66 -0
- package/commons/components/buttons.xml +23 -0
- package/commons/components/index.ts +13 -0
- package/commons/components/inputs.xml +33 -0
- package/commons/components/layouts.xml +77 -0
- package/commons/components/media.xml +21 -0
- package/commons/components/modal.xml +37 -0
- package/commons/components/navigation.xml +59 -0
- package/commons/components/popups.xml +12 -0
- package/commons/components/searchbar.xml +40 -0
- package/commons/components/table.xml +120 -0
- package/commons/components/toasts.xml +13 -0
- package/commons/components/viewport.xml +48 -0
- package/commons/forms/FieldItem.xml +20 -0
- package/commons/forms/FormController.ts +73 -0
- package/commons/forms/Item.ts +68 -0
- package/commons/forms/ItemCollection.ts +152 -0
- package/commons/forms/ItemController.ts +62 -0
- package/commons/forms/MetadataService.ts +45 -0
- package/commons/forms/MultiEnumField.xml +44 -0
- package/commons/forms/MultivalueController.ts +49 -0
- package/commons/forms/SmartareaField.xml +51 -0
- package/commons/forms/SuggestionController.ts +80 -0
- package/commons/forms/SuggestionField.xml +28 -0
- package/commons/forms/fields.xml +138 -0
- package/commons/forms/form.xml +37 -0
- package/commons/forms/index.ts +29 -0
- package/commons/index.ts +6 -0
- package/commons/pipes/index.ts +21 -0
- package/commons/pipes/showCounts.ts +16 -0
- package/commons/services/ErrorHandlingService.ts +11 -0
- package/commons/services/FirebaseService.ts +185 -0
- package/commons/services/GSheetsService.ts +92 -0
- package/commons/services/HeartbeatService.ts +35 -0
- package/commons/services/Invoke.ts +13 -0
- package/commons/services/JsonpService.ts +18 -0
- package/commons/services/Loader.ts +17 -0
- package/commons/services/LocalStorage.ts +46 -0
- package/commons/services/NavigationService.ts +55 -0
- package/commons/services/Service.ts +68 -0
- package/commons/services/ServiceWorker.ts +111 -0
- package/commons/services/ToastService.ts +49 -0
- package/commons/services/index.ts +14 -0
- package/core/Arrmatura.ts +84 -0
- package/core/CNode.ts +112 -0
- package/core/Component.ts +426 -0
- package/core/cnodes/composition.ts +100 -0
- package/core/cnodes/conditionals.ts +51 -0
- package/core/cnodes/elementary.ts +44 -0
- package/core/cnodes/iterations.ts +92 -0
- package/core/cnodes/routing.ts +25 -0
- package/core/compileNode.ts +39 -0
- package/core/expression.ts +73 -0
- package/core/index.ts +1 -0
- package/core/register.ts +43 -0
- package/core/utils.ts +25 -0
- package/core-web/WebPlatform.ts +25 -0
- package/core-web/attributes.ts +190 -0
- package/core-web/elements.ts +63 -0
- package/core-web/index.ts +16 -0
- package/core-web/reflow.ts +28 -0
- package/core-web/type.ts +18 -0
- package/docs/index.js +187 -4069
- package/lib/arrayGroupBy.ts +19 -0
- package/lib/arraySortBy.ts +49 -0
- package/lib/arrayToObject.ts +18 -0
- package/lib/codus.ts +25 -0
- package/lib/consts.ts +3 -0
- package/lib/createListeners.ts +12 -0
- package/lib/curry.ts +8 -0
- package/lib/dateFormat.ts +69 -0
- package/lib/dateOf.ts +31 -0
- package/lib/dateParseISO8601.ts +27 -0
- package/lib/fx.ts +62 -0
- package/lib/guid.ts +7 -0
- package/lib/index.ts +19 -0
- package/lib/l10n.ts +40 -0
- package/lib/nextId.ts +9 -0
- package/lib/noop.ts +8 -0
- package/lib/obj.ts +64 -0
- package/lib/predicat.ts +26 -0
- package/lib/resources.ru.ts +20 -0
- package/lib/resources.ts +20 -0
- package/lib/scalar.ts +25 -0
- package/lib/str.ts +76 -0
- package/lib/urls.ts +91 -0
- package/lib/xml.ts +117 -0
- package/package.json +18 -8
- package/docs/index.js.map +0 -1
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { CNode } from '../CNode';
|
|
2
|
+
import { compileNodes } from '../compileNode';
|
|
3
|
+
import { Component } from '../Component';
|
|
4
|
+
|
|
5
|
+
export class ElementComponent extends Component<CElementNode> {
|
|
6
|
+
get displayName(): string {
|
|
7
|
+
return this.node.tag;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
createImplementation(initials: Hash): IComponentImplementation {
|
|
11
|
+
return this.arrmatura.platform.createElement(this.node.tag, initials, this);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class TextElementComponent extends Component<CTextElementNode> {
|
|
16
|
+
createImplementation(initials: Hash) {
|
|
17
|
+
return this.arrmatura.platform.createTextElement(initials, this);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export class CElementNode extends CNode<ElementComponent> {
|
|
22
|
+
constructor(x: XmlNode) {
|
|
23
|
+
super();
|
|
24
|
+
this.tag = x.tag;
|
|
25
|
+
this.content = compileNodes(x.nodes);
|
|
26
|
+
this.compileAttributes(x.attrs);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
get ComponentConstructor() {
|
|
30
|
+
return ElementComponent;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export class CTextElementNode extends CNode<TextElementComponent> {
|
|
35
|
+
constructor(x: XmlNode) {
|
|
36
|
+
super();
|
|
37
|
+
this.tag = x.tag;
|
|
38
|
+
this.compileAttributes(x.attrs);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
get ComponentConstructor() {
|
|
42
|
+
return TextElementComponent;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { CNode } from '../CNode';
|
|
2
|
+
import { compileNodes } from '../compileNode';
|
|
3
|
+
import { Component } from '../Component';
|
|
4
|
+
|
|
5
|
+
class ForComponent extends Component<CForNode> {
|
|
6
|
+
pkHash: Hash<Datum> = {};
|
|
7
|
+
|
|
8
|
+
get displayName(): string {
|
|
9
|
+
return '🔹';
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
get content() {
|
|
13
|
+
const each: any = this.$impl.each;
|
|
14
|
+
const itemNode = this.node.itemNode;
|
|
15
|
+
const nodes: ComponentContent = new Map();
|
|
16
|
+
this.pkHash = {};
|
|
17
|
+
if (each?.length) {
|
|
18
|
+
if (!each.forEach) {
|
|
19
|
+
throw new Error('[ui:for] Items has no forEach() ' + each);
|
|
20
|
+
}
|
|
21
|
+
each.forEach((datum: Datum) => {
|
|
22
|
+
const id = datum.id;
|
|
23
|
+
if (id == null) {
|
|
24
|
+
console.error('ERROR: empty item id: ', datum);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const pk = String(id);
|
|
28
|
+
if (this.pkHash[pk]) {
|
|
29
|
+
console.error('ERROR: duplicate item id: ' + pk, datum);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
this.pkHash[pk] = datum;
|
|
33
|
+
const inode = itemNode.cloneWithDatum(datum).addPropertyResolver(() => this.pkHash[pk], itemNode.itemName);
|
|
34
|
+
nodes.set(inode.uid, inode);
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
return nodes;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export class CForNode extends CNode<ForComponent> {
|
|
42
|
+
itemNode: CItemNode;
|
|
43
|
+
constructor(x: XmlNode, [itemName, prep, expr = prep]: string[]) {
|
|
44
|
+
super();
|
|
45
|
+
this.itemNode = new CItemNode(itemName, compileNodes([x]));
|
|
46
|
+
this.compileAttribute('each', '{:<'.includes(expr[0]) ? expr : `{${expr}}`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
get ComponentConstructor() {
|
|
50
|
+
return ForComponent;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
class ItemComponent extends Component<CItemNode> {
|
|
55
|
+
get displayName(): string {
|
|
56
|
+
return '🔹';
|
|
57
|
+
}
|
|
58
|
+
get recontentScope(): Component {
|
|
59
|
+
return this;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
emit(key: string, data: Delta): void {
|
|
63
|
+
return this.$scope.emit(key, data);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
get(propId: string) {
|
|
67
|
+
const iName: string = this.node.itemName;
|
|
68
|
+
const value = propId.startsWith(iName + '.') || propId === iName ? super.get(propId) : this.$scope.get(propId);
|
|
69
|
+
// this.log(propId, value);
|
|
70
|
+
return value;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export class CItemNode extends CNode<ItemComponent> {
|
|
75
|
+
itemName: string;
|
|
76
|
+
constructor(itemName: string, content?: ComponentContent) {
|
|
77
|
+
super();
|
|
78
|
+
this.itemName = itemName;
|
|
79
|
+
this.content = content;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
cloneWithDatum(datum: Datum) {
|
|
83
|
+
const uid = this.uid + '#' + datum.id;
|
|
84
|
+
return Object.assign(new CItemNode(this.itemName, this.content), {
|
|
85
|
+
uid,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
get ComponentConstructor() {
|
|
90
|
+
return ItemComponent;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { CNode } from '../CNode';
|
|
2
|
+
import { compileNode } from '../compileNode';
|
|
3
|
+
import { Component } from '../Component';
|
|
4
|
+
|
|
5
|
+
export class DynamicTagComponent extends Component<CDynamicTagNode> {
|
|
6
|
+
get content(): ComponentContent | undefined {
|
|
7
|
+
const tag = this.$impl.tag;
|
|
8
|
+
const uid = this.node.uid + ':' + tag;
|
|
9
|
+
return new Map([[uid, Object.assign(compileNode(Object.assign(this.node.xml, { tag })), { uid })]]);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class CDynamicTagNode extends CNode<DynamicTagComponent> {
|
|
14
|
+
xml: XmlNode & { tag: string };
|
|
15
|
+
|
|
16
|
+
constructor(xml: XmlNode, expr: string) {
|
|
17
|
+
super();
|
|
18
|
+
this.compileAttribute('tag', expr);
|
|
19
|
+
this.xml = xml;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
get ComponentConstructor() {
|
|
23
|
+
return DynamicTagComponent;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { CCompositeNode, CFragmentNode, CSlotNode } from './cnodes/composition';
|
|
2
|
+
import { CDynamicTagNode } from './cnodes/routing';
|
|
3
|
+
import { CElementNode, CTextElementNode } from './cnodes/elementary';
|
|
4
|
+
import { CForNode } from './cnodes/iterations';
|
|
5
|
+
import { CIfNode } from './cnodes/conditionals';
|
|
6
|
+
|
|
7
|
+
export const compileNode = (x: XmlNode): INode => {
|
|
8
|
+
const { tag, attrs } = x;
|
|
9
|
+
const each = attrs?.['ui:for'];
|
|
10
|
+
if (each) {
|
|
11
|
+
delete attrs['ui:for'];
|
|
12
|
+
return new CForNode(x, String(each).trim().split(' '));
|
|
13
|
+
}
|
|
14
|
+
const when = attrs?.['ui:if'];
|
|
15
|
+
if (when) {
|
|
16
|
+
delete attrs['ui:if'];
|
|
17
|
+
return new CIfNode(x, String(when));
|
|
18
|
+
}
|
|
19
|
+
if (tag === 'ui:tag') {
|
|
20
|
+
const expr = attrs?.['tag'] || tag;
|
|
21
|
+
delete attrs?.['tag'];
|
|
22
|
+
return new CDynamicTagNode(x, String(expr));
|
|
23
|
+
}
|
|
24
|
+
if (tag === 'ui:slot') {
|
|
25
|
+
return new CSlotNode(x);
|
|
26
|
+
}
|
|
27
|
+
if (tag === 'ui:fragment') {
|
|
28
|
+
return new CFragmentNode(x);
|
|
29
|
+
}
|
|
30
|
+
if (tag === '#text') {
|
|
31
|
+
return new CTextElementNode(x);
|
|
32
|
+
}
|
|
33
|
+
if (tag[0].match(/[A-Z]/)) return new CCompositeNode(x);
|
|
34
|
+
|
|
35
|
+
return new CElementNode(x);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export const compileNodes = (x?: XmlNode[] | null): ComponentContent =>
|
|
39
|
+
new Map<Uid, INode>(x?.map(compileNode).map((c) => [c.uid, c]) || []);
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { scalarParse } from 'scalar';
|
|
2
|
+
|
|
3
|
+
const RE_SINGLE_PLACEHOLDER = /^\{([^}]+)\}$/;
|
|
4
|
+
|
|
5
|
+
const RE_PLACEHOLDER = /\{([^}]+)\}/g;
|
|
6
|
+
|
|
7
|
+
const withNoPipes = (_: unknown, v: unknown) => v;
|
|
8
|
+
|
|
9
|
+
const splitExpression = (v: Expression) => {
|
|
10
|
+
return v.split('|').map((s) => s.trim());
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export function withPipes(pipes: string[]) {
|
|
14
|
+
return !pipes.length
|
|
15
|
+
? withNoPipes
|
|
16
|
+
: (c: IComponent, initialValue: unknown) => pipes.reduce((r, pk) => c.applyPipe(pk, r), initialValue);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function compilePipeExpression(v: Expression) {
|
|
20
|
+
const [key, ...pipes] = splitExpression(v);
|
|
21
|
+
return { key, pipec: withPipes(pipes) };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function stringInterpolation(v: string, fnx: Array<(c: IComponent) => unknown> = []) {
|
|
25
|
+
const pattern = v.trim().replace(RE_PLACEHOLDER, (_: string, expr: string) => {
|
|
26
|
+
fnx.push(placeholder(expr));
|
|
27
|
+
return '{{' + (fnx.length - 1) + '}}';
|
|
28
|
+
});
|
|
29
|
+
return !fnx.length
|
|
30
|
+
? (_: IComponent) => pattern
|
|
31
|
+
: (c: IComponent) =>
|
|
32
|
+
pattern
|
|
33
|
+
.replace(/\{\{(\d+)\}\}/g, (_: string, idx: string) => {
|
|
34
|
+
const r = fnx[Number(idx)](c);
|
|
35
|
+
return !r && r !== 0 ? '' : String(r);
|
|
36
|
+
})
|
|
37
|
+
.replace(/\s+/g, ' ');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function resourceExpression(expr: string) {
|
|
41
|
+
const { key, pipec } = compilePipeExpression(expr);
|
|
42
|
+
const intrpltn = stringInterpolation(key);
|
|
43
|
+
return !pipec.length ? (c: IComponent) => c.res(intrpltn(c)) : (c: IComponent) => pipec(c, c.res(intrpltn(c)));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function placeholder(expr: string) {
|
|
47
|
+
const { key, pipec } = compilePipeExpression(expr);
|
|
48
|
+
const fn =
|
|
49
|
+
key[0] === ':'
|
|
50
|
+
? (
|
|
51
|
+
(k) => (c: IComponent) =>
|
|
52
|
+
c.res(k)
|
|
53
|
+
)(key.slice(1))
|
|
54
|
+
: (c: IComponent) => c.getFromScope(key);
|
|
55
|
+
return !pipec.length ? fn : (c: IComponent) => pipec(c, fn(c));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function compileExpression(v: unknown) {
|
|
59
|
+
if (typeof v !== 'string') {
|
|
60
|
+
return () => v;
|
|
61
|
+
}
|
|
62
|
+
if (v[0] === ':') {
|
|
63
|
+
return resourceExpression(v.slice(1).trim());
|
|
64
|
+
}
|
|
65
|
+
if (!v.includes('{')) {
|
|
66
|
+
const vv = scalarParse(v);
|
|
67
|
+
return () => vv;
|
|
68
|
+
}
|
|
69
|
+
if (v.match(RE_SINGLE_PLACEHOLDER)) {
|
|
70
|
+
return placeholder(v.slice(1, -1).trim());
|
|
71
|
+
}
|
|
72
|
+
return stringInterpolation(v);
|
|
73
|
+
}
|
package/core/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './Arrmatura';
|
package/core/register.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { compileNodes } from './compileNode';
|
|
2
|
+
import { xmlParse } from 'xml';
|
|
3
|
+
import { fnName } from './utils';
|
|
4
|
+
|
|
5
|
+
export class ComponentDefinition {
|
|
6
|
+
tag: string;
|
|
7
|
+
template?: string;
|
|
8
|
+
Ctor: any;
|
|
9
|
+
$templateContent?: ComponentContent;
|
|
10
|
+
|
|
11
|
+
constructor({ tag, template, Ctor }: ComponentDefinitionInfo) {
|
|
12
|
+
this.tag = tag;
|
|
13
|
+
this.template = template;
|
|
14
|
+
this.Ctor = Ctor || function () {};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
get content() {
|
|
18
|
+
return this.$templateContent || (this.$templateContent = compileNodes(xmlParse(this.template)));
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const REGISTRY = new Map<string, ComponentDefinition>();
|
|
23
|
+
|
|
24
|
+
const notFound = (tag: string): ComponentDefinition =>
|
|
25
|
+
new ComponentDefinition({ tag, template: '<b>??' + tag + '??</b>' });
|
|
26
|
+
|
|
27
|
+
const setInfo = (ctr: ComponentDefinitionInfo) => REGISTRY.set(ctr.tag, new ComponentDefinition(ctr));
|
|
28
|
+
|
|
29
|
+
const registerType = (ctr: string | ComponentDefinitionInfo | Constructor) => {
|
|
30
|
+
if (typeof ctr === 'string') {
|
|
31
|
+
ctr.replace(/<component\s+id="(.+)">([\s\S]*?)<\/component>/gm, (_, tag, template) => {
|
|
32
|
+
setInfo({ tag, template: template.trim() });
|
|
33
|
+
return '';
|
|
34
|
+
});
|
|
35
|
+
} else {
|
|
36
|
+
setInfo(typeof ctr === 'function' ? { Ctor: ctr, tag: ctr.name || fnName(ctr) } : ctr);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export const registerTypes = (types?: Array<string | ComponentDefinitionInfo | Constructor>) =>
|
|
41
|
+
types?.forEach(registerType);
|
|
42
|
+
|
|
43
|
+
export const getByTag = (tag: string) => REGISTRY.get(tag) || notFound(tag);
|
package/core/utils.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { nextId } from 'nextId';
|
|
2
|
+
import { identity } from 'noop';
|
|
3
|
+
import { attributesToString } from 'xml';
|
|
4
|
+
|
|
5
|
+
export const applyValue = (value: any, fn = identity) => (value?.then ? value.then(fn) : fn(value));
|
|
6
|
+
|
|
7
|
+
export const fnName = (ctor: object) => (/^function\s+([\w$]+)\s*\(/.exec(ctor.toString()) || [])[1] || nextId('C');
|
|
8
|
+
|
|
9
|
+
export const methodName = (x: unknown, pre = '') => {
|
|
10
|
+
const s = `${x || ''}`;
|
|
11
|
+
return pre + s[0].toUpperCase() + s.slice(1);
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const stringifyState = (attrs?: object | Hash) => {
|
|
15
|
+
if (!attrs) {
|
|
16
|
+
return '';
|
|
17
|
+
}
|
|
18
|
+
const r: string[] = [];
|
|
19
|
+
Object.entries(attrs).forEach(([k, v]) => {
|
|
20
|
+
if (v && k[0] !== '$') {
|
|
21
|
+
r.push(' ' + k + '="' + attributesToString(v) + '"');
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
return r.length ? r.join('') : '';
|
|
25
|
+
};
|
|
@@ -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,190 @@
|
|
|
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
|
+
$[fnKey]({ ...e.$dataset }, ev);
|
|
16
|
+
return false;
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
export function setAttribute($: IElement, key: string, value: null | string | ((ev: any) => any)) {
|
|
21
|
+
if (value != null) {
|
|
22
|
+
if (typeof value === 'function') {
|
|
23
|
+
if (!$.$listeners) {
|
|
24
|
+
$.$listeners = new Map();
|
|
25
|
+
}
|
|
26
|
+
if (!$.$listeners.has(key)) {
|
|
27
|
+
const [akey, ekey = akey] = key.split(':');
|
|
28
|
+
$.$element.addEventListener(ekey, value, false);
|
|
29
|
+
$.$listeners.set(key, value);
|
|
30
|
+
}
|
|
31
|
+
} else {
|
|
32
|
+
$.$element.setAttribute(key, value);
|
|
33
|
+
}
|
|
34
|
+
} else {
|
|
35
|
+
if ($.$listeners?.has(key)) {
|
|
36
|
+
const [akey, ekey = akey] = key.split(':');
|
|
37
|
+
$.$element.removeEventListener(ekey, $.$listeners.get(key));
|
|
38
|
+
$.$listeners.delete(key);
|
|
39
|
+
} else {
|
|
40
|
+
$.$element.removeAttribute(key);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// DOM
|
|
46
|
+
export const ATTR_SETTERS: Hash<($: IElement, e: DomNode, v: unknown) => void> = {
|
|
47
|
+
disabled: (_, e, v) => (e.disabled = v ? true : null),
|
|
48
|
+
class: (_, e, v) => {
|
|
49
|
+
e.className = String(v || '');
|
|
50
|
+
},
|
|
51
|
+
selected: (_, e, v) => (e.selected = v ? true : null),
|
|
52
|
+
value: (_, e, v) => (e.value = v == null ? '' : v),
|
|
53
|
+
checked: (_, e, v) => (e.checked = !!v),
|
|
54
|
+
|
|
55
|
+
init: () => {
|
|
56
|
+
// do nothing
|
|
57
|
+
},
|
|
58
|
+
data: function (_, e, u) {
|
|
59
|
+
const v = u as Hash;
|
|
60
|
+
if (v && e.$dataset) {
|
|
61
|
+
Object.keys(e.$dataset).forEach((k) => {
|
|
62
|
+
if (v[k] == null) {
|
|
63
|
+
e.dataset[camelize(k, '-', '')] = null;
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
e.$dataset = { ...e.$dataset, ...v };
|
|
67
|
+
} else {
|
|
68
|
+
e.$dataset = v ? { ...v } : e.$dataset || {};
|
|
69
|
+
}
|
|
70
|
+
if (v) {
|
|
71
|
+
Object.keys(v).forEach((k) => {
|
|
72
|
+
e.dataset[camelize(k, '-', '')] = v[k];
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
click: ($, e, v) => setListener($, 'click:click', e, v),
|
|
78
|
+
['bubble-click']: ($, e, v) => setListener($, 'bubble-click:click', e, v),
|
|
79
|
+
blur: ($, e, v) => setListener($, 'blur:blur', e, v),
|
|
80
|
+
dblclick: ($, e, v) => setListener($, 'dblclick:dblclick', e, v),
|
|
81
|
+
scroll: ($, e, v) => setListener($, 'scroll:scroll', e, v),
|
|
82
|
+
|
|
83
|
+
touchstart: function ($, e, v) {
|
|
84
|
+
const h = !v
|
|
85
|
+
? null
|
|
86
|
+
: (ev: TouchEvent & { pageX: number; pageY: number }) => {
|
|
87
|
+
$.touchstart(
|
|
88
|
+
{
|
|
89
|
+
...e.$dataset,
|
|
90
|
+
x: ev.pageX || ev.changedTouches[0].screenX,
|
|
91
|
+
y: ev.pageY || ev.changedTouches[0].screenY,
|
|
92
|
+
},
|
|
93
|
+
ev
|
|
94
|
+
);
|
|
95
|
+
return false;
|
|
96
|
+
};
|
|
97
|
+
setAttribute($, 'touchstart:touchstart', h);
|
|
98
|
+
setAttribute($, 'touchstart:mousedown', h);
|
|
99
|
+
},
|
|
100
|
+
touch: function ($, e, v) {
|
|
101
|
+
const data = { ...e.$dataset };
|
|
102
|
+
const hs = !v
|
|
103
|
+
? null
|
|
104
|
+
: (ev: TouchEvent & { pageX: number; pageY: number }) => {
|
|
105
|
+
data.active = true;
|
|
106
|
+
data.x = ev.pageX || ev.changedTouches[0].screenX;
|
|
107
|
+
data.y = ev.pageY || ev.changedTouches[0].screenY;
|
|
108
|
+
return false;
|
|
109
|
+
};
|
|
110
|
+
setAttribute($, 'touch:touchstart', hs);
|
|
111
|
+
setAttribute($, 'touch:mousedown', hs);
|
|
112
|
+
const h = !v
|
|
113
|
+
? () => null
|
|
114
|
+
: (stop: boolean = false) =>
|
|
115
|
+
(ev: TouchEvent & { pageX: number; pageY: number }) => {
|
|
116
|
+
if (data.active) {
|
|
117
|
+
data.active = !stop;
|
|
118
|
+
|
|
119
|
+
data.xx = ev.pageX || ev.changedTouches[0].screenX;
|
|
120
|
+
data.yy = ev.pageY || ev.changedTouches[0].screenY;
|
|
121
|
+
data.dx = data.xx - data.x;
|
|
122
|
+
data.dy = data.yy - data.y;
|
|
123
|
+
$.touch(data, ev);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return false;
|
|
127
|
+
};
|
|
128
|
+
setAttribute($, 'touch:touchcancel', h(true));
|
|
129
|
+
setAttribute($, 'touch:touchend', h(true));
|
|
130
|
+
setAttribute($, 'touch:mouseup', h(true));
|
|
131
|
+
setAttribute($, 'touch:touchmove', h(false));
|
|
132
|
+
setAttribute($, 'touch:mousemove', h(false));
|
|
133
|
+
},
|
|
134
|
+
keypress: function ($, e, v) {
|
|
135
|
+
setAttribute(
|
|
136
|
+
$,
|
|
137
|
+
'keypress:keyup',
|
|
138
|
+
!v
|
|
139
|
+
? null
|
|
140
|
+
: (ev: KeyboardEvent) => {
|
|
141
|
+
if (ev.keyCode !== 13 && ev.keyCode !== 27) {
|
|
142
|
+
$.keypress?.({ value: e.value, ...e.$dataset }, ev);
|
|
143
|
+
setTimeout(() => e.focus(), 0);
|
|
144
|
+
}
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
);
|
|
148
|
+
},
|
|
149
|
+
enter: function ($, e, v) {
|
|
150
|
+
setAttribute(
|
|
151
|
+
$,
|
|
152
|
+
'enter:keyup',
|
|
153
|
+
!v
|
|
154
|
+
? null
|
|
155
|
+
: (ev: KeyboardEvent) => {
|
|
156
|
+
if (ev.keyCode === 13) {
|
|
157
|
+
$.enter({ value: e.value, ...e.$dataset }, ev);
|
|
158
|
+
}
|
|
159
|
+
if (ev.keyCode === 13 || ev.keyCode === 27) {
|
|
160
|
+
e.blur();
|
|
161
|
+
}
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
);
|
|
165
|
+
},
|
|
166
|
+
change: function ($, e, v) {
|
|
167
|
+
setAttribute(
|
|
168
|
+
$,
|
|
169
|
+
'change:change',
|
|
170
|
+
!v
|
|
171
|
+
? null
|
|
172
|
+
: (ev: Event) => {
|
|
173
|
+
$.change({ value: e.value, ...e.$dataset }, ev);
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
);
|
|
177
|
+
},
|
|
178
|
+
toggle: function ($, e, v) {
|
|
179
|
+
setAttribute(
|
|
180
|
+
$,
|
|
181
|
+
'toggle:change',
|
|
182
|
+
!v
|
|
183
|
+
? null
|
|
184
|
+
: (ev: Event) => {
|
|
185
|
+
$.toggle({ value: e.checked, ...e.$dataset }, ev);
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
);
|
|
189
|
+
},
|
|
190
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { ATTR_SETTERS, setAttribute } from './attributes';
|
|
2
|
+
import { DomNode, IElement } from './type';
|
|
3
|
+
|
|
4
|
+
export class DomElement implements IComponentImplementation, IElement {
|
|
5
|
+
$: IComponent;
|
|
6
|
+
$element: DomNode;
|
|
7
|
+
$listeners: any;
|
|
8
|
+
|
|
9
|
+
init?: () => any;
|
|
10
|
+
[key: string]: unknown;
|
|
11
|
+
|
|
12
|
+
constructor(tag: string, _: Hash, $: IComponent) {
|
|
13
|
+
this.$ = $;
|
|
14
|
+
this.$element = document.createElement(tag) as DomNode;
|
|
15
|
+
this.$element.impl = this;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
done() {
|
|
19
|
+
this.$element?.parentElement?.removeChild(this.$element);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
stateChanged(changes: Map<string, any>) {
|
|
23
|
+
const attrs = this;
|
|
24
|
+
const e = this.$element;
|
|
25
|
+
// this.$.log('changes', changes)
|
|
26
|
+
changes.forEach((value, key) => {
|
|
27
|
+
if (key[0] === '$') return;
|
|
28
|
+
if (value !== attrs[key]) {
|
|
29
|
+
const setter = ATTR_SETTERS[key];
|
|
30
|
+
if (setter) {
|
|
31
|
+
setter(this, e, value);
|
|
32
|
+
} else if (key.slice(0, 5) === 'data-') {
|
|
33
|
+
ATTR_SETTERS.data(this, e, {
|
|
34
|
+
...e.$dataset,
|
|
35
|
+
[key.slice(5)]: value,
|
|
36
|
+
});
|
|
37
|
+
} else {
|
|
38
|
+
setAttribute(this, key, value);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
attrs[key] = value;
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export class DomTextElement implements IComponentImplementation {
|
|
47
|
+
$element: Text;
|
|
48
|
+
[key: string]: unknown;
|
|
49
|
+
|
|
50
|
+
constructor(attrs: Hash) {
|
|
51
|
+
this.$element = document.createTextNode(String(attrs.text || ''));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
done() {
|
|
55
|
+
this.$element.parentElement?.removeChild(this.$element);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
stateChanged(changes: Map<string, any>) {
|
|
59
|
+
if (changes.has('text')) {
|
|
60
|
+
this.$element.textContent = changes.get('text');
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { CArrmaturaNode } 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 & { pipes: Hash<Pipe> }
|
|
11
|
+
) {
|
|
12
|
+
const node = new CArrmaturaNode(template, { functions, types, resources }, new WebPlatform(rootElement));
|
|
13
|
+
node.launch();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
Object.defineProperty(window, 'arrmatura', { value: arrmatura });
|
|
@@ -0,0 +1,28 @@
|
|
|
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
|
+
reflow(p, e);
|
|
21
|
+
cursor = appendElt(e, parent, cursor);
|
|
22
|
+
} else {
|
|
23
|
+
cursor = reflow(p, parent, cursor);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return cursor;
|
|
28
|
+
}
|
package/core-web/type.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
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
|
+
init?: () => any;
|
|
15
|
+
$listeners: any;
|
|
16
|
+
$element: DomNode;
|
|
17
|
+
[key: string]: any;
|
|
18
|
+
}
|