@web-atoms/web-controls 2.1.412 → 2.1.414

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.
@@ -0,0 +1,316 @@
1
+ import { App } from "@web-atoms/core/dist/App";
2
+ import { AtomDisposableList } from "@web-atoms/core/dist/core/AtomDisposableList";
3
+ import Colors from "@web-atoms/core/dist/core/Colors";
4
+ import sleep from "@web-atoms/core/dist/core/sleep";
5
+ import { CancelToken } from "@web-atoms/core/dist/core/types";
6
+ import XNode from "@web-atoms/core/dist/core/XNode";
7
+ import StyleRule, { ContentAlignType } from "@web-atoms/core/dist/style/StyleRule";
8
+ import { AtomControl, ElementValueSetters } from "@web-atoms/core/dist/web/controls/AtomControl";
9
+ import { IPopupOptions } from "@web-atoms/core/dist/web/services/PopupService";
10
+ import CSS from "@web-atoms/core/dist/web/styles/CSS";
11
+ import IElement from "./IElement";
12
+
13
+ CSS(StyleRule()
14
+ .position("absolute")
15
+ .borderRadius(5)
16
+ .padding(5)
17
+ .border("solid 1px lightgray")
18
+ .defaultBoxShadow()
19
+ .zIndex(5000)
20
+ .backgroundColor("var(--primary-bg, white)")
21
+ , "*[data-inline-popup=inline-popup]");
22
+
23
+ function closeHandler(
24
+ opener: HTMLElement,
25
+ container: HTMLElement, close) {
26
+ let handler: any = null;
27
+ const body = document.body;
28
+ handler = (e: Event) => {
29
+ let start = e.target as HTMLElement;
30
+ if (e.defaultPrevented) {
31
+ return;
32
+ }
33
+ while (start) {
34
+ if (start === body) {
35
+ break;
36
+ }
37
+ if (start === opener) {
38
+ return;
39
+ }
40
+ if (start === container) {
41
+ return;
42
+ }
43
+ start = start.parentElement;
44
+ }
45
+ close();
46
+ e.preventDefault();
47
+ e.stopImmediatePropagation?.();
48
+ };
49
+ document.body.addEventListener("click", handler, true);
50
+ return () => document.body.removeEventListener("click", handler, true);
51
+ }
52
+
53
+ export default class InlinePopup extends AtomControl {
54
+
55
+ public static async show<T = void>(
56
+ target: HTMLElement | AtomControl, node: XNode, options: IPopupOptions = {}) {
57
+
58
+ const targetElement = ((target as any).element ?? target) as HTMLElement;
59
+
60
+ const control = (target as any).element ? target as AtomControl : AtomControl.from(target as any);
61
+
62
+ const targetStyle = window.getComputedStyle(targetElement);
63
+ if (!/fixed|absolute|relative/i.test(targetStyle.position)) {
64
+ targetElement.style.position = "relative";
65
+ }
66
+
67
+ await sleep(10);
68
+
69
+ const container = document.createElement("div");
70
+ container.setAttribute("data-inline-popup", "inline-popup");
71
+
72
+ const alignment = options.alignment ?? "bottomLeft";
73
+ switch (alignment) {
74
+ case "bottomLeft":
75
+ container.style.top = `${targetElement.offsetHeight}px`;
76
+ container.style.left = "0px";
77
+ break;
78
+ case "bottomRight":
79
+ container.style.top = `${targetElement.offsetHeight}px`;
80
+ container.style.right = "0px";
81
+ break;
82
+ case "topRight":
83
+ container.style.top = "0px";
84
+ container.style.left = `${targetElement.offsetWidth}px`;
85
+ break;
86
+ default:
87
+ container.style.top = `${targetElement.offsetHeight}px`;
88
+ container.style.left = "0px";
89
+ break;
90
+ }
91
+
92
+ container._logicalParent = targetElement;
93
+
94
+ // @ts-ignore
95
+ control.render(<div> {node} </div>, container, control);
96
+
97
+ targetElement.insertAdjacentElement("beforeend", container);
98
+
99
+ return await new Promise<T>((resolve, reject) => {
100
+
101
+ const disposables = new AtomDisposableList();
102
+
103
+ let resolved = false;
104
+
105
+ const close = (r?) => {
106
+ if (resolved) {
107
+ return;
108
+ }
109
+ resolved = true;
110
+ resolve(r);
111
+ disposables.dispose();
112
+ };
113
+
114
+ const cancel = (r = "cancelled") => {
115
+ if (resolved) {
116
+ return;
117
+ }
118
+ resolved = true;
119
+ reject(r);
120
+ disposables.dispose();
121
+ };
122
+
123
+ const firstChild = (container.firstElementChild as HTMLElement).atomControl;
124
+
125
+ if (firstChild instanceof InlinePopup) {
126
+ firstChild.cancel = cancel;
127
+ firstChild.close = close;
128
+ }
129
+
130
+ const defaultClose = options.onClick === "close" ? close : cancel;
131
+
132
+ const observer = new MutationObserver(() => {
133
+ if (!container.isConnected) {
134
+ defaultClose();
135
+ }
136
+ });
137
+ observer.observe(targetElement, { childList: true });
138
+ disposables.add(() => {
139
+ observer.disconnect();
140
+ control.dispose(container);
141
+ container.remove();
142
+ });
143
+
144
+ if (options.onClick) {
145
+ disposables.add(control.bindEvent(container, "click", async () => {
146
+ await sleep(200);
147
+ defaultClose();
148
+ }));
149
+ }
150
+
151
+ options.cancelToken?.registerForCancel(cancel);
152
+
153
+ disposables.add(closeHandler(targetElement, container, defaultClose));
154
+
155
+ });
156
+ }
157
+
158
+ public static showControl<T>(target: HTMLElement | AtomControl, options: IPopupOptions = {}) {
159
+ const node = XNode.create(this, {});
160
+ return this.show<T>(target, node, options);
161
+ }
162
+
163
+ public close: (r?) => void;
164
+
165
+ public cancel: (r?) => void;
166
+
167
+ protected dispatchClickEvent(e: MouseEvent, data: any) {
168
+ let start = this.element.parentElement;
169
+ while (start) {
170
+ const atomControl = AtomControl.from(start);
171
+ if (atomControl) {
172
+ (atomControl as any).dispatchClickEvent(e, data);
173
+ return;
174
+ }
175
+ start = start.parentElement;
176
+ }
177
+ super.dispatchClickEvent(e, data);
178
+ }
179
+ }
180
+
181
+ export interface IInlinePopupButtonOptions extends IElement {
182
+ text?: any;
183
+ label?: any;
184
+ icon?: any;
185
+ hasBorder?: boolean;
186
+ nodes?: XNode[];
187
+ onClick?: "close" | "cancel";
188
+ popup?: PopupFactory;
189
+ }
190
+
191
+ CSS(StyleRule()
192
+ .flexLayout({ alignItems: "center", inline: true, justifyContent: "default"})
193
+ .flexWrap("wrap")
194
+ .padding(3)
195
+ .paddingLeft(5)
196
+ .paddingRight(5)
197
+ .and(StyleRule("[data-has-border=false]")
198
+ .border("none")
199
+ .backgroundColor(Colors.transparent)
200
+ )
201
+ , "*[data-inline-popup-button=inline-popup-button]");
202
+
203
+ export type PopupFactory = (data) => XNode;
204
+
205
+ document.body.addEventListener("click", (e) => {
206
+
207
+ let start = e.target as HTMLElement;
208
+ let popupFactory: PopupFactory;
209
+ while (start) {
210
+ popupFactory = (start as any).popupFactory;
211
+ if (popupFactory) {
212
+ // stop...
213
+ break;
214
+ }
215
+ start = start.parentElement;
216
+ }
217
+
218
+ if (!start) {
219
+ return;
220
+ }
221
+
222
+ const control = AtomControl.from(start) as any;
223
+ const app = control.app as App;
224
+ const target = start;
225
+ const element = control.element;
226
+ let itemIndex;
227
+ let data;
228
+ if (control.items && control.itemRenderer) {
229
+ // this is atom repeater
230
+ while (start && start !== element) {
231
+ itemIndex ??= start.dataset.itemIndex;
232
+ if (itemIndex) {
233
+ data = control.items[itemIndex];
234
+ break;
235
+ }
236
+ start = start.parentElement;
237
+ }
238
+ }
239
+
240
+ if (!data) {
241
+ data = new Proxy(target, {
242
+ get(t, p, receiver) {
243
+ let s = target;
244
+ while (s) {
245
+ const v = s.dataset[p as string];
246
+ if (v !== void 0) {
247
+ return v;
248
+ }
249
+ s = s.parentElement;
250
+ }
251
+ },
252
+ });
253
+ }
254
+
255
+ const node = popupFactory(data);
256
+
257
+ app.runAsync(() => InlinePopup.show(target, node));
258
+
259
+ });
260
+
261
+ ElementValueSetters["data-popup-class"] = (c, e, v) => {
262
+ (e as any).popupFactory = v;
263
+ };
264
+
265
+ export function InlinePopupButton(
266
+ {
267
+ text,
268
+ label,
269
+ icon,
270
+ hasBorder = false,
271
+ nodes = [],
272
+ onClick = "close",
273
+ popup,
274
+ ... a
275
+ }: IInlinePopupButtonOptions,
276
+ ... popupNodes: XNode[]) {
277
+
278
+ if (popup) {
279
+ return <button
280
+ data-popup-class={popup}
281
+ data-has-border={!!hasBorder}
282
+ data-inline-popup-button="inline-popup-button"
283
+ { ... a}>
284
+ {icon && <i class={icon}/>}
285
+ {text && <span text={text}/>}
286
+ {label && <label text={text}/>}
287
+ { ... nodes}
288
+ </button>;
289
+ }
290
+
291
+ let isOpen = false;
292
+ const done = () => isOpen = false;
293
+ const click = async (e: MouseEvent) => {
294
+ if (isOpen || e.defaultPrevented) {
295
+ return;
296
+ }
297
+ const popupNode = popupNodes.length > 1 ? <div>{... popupNodes }</div> : popupNodes[0];
298
+ try {
299
+ isOpen = true;
300
+ await InlinePopup.show(
301
+ e.currentTarget as any, popupNode, { onClick });
302
+ } finally {
303
+ done();
304
+ }
305
+ };
306
+ return <button
307
+ event-click={click}
308
+ data-has-border={!!hasBorder}
309
+ data-inline-popup-button="inline-popup-button"
310
+ { ... a}>
311
+ {icon && <i class={icon}/>}
312
+ {text && <span text={text}/>}
313
+ {label && <label text={text}/>}
314
+ { ... nodes}
315
+ </button>;
316
+ }
@@ -5,6 +5,7 @@ import StyleRule from "@web-atoms/core/dist/style/StyleRule";
5
5
  import { AtomControl } from "@web-atoms/core/dist/web/controls/AtomControl";
