@dooboostore/simple-web-component 1.0.29 → 1.0.30
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 +432 -27
- package/dist/cjs/decorators/elementDefine.js +2 -0
- package/dist/cjs/decorators/elementDefine.js.map +2 -2
- package/dist/cjs/decorators/emitCustomEvent.js.map +2 -2
- package/dist/cjs/decorators/index.js +1 -0
- package/dist/cjs/decorators/index.js.map +2 -2
- package/dist/cjs/decorators/setProperty.js +97 -0
- package/dist/cjs/decorators/setProperty.js.map +7 -0
- package/dist/cjs/elements/SwcAppMixin.js +1 -1
- package/dist/cjs/elements/SwcAppMixin.js.map +2 -2
- package/dist/cjs/elements/SwcAsync.js +4 -1
- package/dist/cjs/elements/SwcAsync.js.map +2 -2
- package/dist/cjs/elements/SwcChoose.js +54 -38
- package/dist/cjs/elements/SwcChoose.js.map +2 -2
- package/dist/cjs/elements/register.js +131 -39
- package/dist/cjs/elements/register.js.map +2 -2
- package/dist/cjs/index.js +2 -1
- package/dist/cjs/index.js.map +2 -2
- package/dist/cjs/utils/Utils.js +11 -8
- package/dist/cjs/utils/Utils.js.map +2 -2
- package/dist/esm/decorators/elementDefine.js +2 -0
- package/dist/esm/decorators/elementDefine.js.map +2 -2
- package/dist/esm/decorators/emitCustomEvent.js.map +2 -2
- package/dist/esm/decorators/index.js +1 -0
- package/dist/esm/decorators/index.js.map +2 -2
- package/dist/esm/decorators/setProperty.js +78 -0
- package/dist/esm/decorators/setProperty.js.map +7 -0
- package/dist/esm/elements/SwcAppMixin.js +1 -1
- package/dist/esm/elements/SwcAppMixin.js.map +2 -2
- package/dist/esm/elements/SwcAsync.js +4 -1
- package/dist/esm/elements/SwcAsync.js.map +2 -2
- package/dist/esm/elements/SwcChoose.js +54 -38
- package/dist/esm/elements/SwcChoose.js.map +2 -2
- package/dist/esm/elements/register.js +131 -39
- package/dist/esm/elements/register.js.map +2 -2
- package/dist/esm/index.js +2 -1
- package/dist/esm/index.js.map +2 -2
- package/dist/esm/utils/Utils.js +11 -8
- package/dist/esm/utils/Utils.js.map +2 -2
- package/dist/esm-bundle/dooboostore-simple-web-component.esm.js +87 -8
- package/dist/esm-bundle/dooboostore-simple-web-component.esm.js.map +4 -4
- package/dist/types/decorators/elementDefine.d.ts.map +1 -1
- package/dist/types/decorators/emitCustomEvent.d.ts.map +1 -1
- package/dist/types/decorators/index.d.ts +1 -0
- package/dist/types/decorators/index.d.ts.map +1 -1
- package/dist/types/decorators/setProperty.d.ts +13 -0
- package/dist/types/decorators/setProperty.d.ts.map +1 -0
- package/dist/types/elements/SwcAsync.d.ts.map +1 -1
- package/dist/types/elements/SwcChoose.d.ts +1 -1
- package/dist/types/elements/SwcChoose.d.ts.map +1 -1
- package/dist/types/elements/register.d.ts +3 -1
- package/dist/types/elements/register.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/utils/Utils.d.ts.map +1 -1
- package/dist/umd-bundle/dooboostore-simple-web-component.umd.js +87 -8
- package/dist/umd-bundle/dooboostore-simple-web-component.umd.js.map +4 -4
- package/package.json +1 -1
- package/src/decorators/elementDefine.ts +4 -0
- package/src/decorators/emitCustomEvent.ts +1 -0
- package/src/decorators/index.ts +1 -0
- package/src/decorators/setProperty.ts +97 -0
- package/src/elements/SwcAppMixin.ts +2 -2
- package/src/elements/SwcAsync.ts +5 -1
- package/src/elements/SwcChoose.ts +76 -37
- package/src/elements/register.ts +158 -39
- package/src/index.ts +3 -1
- package/src/utils/Utils.ts +15 -11
package/package.json
CHANGED
|
@@ -91,6 +91,7 @@ export const ensureInit = (inst: any) => {
|
|
|
91
91
|
|
|
92
92
|
// Call constructor script if present
|
|
93
93
|
const hostSet = SwcUtils.getHostSet(inst);
|
|
94
|
+
const appHosts = SwcUtils.findAllParentAppHostsDirect(inst);
|
|
94
95
|
inst._executeSwcScript?.('swc-on-constructor', hostSet);
|
|
95
96
|
|
|
96
97
|
// Call @onInitialize lifecycle methods
|
|
@@ -494,6 +495,9 @@ export const elementDefine =
|
|
|
494
495
|
(this as any).__swc_connected = true;
|
|
495
496
|
|
|
496
497
|
// Register to appHost LAST when all lifecycle completed
|
|
498
|
+
// console.log('!!!!!!!!!!!!!!', this, appHost)
|
|
499
|
+
const appHostss = SwcUtils.findAllParentAppHostsDirect(this)
|
|
500
|
+
// const nAppHostSet = SwcUtils.getHostSet(this as any);
|
|
497
501
|
if (appHost && typeof (appHost as any)._connected === 'function') {
|
|
498
502
|
(appHost as any)._connected(this);
|
|
499
503
|
} else if (appHost && typeof (appHost as any)) {
|
|
@@ -52,6 +52,7 @@ export function emitCustomEvent(selectorOrTarget: string, type: string, options:
|
|
|
52
52
|
const hostSet = SwcUtils.getHostSet(this as any);
|
|
53
53
|
const conf = getElementConfig(this);
|
|
54
54
|
const currentWin = (this as any)._resolveWindow?.(conf) || ((typeof window !== 'undefined' ? window : undefined) as Window);
|
|
55
|
+
// console.log('emitEEEEEEEEEEE', hostSet);
|
|
55
56
|
|
|
56
57
|
const eventTargets: EventTarget[] = [];
|
|
57
58
|
if (selectorOrTarget === ':window') eventTargets.push(currentWin);
|
package/src/decorators/index.ts
CHANGED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { ReflectUtils } from '@dooboostore/core';
|
|
2
|
+
import { ensureInit } from './elementDefine';
|
|
3
|
+
import { SwcUtils } from '../utils/Utils';
|
|
4
|
+
import { SwcQueryOptions } from '../types';
|
|
5
|
+
|
|
6
|
+
export interface PropertySetOptions extends SwcQueryOptions {
|
|
7
|
+
name?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface PropertySetMetadata {
|
|
11
|
+
propertyKey: string | symbol;
|
|
12
|
+
selector: string;
|
|
13
|
+
options: PropertySetOptions;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const PROPERTY_SET_METADATA_KEY = Symbol.for('simple-web-component:property-set');
|
|
17
|
+
|
|
18
|
+
export const setProperty = (selector: string, propertyNameOrOptions?: string | PropertySetOptions, options: PropertySetOptions = {}): MethodDecorator => {
|
|
19
|
+
return (target: Object, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
|
|
20
|
+
const constructor = target.constructor;
|
|
21
|
+
let metaList = ReflectUtils.getOwnMetadata(PROPERTY_SET_METADATA_KEY, constructor) as Map<string | symbol, PropertySetMetadata>;
|
|
22
|
+
if (!metaList) {
|
|
23
|
+
metaList = new Map<string | symbol, PropertySetMetadata>();
|
|
24
|
+
ReflectUtils.defineMetadata(PROPERTY_SET_METADATA_KEY, metaList, constructor);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let propertyName = typeof propertyNameOrOptions === 'string' ? propertyNameOrOptions : undefined;
|
|
28
|
+
let finalOptions = options;
|
|
29
|
+
|
|
30
|
+
if (typeof propertyNameOrOptions === 'object' && propertyNameOrOptions !== null) {
|
|
31
|
+
finalOptions = propertyNameOrOptions as PropertySetOptions;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
metaList.set(propertyKey, {
|
|
35
|
+
propertyKey,
|
|
36
|
+
selector,
|
|
37
|
+
options: { ...finalOptions, name: propertyName }
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const original = descriptor.value;
|
|
41
|
+
descriptor.value = async function (...args: any[]) {
|
|
42
|
+
ensureInit(this);
|
|
43
|
+
const res = await (original as any).apply(this, args);
|
|
44
|
+
if (res !== undefined) {
|
|
45
|
+
const win = (this as any).ownerDocument?.defaultView || window;
|
|
46
|
+
const host = this as unknown as HTMLElement;
|
|
47
|
+
const hostSet = { ...SwcUtils.getHelperAndHostSet(win, host), $this: this };
|
|
48
|
+
const r = finalOptions.root || 'auto';
|
|
49
|
+
const targetEls: HTMLElement[] = [];
|
|
50
|
+
|
|
51
|
+
const applyRoot = (t: any) => {
|
|
52
|
+
if (!t || !(t instanceof win.HTMLElement)) return;
|
|
53
|
+
targetEls.push(t);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
if (selector === ':parentHost') applyRoot(hostSet.$parentHost);
|
|
57
|
+
else if (selector === ':appHost') applyRoot(hostSet.$appHost);
|
|
58
|
+
else if (selector === ':firstHost') applyRoot(hostSet.$firstHost);
|
|
59
|
+
else if (selector === ':lastHost') applyRoot(hostSet.$lastHost);
|
|
60
|
+
else if (selector === ':firstAppHost') applyRoot(hostSet.$firstAppHost);
|
|
61
|
+
else if (selector === ':lastAppHost') applyRoot(hostSet.$lastAppHost);
|
|
62
|
+
else if (selector === ':hosts') hostSet.$hosts.forEach(applyRoot);
|
|
63
|
+
else if (selector === ':appHosts') hostSet.$appHosts.forEach(applyRoot);
|
|
64
|
+
else if (selector === ':host' || !selector) applyRoot(host);
|
|
65
|
+
else {
|
|
66
|
+
const targetRoot = r === 'auto' ? (host.shadowRoot || host) : (r === 'shadow' ? host.shadowRoot : host);
|
|
67
|
+
if (targetRoot) {
|
|
68
|
+
const found = targetRoot.querySelectorAll(selector);
|
|
69
|
+
if (found) targetEls.push(...(Array.from(found) as HTMLElement[]));
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
targetEls.forEach(targetEl => {
|
|
74
|
+
const resolvedRes = typeof res === 'function' ? (res as any)(targetEl, hostSet) : res;
|
|
75
|
+
|
|
76
|
+
if (propertyName) {
|
|
77
|
+
// Case: @setProperty(selector, 'propertyName')
|
|
78
|
+
(targetEl as any)[propertyName] = resolvedRes;
|
|
79
|
+
} else if (typeof resolvedRes === 'object' && resolvedRes !== null && !Array.isArray(resolvedRes)) {
|
|
80
|
+
// Case: @setProperty(selector) with Object return - assign multiple properties
|
|
81
|
+
Object.entries(resolvedRes as any).forEach(([prop, val]) => {
|
|
82
|
+
const finalVal = typeof val === 'function' ? val(targetEl, hostSet) : val;
|
|
83
|
+
(targetEl as any)[prop] = finalVal;
|
|
84
|
+
});
|
|
85
|
+
} else {
|
|
86
|
+
// Simple value - assign to 'value' property by default
|
|
87
|
+
(targetEl as any).value = resolvedRes;
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
};
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export const setPropertyHost = (propertyNameOrOptions?: string | PropertySetOptions, options: PropertySetOptions = {}): MethodDecorator => {
|
|
96
|
+
return setProperty(':host', propertyNameOrOptions, options);
|
|
97
|
+
};
|
|
@@ -70,14 +70,14 @@ export function SwcAppMixin<T extends { new (...args: any[]): HTMLElement }>(Bas
|
|
|
70
70
|
|
|
71
71
|
// Invoke method if conditions pass
|
|
72
72
|
if (pathMatched && filterPassed) {
|
|
73
|
-
instance[methodName]?.(re, pathData
|
|
73
|
+
instance[methodName]?.({...re, pathData: pathData});
|
|
74
74
|
}
|
|
75
75
|
});
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
_connected(instance: any) {
|
|
80
|
-
// console.log('appmixin--> connected', instance);
|
|
80
|
+
// console.log('appmixin--> connected', this, instance);
|
|
81
81
|
if (instance) {
|
|
82
82
|
this._swc_connected_instance.add(instance);
|
|
83
83
|
if (this.simpleApplication) {
|
package/src/elements/SwcAsync.ts
CHANGED
|
@@ -31,7 +31,10 @@ export class SwcAsync extends HTMLTemplateElement {
|
|
|
31
31
|
@changedAttributeHost('on-get-value')
|
|
32
32
|
private refresh() {
|
|
33
33
|
const script = this.getAttribute('on-get-value');
|
|
34
|
-
if (!script)
|
|
34
|
+
if (!script) {
|
|
35
|
+
this.render('default');
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
35
38
|
const win = this.ownerDocument?.defaultView || window;
|
|
36
39
|
const result = FunctionUtils.executeReturn({ script, context: this, args: SwcUtils.getHelperAndHostSet(win, this) });
|
|
37
40
|
|
|
@@ -85,6 +88,7 @@ export class SwcAsync extends HTMLTemplateElement {
|
|
|
85
88
|
}
|
|
86
89
|
|
|
87
90
|
connectedCallback() {
|
|
91
|
+
console.log('[SwcAsync] connectedCallback called');
|
|
88
92
|
this.refresh();
|
|
89
93
|
}
|
|
90
94
|
disconnectedCallback() {
|
|
@@ -5,6 +5,7 @@ import { FunctionUtils } from '@dooboostore/core';
|
|
|
5
5
|
export class SwcChoose extends HTMLTemplateElement {
|
|
6
6
|
private _nodes: Node[] = [];
|
|
7
7
|
private _value: any = null;
|
|
8
|
+
private _previousTemplate: HTMLTemplateElement | null = null;
|
|
8
9
|
|
|
9
10
|
get value(): any {
|
|
10
11
|
return this._value;
|
|
@@ -14,76 +15,114 @@ export class SwcChoose extends HTMLTemplateElement {
|
|
|
14
15
|
this.render();
|
|
15
16
|
}
|
|
16
17
|
|
|
17
|
-
private _resolvePortal(): Element | null {
|
|
18
|
-
const portalScript = this.getAttribute('on-get-portal');
|
|
19
|
-
if (!portalScript) return this.parentElement;
|
|
20
|
-
|
|
21
|
-
const win = this.ownerDocument?.defaultView || window;
|
|
22
|
-
const res = FunctionUtils.executeReturn({
|
|
23
|
-
script: portalScript,
|
|
24
|
-
context: this,
|
|
25
|
-
args: SwcUtils.getHelperAndHostSet(win, this)
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
if (res instanceof HTMLElement) return res;
|
|
29
|
-
if (typeof res === 'string') return win.document.querySelector(res);
|
|
30
|
-
return this.parentElement;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
18
|
private _executeCloneCallback(attr: string, args: any) {
|
|
34
19
|
const script = this.getAttribute(attr);
|
|
35
20
|
if (!script) return;
|
|
36
21
|
FunctionUtils.execute({ script, context: this, args });
|
|
37
22
|
}
|
|
38
23
|
|
|
39
|
-
@changedAttributeHost('
|
|
40
|
-
private refresh() {
|
|
41
|
-
|
|
42
|
-
if (
|
|
24
|
+
@changedAttributeHost('value')
|
|
25
|
+
private refresh(valueAttr?: string | null) {
|
|
26
|
+
valueAttr ??= this.getAttribute('value');
|
|
27
|
+
if (valueAttr === null) {
|
|
43
28
|
this.value = null;
|
|
44
29
|
return;
|
|
45
30
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
31
|
+
|
|
32
|
+
// {{ }} 브레이스로 감싸진 값은 스크립트 실행
|
|
33
|
+
if (valueAttr.startsWith('{{') && valueAttr.endsWith('}}')) {
|
|
34
|
+
const script = valueAttr.slice(2, -2).trim();
|
|
35
|
+
const win = this.ownerDocument?.defaultView || window;
|
|
36
|
+
const result = FunctionUtils.executeReturn({ script, context: this, args: SwcUtils.getHelperAndHostSet(win, this) });
|
|
37
|
+
this.value = result;
|
|
38
|
+
} else {
|
|
39
|
+
// 일반 문자열 값
|
|
40
|
+
this.value = valueAttr;
|
|
41
|
+
}
|
|
49
42
|
}
|
|
50
43
|
|
|
51
44
|
private render() {
|
|
52
|
-
this.cleanup();
|
|
53
|
-
const portal = this._resolvePortal();
|
|
54
|
-
if (!portal) return;
|
|
55
|
-
|
|
56
45
|
const win = this.ownerDocument?.defaultView || window;
|
|
57
46
|
const helperSet = SwcUtils.getHelperAndHostSet(win, this);
|
|
58
|
-
|
|
59
47
|
const templates = Array.from(this.content.querySelectorAll('template'));
|
|
60
48
|
let selected: HTMLTemplateElement | null = null;
|
|
49
|
+
|
|
61
50
|
for (const t of templates) {
|
|
62
|
-
if (t.getAttribute('is') === 'swc-when'
|
|
63
|
-
|
|
64
|
-
|
|
51
|
+
if (t.getAttribute('is') === 'swc-when') {
|
|
52
|
+
let matches = false;
|
|
53
|
+
const value = t.getAttribute('value') || '';
|
|
54
|
+
|
|
55
|
+
// {{ }} 브레이스로 감싸진 값은 스크립트 실행
|
|
56
|
+
if (value.startsWith('{{') && value.endsWith('}}')) {
|
|
57
|
+
const script = value.slice(2, -2).trim();
|
|
58
|
+
const result = FunctionUtils.executeReturn({
|
|
59
|
+
script,
|
|
60
|
+
context: t,
|
|
61
|
+
args: { ...helperSet, $value: this._value }
|
|
62
|
+
});
|
|
63
|
+
// console.log('rrrrrrrrrrrrrr', result);
|
|
64
|
+
matches = result === true;
|
|
65
|
+
} else {
|
|
66
|
+
// 일반 문자열 비교 (기존 방식)
|
|
67
|
+
matches = String(value) === String(this._value);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (matches) {
|
|
71
|
+
selected = t;
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
65
74
|
}
|
|
66
75
|
}
|
|
76
|
+
|
|
67
77
|
const target = selected || (this.content.querySelector('template[is="swc-otherwise"]') as HTMLTemplateElement);
|
|
68
78
|
|
|
79
|
+
// skip-if-same attribute가 있고 이전과 같은 template이면 렌더링 스킵
|
|
80
|
+
// 최상위 skip-if-same 또는 각 when/otherwise의 skip-if-same 체크
|
|
81
|
+
const targetHasSkipIfSame = target?.hasAttribute('skip-if-same');
|
|
82
|
+
const shouldSkip = (this.hasAttribute('skip-if-same') || targetHasSkipIfSame) && this._previousTemplate === target;
|
|
83
|
+
if (shouldSkip) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
this.cleanup();
|
|
88
|
+
this._previousTemplate = target;
|
|
89
|
+
|
|
69
90
|
if (target) {
|
|
70
91
|
const clone = target.content.cloneNode(true) as DocumentFragment;
|
|
71
92
|
this._nodes = Array.from(clone.childNodes);
|
|
72
93
|
const elements = this._nodes.filter(n => n.nodeType === 1) as HTMLElement[];
|
|
73
94
|
const groupArgs = { ...helperSet, $value: this._value, $nodes: this._nodes, $elements: elements, $firstElement: elements[0] };
|
|
74
95
|
|
|
96
|
+
// 복제된 최상위 요소들은 SwcChoose의 직접 자식이 될 것이므로 __swc_host 설정 안 함
|
|
75
97
|
this._nodes.forEach((n, nodeIdx) => {
|
|
76
|
-
if (n instanceof HTMLElement) (n as any).__swc_host = this;
|
|
98
|
+
// if (n instanceof HTMLElement) (n as any).__swc_host = this; // 제거: 부모 체인 유지
|
|
77
99
|
this._executeCloneCallback('on-clone-node', { ...groupArgs, $node: n, $nodeIndex: nodeIdx, $isElement: n.nodeType === 1 });
|
|
100
|
+
|
|
101
|
+
// Attribute {{ }} 치환 - HTML string 차원에서 동적 치환
|
|
102
|
+
if (n instanceof HTMLElement) {
|
|
103
|
+
Array.from(n.attributes).forEach(attr => {
|
|
104
|
+
const value = attr.value || '';
|
|
105
|
+
if (value.startsWith('{{') && value.endsWith('}}')) {
|
|
106
|
+
const script = value.slice(2, -2).trim();
|
|
107
|
+
const result = FunctionUtils.executeReturn({
|
|
108
|
+
script,
|
|
109
|
+
context: n,
|
|
110
|
+
args: { ...groupArgs, $value: this._value }
|
|
111
|
+
});
|
|
112
|
+
n.setAttribute(attr.name, String(result));
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
}
|
|
78
116
|
});
|
|
79
117
|
|
|
80
118
|
this._executeCloneCallback('on-clone-nodes', groupArgs);
|
|
81
119
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
120
|
+
this.parentElement?.insertBefore(clone, this.nextSibling);
|
|
121
|
+
// if (portal === this.parentElement) {
|
|
122
|
+
// portal.insertBefore(clone, this.nextSibling);
|
|
123
|
+
// } else {
|
|
124
|
+
// portal.appendChild(clone);
|
|
125
|
+
// }
|
|
87
126
|
}
|
|
88
127
|
}
|
|
89
128
|
|
package/src/elements/register.ts
CHANGED
|
@@ -4,7 +4,9 @@ import { SwcUtils } from '../utils/Utils';
|
|
|
4
4
|
import { FunctionUtils } from '@dooboostore/core';
|
|
5
5
|
import { changedAttributeHost } from '../decorators/changedAttributeHost';
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
type Constructor<T> = new (...args: any[]) => T;
|
|
8
|
+
|
|
9
|
+
export const registerAllElements = (w: any): Record<string, Constructor<HTMLElement>> => {
|
|
8
10
|
const {
|
|
9
11
|
HTMLElement: _HTMLElement,
|
|
10
12
|
HTMLTemplateElement: _HTMLTemplateElement,
|
|
@@ -374,6 +376,7 @@ export const registerAllElements = (w: any) => {
|
|
|
374
376
|
class SwcChoose extends HTMLTemplateElement {
|
|
375
377
|
private _nodes: Node[] = [];
|
|
376
378
|
private _value: any = null;
|
|
379
|
+
private _previousTemplate: HTMLTemplateElement | null = null;
|
|
377
380
|
|
|
378
381
|
get value(): any {
|
|
379
382
|
return this._value;
|
|
@@ -383,76 +386,114 @@ export const registerAllElements = (w: any) => {
|
|
|
383
386
|
this.render();
|
|
384
387
|
}
|
|
385
388
|
|
|
386
|
-
private _resolvePortal(): Element | null {
|
|
387
|
-
const portalScript = this.getAttribute('on-get-portal');
|
|
388
|
-
if (!portalScript) return this.parentElement;
|
|
389
|
-
|
|
390
|
-
const win = this.ownerDocument?.defaultView || window;
|
|
391
|
-
const res = FunctionUtils.executeReturn({
|
|
392
|
-
script: portalScript,
|
|
393
|
-
context: this,
|
|
394
|
-
args: SwcUtils.getHelperAndHostSet(win, this)
|
|
395
|
-
});
|
|
396
|
-
|
|
397
|
-
if (res instanceof HTMLElement) return res;
|
|
398
|
-
if (typeof res === 'string') return win.document.querySelector(res);
|
|
399
|
-
return this.parentElement;
|
|
400
|
-
}
|
|
401
|
-
|
|
402
389
|
private _executeCloneCallback(attr: string, args: any) {
|
|
403
390
|
const script = this.getAttribute(attr);
|
|
404
391
|
if (!script) return;
|
|
405
392
|
FunctionUtils.execute({ script, context: this, args });
|
|
406
393
|
}
|
|
407
394
|
|
|
408
|
-
@changedAttributeHost('
|
|
409
|
-
private refresh() {
|
|
410
|
-
|
|
411
|
-
if (
|
|
395
|
+
@changedAttributeHost('value')
|
|
396
|
+
private refresh(valueAttr?: string | null) {
|
|
397
|
+
valueAttr ??= this.getAttribute('value');
|
|
398
|
+
if (valueAttr === null) {
|
|
412
399
|
this.value = null;
|
|
413
400
|
return;
|
|
414
401
|
}
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
402
|
+
|
|
403
|
+
// {{ }} 브레이스로 감싸진 값은 스크립트 실행
|
|
404
|
+
if (valueAttr.startsWith('{{') && valueAttr.endsWith('}}')) {
|
|
405
|
+
const script = valueAttr.slice(2, -2).trim();
|
|
406
|
+
const win = this.ownerDocument?.defaultView || window;
|
|
407
|
+
const result = FunctionUtils.executeReturn({ script, context: this, args: SwcUtils.getHelperAndHostSet(win, this) });
|
|
408
|
+
this.value = result;
|
|
409
|
+
} else {
|
|
410
|
+
// 일반 문자열 값
|
|
411
|
+
this.value = valueAttr;
|
|
412
|
+
}
|
|
418
413
|
}
|
|
419
414
|
|
|
420
415
|
private render() {
|
|
421
|
-
this.cleanup();
|
|
422
|
-
const portal = this._resolvePortal();
|
|
423
|
-
if (!portal) return;
|
|
424
|
-
|
|
425
416
|
const win = this.ownerDocument?.defaultView || window;
|
|
426
417
|
const helperSet = SwcUtils.getHelperAndHostSet(win, this);
|
|
427
|
-
|
|
428
418
|
const templates = Array.from(this.content.querySelectorAll('template'));
|
|
429
419
|
let selected: HTMLTemplateElement | null = null;
|
|
420
|
+
|
|
430
421
|
for (const t of templates) {
|
|
431
|
-
if (t.getAttribute('is') === 'swc-when'
|
|
432
|
-
|
|
433
|
-
|
|
422
|
+
if (t.getAttribute('is') === 'swc-when') {
|
|
423
|
+
let matches = false;
|
|
424
|
+
const value = t.getAttribute('value') || '';
|
|
425
|
+
|
|
426
|
+
// {{ }} 브레이스로 감싸진 값은 스크립트 실행
|
|
427
|
+
if (value.startsWith('{{') && value.endsWith('}}')) {
|
|
428
|
+
const script = value.slice(2, -2).trim();
|
|
429
|
+
const result = FunctionUtils.executeReturn({
|
|
430
|
+
script,
|
|
431
|
+
context: t,
|
|
432
|
+
args: { ...helperSet, $value: this._value }
|
|
433
|
+
});
|
|
434
|
+
// console.log('rrrrrrrrrrrrrr', result);
|
|
435
|
+
matches = result === true;
|
|
436
|
+
} else {
|
|
437
|
+
// 일반 문자열 비교 (기존 방식)
|
|
438
|
+
matches = String(value) === String(this._value);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
if (matches) {
|
|
442
|
+
selected = t;
|
|
443
|
+
break;
|
|
444
|
+
}
|
|
434
445
|
}
|
|
435
446
|
}
|
|
447
|
+
|
|
436
448
|
const target = selected || (this.content.querySelector('template[is="swc-otherwise"]') as HTMLTemplateElement);
|
|
437
449
|
|
|
450
|
+
// skip-if-same attribute가 있고 이전과 같은 template이면 렌더링 스킵
|
|
451
|
+
// 최상위 skip-if-same 또는 각 when/otherwise의 skip-if-same 체크
|
|
452
|
+
const targetHasSkipIfSame = target?.hasAttribute('skip-if-same');
|
|
453
|
+
const shouldSkip = (this.hasAttribute('skip-if-same') || targetHasSkipIfSame) && this._previousTemplate === target;
|
|
454
|
+
if (shouldSkip) {
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
this.cleanup();
|
|
459
|
+
this._previousTemplate = target;
|
|
460
|
+
|
|
438
461
|
if (target) {
|
|
439
462
|
const clone = target.content.cloneNode(true) as DocumentFragment;
|
|
440
463
|
this._nodes = Array.from(clone.childNodes);
|
|
441
464
|
const elements = this._nodes.filter(n => n.nodeType === 1) as HTMLElement[];
|
|
442
465
|
const groupArgs = { ...helperSet, $value: this._value, $nodes: this._nodes, $elements: elements, $firstElement: elements[0] };
|
|
443
466
|
|
|
467
|
+
// 복제된 최상위 요소들은 SwcChoose의 직접 자식이 될 것이므로 __swc_host 설정 안 함
|
|
444
468
|
this._nodes.forEach((n, nodeIdx) => {
|
|
445
|
-
if (n instanceof HTMLElement) (n as any).__swc_host = this;
|
|
469
|
+
// if (n instanceof HTMLElement) (n as any).__swc_host = this; // 제거: 부모 체인 유지
|
|
446
470
|
this._executeCloneCallback('on-clone-node', { ...groupArgs, $node: n, $nodeIndex: nodeIdx, $isElement: n.nodeType === 1 });
|
|
471
|
+
|
|
472
|
+
// Attribute {{ }} 치환 - HTML string 차원에서 동적 치환
|
|
473
|
+
if (n instanceof HTMLElement) {
|
|
474
|
+
Array.from(n.attributes).forEach(attr => {
|
|
475
|
+
const value = attr.value || '';
|
|
476
|
+
if (value.startsWith('{{') && value.endsWith('}}')) {
|
|
477
|
+
const script = value.slice(2, -2).trim();
|
|
478
|
+
const result = FunctionUtils.executeReturn({
|
|
479
|
+
script,
|
|
480
|
+
context: n,
|
|
481
|
+
args: { ...groupArgs, $value: this._value }
|
|
482
|
+
});
|
|
483
|
+
n.setAttribute(attr.name, String(result));
|
|
484
|
+
}
|
|
485
|
+
});
|
|
486
|
+
}
|
|
447
487
|
});
|
|
448
488
|
|
|
449
489
|
this._executeCloneCallback('on-clone-nodes', groupArgs);
|
|
450
490
|
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
491
|
+
this.parentElement?.insertBefore(clone, this.nextSibling);
|
|
492
|
+
// if (portal === this.parentElement) {
|
|
493
|
+
// portal.insertBefore(clone, this.nextSibling);
|
|
494
|
+
// } else {
|
|
495
|
+
// portal.appendChild(clone);
|
|
496
|
+
// }
|
|
456
497
|
}
|
|
457
498
|
}
|
|
458
499
|
|
|
@@ -499,7 +540,10 @@ export const registerAllElements = (w: any) => {
|
|
|
499
540
|
@changedAttributeHost('on-get-value')
|
|
500
541
|
private refresh() {
|
|
501
542
|
const script = this.getAttribute('on-get-value');
|
|
502
|
-
if (!script)
|
|
543
|
+
if (!script) {
|
|
544
|
+
this.render('default');
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
503
547
|
const win = this.ownerDocument?.defaultView || window;
|
|
504
548
|
const result = FunctionUtils.executeReturn({ script, context: this, args: SwcUtils.getHelperAndHostSet(win, this) });
|
|
505
549
|
|
|
@@ -553,6 +597,7 @@ export const registerAllElements = (w: any) => {
|
|
|
553
597
|
}
|
|
554
598
|
|
|
555
599
|
connectedCallback() {
|
|
600
|
+
console.log('[SwcAsync] connectedCallback called');
|
|
556
601
|
this.refresh();
|
|
557
602
|
}
|
|
558
603
|
disconnectedCallback() {
|
|
@@ -732,4 +777,78 @@ export const registerAllElements = (w: any) => {
|
|
|
732
777
|
@elementDefine('swc-app-body', { extends: 'body', window: w })
|
|
733
778
|
class SwcAppBody extends SwcAppMixin(HTMLBodyElement) {}
|
|
734
779
|
|
|
780
|
+
|
|
781
|
+
return {
|
|
782
|
+
// Sub Templates
|
|
783
|
+
SwcLoading,
|
|
784
|
+
SwcError,
|
|
785
|
+
SwcSuccess,
|
|
786
|
+
SwcWhen,
|
|
787
|
+
SwcOtherwise,
|
|
788
|
+
SwcDefault,
|
|
789
|
+
// Core Elements
|
|
790
|
+
SwcApp,
|
|
791
|
+
SwcIf,
|
|
792
|
+
SwcLoop,
|
|
793
|
+
SwcChoose,
|
|
794
|
+
SwcAsync,
|
|
795
|
+
// App Elements
|
|
796
|
+
SwcAppAnchor,
|
|
797
|
+
SwcAppArea,
|
|
798
|
+
SwcAppAudio,
|
|
799
|
+
SwcAppBase,
|
|
800
|
+
SwcAppButton,
|
|
801
|
+
SwcAppCanvas,
|
|
802
|
+
SwcAppData,
|
|
803
|
+
SwcAppDataList,
|
|
804
|
+
SwcAppDetails,
|
|
805
|
+
SwcAppDialog,
|
|
806
|
+
SwcAppDiv,
|
|
807
|
+
SwcAppDList,
|
|
808
|
+
SwcAppEmbed,
|
|
809
|
+
SwcAppFieldSet,
|
|
810
|
+
SwcAppForm,
|
|
811
|
+
SwcAppHR,
|
|
812
|
+
SwcAppIFrame,
|
|
813
|
+
SwcAppImage,
|
|
814
|
+
SwcAppInput,
|
|
815
|
+
SwcAppLabel,
|
|
816
|
+
SwcAppLegend,
|
|
817
|
+
SwcAppLI,
|
|
818
|
+
SwcAppLink,
|
|
819
|
+
SwcAppMap,
|
|
820
|
+
SwcAppMeta,
|
|
821
|
+
SwcAppMeter,
|
|
822
|
+
SwcAppMod,
|
|
823
|
+
SwcAppObject,
|
|
824
|
+
SwcAppOList,
|
|
825
|
+
SwcAppOptGroup,
|
|
826
|
+
SwcAppOption,
|
|
827
|
+
SwcAppOutput,
|
|
828
|
+
SwcAppParagraph,
|
|
829
|
+
SwcAppParam,
|
|
830
|
+
SwcAppPicture,
|
|
831
|
+
SwcAppPre,
|
|
832
|
+
SwcAppProgress,
|
|
833
|
+
SwcAppQuote,
|
|
834
|
+
SwcAppScript,
|
|
835
|
+
SwcAppSelect,
|
|
836
|
+
SwcAppSlot,
|
|
837
|
+
SwcAppSource,
|
|
838
|
+
SwcAppSpan,
|
|
839
|
+
SwcAppStyle,
|
|
840
|
+
SwcAppTable,
|
|
841
|
+
SwcAppTableSection,
|
|
842
|
+
SwcAppTableCell,
|
|
843
|
+
SwcAppTemplate,
|
|
844
|
+
SwcAppTextArea,
|
|
845
|
+
SwcAppTime,
|
|
846
|
+
SwcAppTitle,
|
|
847
|
+
SwcAppTableRow,
|
|
848
|
+
SwcAppTrack,
|
|
849
|
+
SwcAppUList,
|
|
850
|
+
SwcAppVideo,
|
|
851
|
+
SwcAppHeading,
|
|
852
|
+
SwcAppBody,
|
|
853
|
+
};
|
|
735
854
|
};
|
package/src/index.ts
CHANGED
|
@@ -16,12 +16,14 @@ export * from './config/config';
|
|
|
16
16
|
|
|
17
17
|
// Registration Factory
|
|
18
18
|
export default (w: Window, other?: (((w: Window) => ConstructorType<any>) | any) []) => {
|
|
19
|
-
registerAllElements(w);
|
|
19
|
+
const cr = registerAllElements(w);
|
|
20
20
|
const r = other?.map(fn => {
|
|
21
21
|
if (typeof fn === 'function') {
|
|
22
22
|
const a = fn(w);
|
|
23
23
|
} else {
|
|
24
24
|
}
|
|
25
25
|
});
|
|
26
|
+
|
|
27
|
+
return cr;
|
|
26
28
|
// return r??[];
|
|
27
29
|
};
|
package/src/utils/Utils.ts
CHANGED
|
@@ -255,16 +255,9 @@ export namespace SwcUtils {
|
|
|
255
255
|
export const findAllParentAppHostsDirect = (el: HTMLElement): SwcAppInterface[] => {
|
|
256
256
|
const appHosts: SwcAppInterface[] = [];
|
|
257
257
|
|
|
258
|
-
//
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
if (rootNode instanceof ShadowRoot) {
|
|
262
|
-
// Element is inside shadow DOM, start from shadow root's host
|
|
263
|
-
current = (rootNode as any).host as HTMLElement;
|
|
264
|
-
} else {
|
|
265
|
-
// Element is in light DOM, start from parent
|
|
266
|
-
current = el.parentElement;
|
|
267
|
-
}
|
|
258
|
+
// Shadow DOM 내부의 depth를 정상적으로 탐색하기 위해
|
|
259
|
+
// el.parentElement부터 시작하고, shadow root 경계를 만날 때만 host로 이동
|
|
260
|
+
let current: HTMLElement | null = el.parentElement;
|
|
268
261
|
|
|
269
262
|
while (current) {
|
|
270
263
|
// console.log('vvvvvvv');
|
|
@@ -280,7 +273,17 @@ export namespace SwcUtils {
|
|
|
280
273
|
if (current.shadowRoot) {
|
|
281
274
|
current = ((current.shadowRoot as any).host as HTMLElement).parentElement;
|
|
282
275
|
} else {
|
|
283
|
-
|
|
276
|
+
// 부모가 없으면 shadow root 경계일 수 있음
|
|
277
|
+
if (!current.parentElement) {
|
|
278
|
+
const parentNode = current.parentNode;
|
|
279
|
+
if (parentNode instanceof ShadowRoot) {
|
|
280
|
+
current = (parentNode as any).host as HTMLElement;
|
|
281
|
+
} else {
|
|
282
|
+
break;
|
|
283
|
+
}
|
|
284
|
+
} else {
|
|
285
|
+
current = current.parentElement;
|
|
286
|
+
}
|
|
284
287
|
}
|
|
285
288
|
}
|
|
286
289
|
return appHosts.reverse(); // [root, ..., closest-parent]
|
|
@@ -316,6 +319,7 @@ export namespace SwcUtils {
|
|
|
316
319
|
}
|
|
317
320
|
|
|
318
321
|
const $appHosts = SwcUtils.findAllParentAppHostsDirect(el); //$hosts.filter(h => h.tagName.toLowerCase() === 'swc-app' || h.getAttribute('is')?.startsWith('swc-app-')) as SwcAppInterface[];
|
|
322
|
+
// console.log('apphosts00000000', $appHosts)
|
|
319
323
|
const $appHost = $appHosts.length > 0 ? ($appHosts[$appHosts.length - 1] === el ? ($appHosts.length > 1 ? $appHosts[$appHosts.length - 2] : null) : $appHosts[$appHosts.length - 1]) : null;
|
|
320
324
|
const $firstAppHost = $appHosts.length > 0 ? $appHosts[0] : null;
|
|
321
325
|
|