Skip to content

Commit 79a9fdb

Browse files
committed
feat: render modAI elements in shadow dom
1 parent 41bcafd commit 79a9fdb

File tree

18 files changed

+817
-738
lines changed

18 files changed

+817
-738
lines changed

_build/gpm.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: modAI
2-
version: 0.11.0-alpha11
2+
version: 0.11.0-alpha13
33
lowCaseName: modai
44
namespace: modAI
55
author: 'John Peca'

_build/js/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { globalState } from './ui/localChat/state';
88
export type Config = {
99
name?: string;
1010
apiURL: string;
11+
cssURL: string;
1112
};
1213

1314
export const init = (config: Config) => {

_build/js/src/resource.ts

Lines changed: 67 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,15 @@ import { ui } from './ui';
22

33
const attachImagePlus = (imgPlusPanel: Element, fieldName: string) => {
44
const imagePlus = Ext.getCmp(imgPlusPanel.firstElementChild?.id);
5+
const label =
6+
imagePlus.el.dom.parentElement?.parentElement?.parentElement?.querySelector('label');
57

6-
const imageWand = ui.generateButton.localChat({
8+
if (!label) {
9+
return;
10+
}
11+
12+
ui.generateButton.localChat({
13+
targetEl: label,
714
key: fieldName,
815
field: fieldName,
916
type: 'image',
@@ -21,6 +28,7 @@ const attachImagePlus = (imgPlusPanel: Element, fieldName: string) => {
2128
});
2229

2330
const altTextWand = ui.generateButton.vision({
31+
targetEl: imagePlus.altTextField.el.dom,
2432
input: imagePlus.altTextField.items.items[0].el.dom,
2533
field: fieldName,
2634
image: imagePlus.imagePreview.el.dom,
@@ -36,25 +44,24 @@ const attachImagePlus = (imgPlusPanel: Element, fieldName: string) => {
3644
imagePlus.altTextField.el.dom.style.display = 'flex';
3745
imagePlus.altTextField.el.dom.style.justifyItems = 'center';
3846
imagePlus.altTextField.el.dom.style.alignItems = 'center';
39-
40-
imagePlus.el.dom.parentElement?.parentElement?.parentElement
41-
?.querySelector('label')
42-
?.appendChild(imageWand);
43-
imagePlus.altTextField.el.dom.appendChild(altTextWand);
4447
};
4548

4649
const attachContent = () => {
4750
const cmp = Ext.getCmp('modx-resource-content');
4851
const label = cmp.el.dom.querySelector('label');
49-
label?.appendChild(
50-
ui.generateButton.localChat({
51-
key: 'res.content',
52-
field: 'res.content',
53-
type: 'text',
54-
availableTypes: ['text', 'image'],
55-
resource: MODx.request.id,
56-
}),
57-
);
52+
53+
if (!label) {
54+
return;
55+
}
56+
57+
ui.generateButton.localChat({
58+
targetEl: label,
59+
key: 'res.content',
60+
field: 'res.content',
61+
type: 'text',
62+
availableTypes: ['text', 'image'],
63+
resource: MODx.request.id,
64+
});
5865
};
5966

6067
const attachTVs = (config: Config) => {
@@ -83,38 +90,40 @@ const attachTVs = (config: Config) => {
8390
if (!label) return;
8491

8592
if (prompt) {
86-
label.appendChild(
87-
ui.generateButton.forcedText({
88-
input: field.el.dom,
89-
resourceId: MODx.request.id,
90-
field: fieldName,
91-
initialValue: field.getValue(),
92-
onChange: (data, noStore) => {
93-
const prevValue = field.getValue();
94-
field.setValue(data.value);
95-
field.fireEvent('change', field, data.value, prevValue);
96-
97-
if (noStore) {
98-
field.el.dom.scrollTop = field.el.dom.scrollHeight;
99-
}
100-
},
101-
}),
102-
);
93+
ui.generateButton.forcedText({
94+
targetEl: label,
95+
input: field.el.dom,
96+
resourceId: MODx.request.id,
97+
field: fieldName,
98+
initialValue: field.getValue(),
99+
onChange: (data, noStore) => {
100+
const prevValue = field.getValue();
101+
field.setValue(data.value);
102+
field.fireEvent('change', field, data.value, prevValue);
103+
104+
if (noStore) {
105+
field.el.dom.scrollTop = field.el.dom.scrollHeight;
106+
}
107+
},
108+
});
103109
} else {
104-
label.appendChild(
105-
ui.generateButton.localChat({
106-
key: fieldName,
107-
field: fieldName,
108-
type: 'text',
109-
availableTypes: ['text', 'image'],
110-
resource: MODx.request.id,
111-
}),
112-
);
110+
ui.generateButton.localChat({
111+
targetEl: label,
112+
key: fieldName,
113+
field: fieldName,
114+
type: 'text',
115+
availableTypes: ['text', 'image'],
116+
resource: MODx.request.id,
117+
});
113118
}
114119
}
115120

116121
if (field.xtype === 'modx-panel-tv-image') {
117-
const imageWand = ui.generateButton.localChat({
122+
const label = wrapper.dom.querySelector('label');
123+
if (!label) return;
124+
125+
ui.generateButton.localChat({
126+
targetEl: label,
118127
key: fieldName,
119128
field: fieldName,
120129
type: 'image',
@@ -135,11 +144,6 @@ const attachTVs = (config: Config) => {
135144
},
136145
},
137146
});
138-
139-
const label = wrapper.dom.querySelector('label');
140-
if (!label) return;
141-
142-
label.appendChild(imageWand);
143147
}
144148
}
145149
};
@@ -166,23 +170,22 @@ const attachResourceFields = (config: Config) => {
166170
fieldsMap[field].forEach((cmpId) => {
167171
const fieldEl = Ext.getCmp(cmpId);
168172
if (fieldEl) {
169-
fieldEl.label.appendChild(
170-
ui.generateButton.forcedText({
171-
resourceId: MODx.request.id,
172-
field: `res.${field}`,
173-
input: fieldEl.el.dom,
174-
initialValue: fieldEl.getValue(),
175-
onChange: (data, noStore) => {
176-
const prevValue = fieldEl.getValue();
177-
fieldEl.setValue(data.value);
178-
fieldEl.fireEvent('change', fieldEl, data.value, prevValue);
179-
180-
if (noStore) {
181-
fieldEl.el.dom.scrollTop = fieldEl.el.dom.scrollHeight;
182-
}
183-
},
184-
}),
185-
);
173+
ui.generateButton.forcedText({
174+
targetEl: fieldEl.label,
175+
resourceId: MODx.request.id,
176+
field: `res.${field}`,
177+
input: fieldEl.el.dom,
178+
initialValue: fieldEl.getValue(),
179+
onChange: (data, noStore) => {
180+
const prevValue = fieldEl.getValue();
181+
fieldEl.setValue(data.value);
182+
fieldEl.fireEvent('change', fieldEl, data.value, prevValue);
183+
184+
if (noStore) {
185+
fieldEl.el.dom.scrollTop = fieldEl.el.dom.scrollHeight;
186+
}
187+
},
188+
});
186189
}
187190
});
188191
}

_build/js/src/ui/cofirmDialog/index.ts

Lines changed: 26 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { button } from '../dom/button';
2+
import { createModAIShadow } from '../dom/modAIShadow';
23
import { createElement } from '../utils';
34

45
import type { Button } from '../dom/button';
@@ -21,13 +22,23 @@ export const confirmDialog = (config: ConfirmDialogOptions) => {
2122
...config,
2223
};
2324

25+
const { shadow, shadowRoot } = createModAIShadow(false, () => {
26+
if (config.showConfirm) {
27+
confirmBtn.focus();
28+
}
29+
30+
if (!config.showConfirm && config.showCancel) {
31+
cancelBtn.focus();
32+
}
33+
});
34+
2435
const cancelBtn = button(
2536
config.cancelText ?? 'Cancel',
2637
() => {
2738
closeDialog();
2839
},
2940
'cancelBtn',
30-
{ tabIndex: 0 },
41+
{ tabIndex: -1 },
3142
);
3243
const confirmBtn = button(
3344
config.confirmText,
@@ -36,7 +47,7 @@ export const confirmDialog = (config: ConfirmDialogOptions) => {
3647
config.onConfirm();
3748
},
3849
'confirmBtn',
39-
{ tabIndex: 0 },
50+
{ tabIndex: -1 },
4051
);
4152

4253
const overlay = createElement(
@@ -67,13 +78,23 @@ export const confirmDialog = (config: ConfirmDialogOptions) => {
6778
const destroyDialog = () => {
6879
document.removeEventListener('keydown', handleDialogKeyDown);
6980
overlay.remove();
81+
shadow.remove();
7082
};
7183

7284
const closeDialog = () => {
7385
destroyDialog();
7486
config.onCancel?.();
7587
};
7688

89+
const focusableElements: Button[] = [];
90+
if (config.showCancel) {
91+
focusableElements.push(cancelBtn);
92+
}
93+
94+
if (config.showConfirm) {
95+
focusableElements.push(confirmBtn);
96+
}
97+
7798
const handleDialogKeyDown = (e: KeyboardEvent) => {
7899
if (e.key === 'Escape') {
79100
e.stopImmediatePropagation();
@@ -83,16 +104,7 @@ export const confirmDialog = (config: ConfirmDialogOptions) => {
83104
}
84105

85106
if (e.key === 'Tab') {
86-
const focusableElements = [cancelBtn, confirmBtn];
87-
if (config.showCancel) {
88-
focusableElements.push(cancelBtn);
89-
}
90-
91-
if (config.showConfirm) {
92-
focusableElements.push(confirmBtn);
93-
}
94-
95-
const focusedElement = document.activeElement;
107+
const focusedElement = shadowRoot.activeElement;
96108
let currentIndex = focusedElement ? focusableElements.indexOf(focusedElement as Button) : -1;
97109

98110
if (e.shiftKey) {
@@ -111,13 +123,6 @@ export const confirmDialog = (config: ConfirmDialogOptions) => {
111123

112124
document.addEventListener('keydown', handleDialogKeyDown);
113125

114-
document.body.append(overlay);
115-
116-
if (config.showConfirm) {
117-
confirmBtn.focus();
118-
}
119-
120-
if (!config.showConfirm && config.showCancel) {
121-
cancelBtn.focus();
122-
}
126+
shadowRoot.appendChild(overlay);
127+
document.body.append(shadow);
123128
};

_build/js/src/ui/dom/modAIShadow.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { globalState } from '../localChat/state';
2+
import { createElement } from '../utils';
3+
4+
export const createModAIShadow = <R extends HTMLElement>(
5+
inline?: boolean,
6+
onLoad?: () => void,
7+
): {
8+
shadow: R;
9+
shadowRoot: ShadowRoot;
10+
} => {
11+
const shadow = createElement('div') as unknown as R;
12+
const shadowRoot = shadow.attachShadow({ mode: 'open' });
13+
14+
shadow.style.display = 'none';
15+
16+
const link = createElement('link', undefined, '', {
17+
rel: 'stylesheet',
18+
type: 'text/css',
19+
href: globalState.config.cssURL,
20+
});
21+
22+
shadowRoot.appendChild(link);
23+
24+
link.onload = () => {
25+
if (inline) {
26+
shadow.style.display = 'inline-block';
27+
} else {
28+
shadow.style.display = '';
29+
}
30+
31+
if (onLoad) {
32+
onLoad();
33+
}
34+
};
35+
36+
return { shadow, shadowRoot };
37+
};

0 commit comments

Comments
 (0)