6
6
  import PopupService, { IPopup, IPopupOptions } from "@web-atoms/core/dist/web/services/PopupService";
7
7
  import CSS from "@web-atoms/core/dist/web/styles/CSS";
8
+ import InlinePopup, { InlinePopupButton } from "./InlinePopup";
8
9
 
9
10
  CSS(StyleRule()
10
11
  .padding(5)
@@ -66,80 +67,82 @@ const iconLabelCss = CSS(
66
67
  )
67
68
  );
68
69
 
69
- export default function PopupButton(
70
- {
71
- icon,
72
- label,
73
- showAsDialog,
74
- ... others
75
- }: IPopupButton,
76
- ... menus: Array<IMenuItem | XNode>) {
77
-
78
- let popup: IPopup = null;
79
- function openPopup(s: AtomControl, e: Event) {
80
- const button = e.currentTarget as HTMLElement;
81
- button.classList.add("pressed");
82
- if (popup) {
83
- popup.dispose();
84
- popup = null;
85
- return;
86
- }
87
-
88
- const menu = document.createElement("div");
89
- (s as any).render(<div data-menu-items="menu-items">
90
- { ... menus}
91
- </div>, menu);
92
-
93
- const options: IPopupOptions = showAsDialog
94
- ? {
95
- alignment: "centerOfScreen",
96
- popupStyle: ".none"
97
- }
98
- : {
99
- alignment: "bottomLeft",
100
- popupStyle: ".none"
101
- };
102
- popup = PopupService.show(button, menu, options);
103
-
104
- const clickHandler = (e) => {
105
- let start = e.target as HTMLElement;
106
- const body = document.body;
107
- while (start) {
108
- if (start === body) {
109
- return;
110
- }
111
- if (start.dataset.menuItem === "menu-item") {
112
- break;
113
- }
114
- start = start.parentElement;
115
- }
116
- popup?.dispose();
117
- popup = null;
118
- };
119
-
120
- menu.addEventListener("click", clickHandler);
121
-
122
- popup.registerDisposable(() => {
123
- button.classList.remove("pressed");
124
- menu.removeEventListener("click", clickHandler);
125
- popup = null;
126
- });
127
- }
128
-
129
- if (label) {
130
- return <button
131
- { ... others }
132
- eventClick={Bind.event((s, e) => openPopup(s as AtomControl, e))}>
133
- <label class={iconLabelCss}>
134
- <i class={icon}/>
135
- <span>{label}</span>
136
- </label>
137
- </button>;
138
- }
139
-
140
- return <button
141
- { ... others }
142
- eventClick={Bind.event((s, e) => openPopup(s as AtomControl, e))}>
143
- <i class={icon}/>
144
- </button>;
145
- }
70
+ export default InlinePopupButton;
71
+
72
+ // export default function PopupButton(
73
+ // {
74
+ // icon,
75
+ // label,
76
+ // showAsDialog,
77
+ // ... others
78
+ // }: IPopupButton,
79
+ // ... menus: Array<IMenuItem | XNode>) {
80
+
81
+ // let popup: IPopup = null;
82
+ // function openPopup(s: AtomControl, e: Event) {
83
+ // const button = e.currentTarget as HTMLElement;
84
+ // button.classList.add("pressed");
85
+ // if (popup) {
86
+ // popup.dispose();
87
+ // popup = null;
88
+ // return;
89
+ // }
90
+
91
+ // const menu = document.createElement("div");
92
+ // (s as any).render(<div data-menu-items="menu-items">
93
+ // { ... menus}
94
+ // </div>, menu);
95
+
96
+ // const options: IPopupOptions = showAsDialog
97
+ // ? {
98
+ // alignment: "centerOfScreen",
99
+ // popupStyle: ".none"
100
+ // }
101
+ // : {
102
+ // alignment: "bottomLeft",
103
+ // popupStyle: ".none"
104
+ // };
105
+ // popup = PopupService.show(button, menu, options);
106
+
107
+ // const clickHandler = (e) => {
108
+ // let start = e.target as HTMLElement;
109
+ // const body = document.body;
110
+ // while (start) {
111
+ // if (start === body) {
112
+ // return;
113
+ // }
114
+ // if (start.dataset.menuItem === "menu-item") {
115
+ // break;
116
+ // }
117
+ // start = start.parentElement;
118
+ // }
119
+ // popup?.dispose();
120
+ // popup = null;
121
+ // };
122
+
123
+ // menu.addEventListener("click", clickHandler);
124
+
125
+ // popup.registerDisposable(() => {
126
+ // button.classList.remove("pressed");
127
+ // menu.removeEventListener("click", clickHandler);
128
+ // popup = null;
129
+ // });
130
+ // }
131
+
132
+ // if (label) {
133
+ // return <button
134
+ // { ... others }
135
+ // eventClick={Bind.event((s, e) => openPopup(s as AtomControl, e))}>
136
+ // <label class={iconLabelCss}>
137
+ // <i class={icon}/>
138
+ // <span>{label}</span>
139
+ // </label>
140
+ // </button>;
141
+ // }
142
+
143
+ // return <button
144
+ // { ... others }
145
+ // eventClick={Bind.event((s, e) => openPopup(s as AtomControl, e))}>
146
+ // <i class={icon}/>
147
+ // </button>;
148
+ // }
@@ -92,6 +92,10 @@ function preventLinkClick(e: Event, editor: HTMLElement, doc: Document) {
92
92
  break;
93
93
  }
94
94
  if (target.isContentEditable) {
95
+ editor.dispatchEvent(new MouseEvent("click", {
96
+ bubbles: true,
97
+ cancelable: true
98
+ }));
95
99
  return;
96
100
  }
97
101
  if (target.tagName === "A") {
@@ -116,6 +120,7 @@ function preventLinkClick(e: Event, editor: HTMLElement, doc: Document) {
116
120
  }
117
121
  }
118
122
  });
123
+
119
124
  editor.dispatchEvent(new CustomEvent("editorClick", {
120
125
  detail: {
121
126
  target,
@@ -125,6 +130,12 @@ function preventLinkClick(e: Event, editor: HTMLElement, doc: Document) {
125
130
  cancelable: true
126
131
  } ));
127
132
 
133
+ editor.dispatchEvent(new MouseEvent("click", {
134
+ bubbles: true,
135
+ cancelable: true
136
+ }));
137
+
138
+
128
139
  // while (target && target !== body) {
129
140
 
130
141
  // const ds = target.dataset;
@@ -283,13 +294,15 @@ export default class AtomHtmlEditor extends AtomControl {
283
294
  if (!doc) {
284
295
  return;
285
296
  }
286
- for (const { name, style } of this.tags) {
287
- if (style) {
288
- const styleElement = doc.createElement("style");
289
- styleElement.textContent = `*[data-command=${name}] {
290
- ${style.toStyleSheet()}
291
- }`
292
- doc.head.appendChild(styleElement);
297
+ if (Array.isArray(this.tags)) {
298
+ for (const { name, style } of this.tags) {
299
+ if (style) {
300
+ const styleElement = doc.createElement("style");
301
+ styleElement.textContent = `*[data-command=${name}] {
302
+ ${style.toStyleSheet()}
303
+ }`
304
+ doc.head.appendChild(styleElement);
305
+ }
293
306
  }
294
307
  }
295
308
  }