@web-atoms/web-controls 2.1.216 → 2.1.217

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.
@@ -1029,77 +1029,125 @@ export default class AtomRepeater extends AtomControl {
1029
1029
  this.refreshItem(item, (ce as any).promise);
1030
1030
  }
1031
1031
  }
1032
- }
1033
1032
 
1034
- function onElementClick(e: Event) {
1035
- let target = e.target as HTMLElement;
1036
- const originalTarget = target;
1037
- let eventName;
1038
- let repeater: AtomRepeater;
1039
- let index;
1040
- let type;
1041
- let recreate;
1042
- while (target) {
1043
- const a = target.atomControl;
1044
- if (a !== undefined && a instanceof AtomRepeater) {
1045
- repeater = a;
1046
- break;
1047
- }
1048
- if (index === undefined) {
1049
- const itemIndex = target.dataset.itemIndex;
1050
- if (itemIndex !== void 0) {
1051
- // tslint:disable-next-line: no-bitwise
1052
- index = ~~itemIndex;
1053
- }
1054
- }
1055
- if (type === undefined) {
1056
- const itemType = target.dataset.header ?? target.dataset.footer;
1057
- if (itemType !== void 0) {
1058
- type = itemType;
1059
- }
1060
- }
1061
- if (eventName === undefined) {
1062
- const itemClickEvent = target.dataset.clickEvent;
1063
- if (itemClickEvent) {
1064
- eventName = itemClickEvent.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
1065
- }
1033
+ protected dispatchClickEvent(e: MouseEvent, data: any): void {
1034
+ let {
1035
+ clickEvent = "itemClick",
1036
+ // tslint:disable-next-line: prefer-const
1037
+ recreate,
1038
+ // tslint:disable-next-line: prefer-const
1039
+ header,
1040
+ // tslint:disable-next-line: prefer-const
1041
+ footer,
1042
+ // tslint:disable-next-line: prefer-const
1043
+ itemIndex
1044
+ } = data;
1045
+ clickEvent = clickEvent.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
1046
+ if (header) {
1047
+ this.dispatchHeaderFooterEvent(clickEvent, header, e.target);
1048
+ return;
1066
1049
  }
1067
- if (recreate === undefined) {
1068
- recreate = target.dataset.recreate;
1050
+ if (footer) {
1051
+ this.dispatchHeaderFooterEvent(clickEvent, header, e.target);
1052
+ return;
1069
1053
  }
1070
- target = target.parentElement as HTMLElement;
1071
- }
1072
-
1073
- if (index === undefined) {
1074
- if (type !== undefined) {
1075
- (repeater as any).dispatchHeaderFooterEvent(eventName, type, originalTarget);
1054
+ if (itemIndex === void 0 || itemIndex === null) {
1055
+ return;
1076
1056
  }
1077
- return;
1078
- }
1079
-
1080
- // tslint:disable-next-line: no-bitwise
1081
- const item = repeater.items[~~index];
1082
- if (eventName === "itemSelect" || eventName === "itemDeselect") {
1083
- const si = repeater.selectedItems ??= [];
1084
- if (si) {
1085
- index = si.indexOf(item);
1086
- if (index === -1) {
1087
- if (repeater.allowMultipleSelection) {
1088
- si.add(item);
1057
+ // tslint:disable-next-line: no-bitwise
1058
+ let index = ~~itemIndex;
1059
+ const item = this.items[index];
1060
+ if (clickEvent === "itemSelect" || clickEvent === "itemDeselect") {
1061
+ const si = this.selectedItems ??= [];
1062
+ if (si) {
1063
+ index = si.indexOf(item);
1064
+ if (index === -1) {
1065
+ if (this.allowMultipleSelection) {
1066
+ si.add(item);
1067
+ } else {
1068
+ si.set(0, item);
1069
+ }
1089
1070
  } else {
1090
- si.set(0, item);
1071
+ si.removeAt(index);
1091
1072
  }
1092
- } else {
1093
- si.removeAt(index);
1094
1073
  }
1095
1074
  }
1096
- }
1097
- if (item) {
1098
- (repeater as any).dispatchItemEvent(eventName, item, recreate, originalTarget);
1075
+ if (item) {
1076
+ this.dispatchItemEvent(clickEvent, item, recreate, e.target);
1077
+ }
1078
+
1099
1079
  }
1100
1080
  }
1101
1081
 
1102
- document.body.addEventListener("click", onElementClick, true);
1082
+ // function onElementClick(e: Event) {
1083
+ // let target = e.target as HTMLElement;
1084
+ // const originalTarget = target;
1085
+ // let eventName;
1086
+ // let repeater: AtomRepeater;
1087
+ // let index;
1088
+ // let type;
1089
+ // let recreate;
1090
+ // while (target) {
1091
+ // const a = target.atomControl;
1092
+ // if (a !== undefined && a instanceof AtomRepeater) {
1093
+ // repeater = a;
1094
+ // break;
1095
+ // }
1096
+ // if (index === undefined) {
1097
+ // const itemIndex = target.dataset.itemIndex;
1098
+ // if (itemIndex !== void 0) {
1099
+ // // tslint:disable-next-line: no-bitwise
1100
+ // index = ~~itemIndex;
1101
+ // }
1102
+ // }
1103
+ // if (type === undefined) {
1104
+ // const itemType = target.dataset.header ?? target.dataset.footer;
1105
+ // if (itemType !== void 0) {
1106
+ // type = itemType;
1107
+ // }
1108
+ // }
1109
+ // if (eventName === undefined) {
1110
+ // const itemClickEvent = target.dataset.clickEvent;
1111
+ // if (itemClickEvent) {
1112
+ // eventName = itemClickEvent.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
1113
+ // }
1114
+ // }
1115
+ // if (recreate === undefined) {
1116
+ // recreate = target.dataset.recreate;
1117
+ // }
1118
+ // target = target.parentElement as HTMLElement;
1119
+ // }
1120
+
1121
+ // if (index === undefined) {
1122
+ // if (type !== undefined) {
1123
+ // (repeater as any).dispatchHeaderFooterEvent(eventName, type, originalTarget);
1124
+ // }
1125
+ // return;
1126
+ // }
1127
+
1128
+ // // tslint:disable-next-line: no-bitwise
1129
+ // const item = repeater.items[~~index];
1130
+ // if (eventName === "itemSelect" || eventName === "itemDeselect") {
1131
+ // const si = repeater.selectedItems ??= [];
1132
+ // if (si) {
1133
+ // index = si.indexOf(item);
1134
+ // if (index === -1) {
1135
+ // if (repeater.allowMultipleSelection) {
1136
+ // si.add(item);
1137
+ // } else {
1138
+ // si.set(0, item);
1139
+ // }
1140
+ // } else {
1141
+ // si.removeAt(index);
1142
+ // }
1143
+ // }
1144
+ // }
1145
+ // if (item) {
1146
+ // (repeater as any).dispatchItemEvent(eventName, item, recreate, originalTarget);
1147
+ // }
1148
+ // }
1149
+
1150
+ // document.body.addEventListener("click", onElementClick, true);
1103
1151
 
1104
1152
  let hoverItem = {
1105
1153
  repeater: null,
@@ -0,0 +1,360 @@
1
+ import Bind from "@web-atoms/core/dist/core/Bind";
2
+ import { BindableProperty } from "@web-atoms/core/dist/core/BindableProperty";
3
+ import Colors from "@web-atoms/core/dist/core/Colors";
4
+ import XNode from "@web-atoms/core/dist/core/XNode";
5
+ import StyleRule from "@web-atoms/core/dist/style/StyleRule";
6
+ import { AtomControl } from "@web-atoms/core/dist/web/controls/AtomControl";
7
+ import CSS from "@web-atoms/core/dist/web/styles/CSS";
8
+ import { ChildEnumerator } from "@web-atoms/core/dist/web/core/AtomUI";
9
+
10
+
11
+ CSS(StyleRule()
12
+ .display("grid")
13
+ .gridTemplateRows("auto 1fr auto")
14
+ .gridTemplateColumns("auto 1fr auto auto")
15
+ .backgroundColor(Colors.black)
16
+ .child(StyleRule("[data-element=video]")
17
+ .gridRowStart("1")
18
+ .gridRowEnd("span 3")
19
+ .gridColumnStart("1")
20
+ .gridColumnEnd("span 3")
21
+ .alignSelf("stretch")
22
+ .justifySelf("stretch")
23
+ )
24
+ .child(StyleRule("[data-element=play-element]")
25
+ .gridRowStart("1")
26
+ .gridRowEnd("span 3")
27
+ .gridColumnStart("1")
28
+ .gridColumnEnd("span 3")
29
+ .alignSelf("stretch")
30
+ .justifySelf("stretch")
31
+ .flexLayout({ justifyContent: "center"})
32
+ .child(StyleRule("button.play")
33
+ .display("inline-flex")
34
+ .alignItems("center")
35
+ .justifyContent("center")
36
+ .color(Colors.white)
37
+ .backgroundColor(Colors.blue)
38
+ .borderRadius(9999)
39
+ .fontSize(25)
40
+ .padding(10)
41
+ .width(50)
42
+ .height(50)
43
+ .textAlign("center")
44
+ .verticalAlign("middle")
45
+ .child(StyleRule("i")
46
+ .marginLeft(4)
47
+ )
48
+ )
49
+ )
50
+ .child(StyleRule("[data-element=progress]")
51
+ .zIndex("11")
52
+ .gridRowStart("2")
53
+ .gridColumnStart("1")
54
+ .gridColumnEnd("span 3")
55
+ .alignSelf("flex-end")
56
+ .height(4)
57
+ .justifySelf("stretch" as any)
58
+ .backgroundColor(Colors.black)
59
+ .width("100%")
60
+ .cursor("pointer")
61
+ )
62
+ .child(StyleRule("[data-element=toolbar]")
63
+ .zIndex("10")
64
+ .gridRowStart("3")
65
+ .gridColumnStart("1")
66
+ .gridColumnEnd("span 3")
67
+ .backgroundColor(Colors.black.withAlphaPercent(0.3))
68
+ .color(Colors.white)
69
+ .flexLayout({
70
+ justifyContent: "flex-start"
71
+ })
72
+ .child(StyleRule("*")
73
+ .minWidth(20)
74
+ .marginLeft(5)
75
+ .padding(5)
76
+ )
77
+ .child(StyleRule("[data-style=button]")
78
+ .width(20)
79
+ )
80
+ .child(StyleRule("[data-font-size=small]")
81
+ .fontSize("x-small")
82
+ )
83
+ .child(StyleRule("[data-element=volume-range]")
84
+ .height(2)
85
+ .color(Colors.green)
86
+ )
87
+ .child(StyleRule("[data-element=full-screen]")
88
+ .marginLeft("auto")
89
+ .marginRight(5)
90
+ )
91
+ )
92
+ .and(StyleRule("[data-controls=true]")
93
+ .child(StyleRule("[data-element=toolbar]")
94
+ .display("flex")
95
+ )
96
+ .child(StyleRule("[data-element=progress]")
97
+ .display("flex")
98
+ )
99
+ )
100
+ .and(StyleRule("[data-state=pause]")
101
+ .child(StyleRule("[data-element=toolbar]")
102
+ .display("flex")
103
+ )
104
+ .child(StyleRule("[data-element=toolbar]")
105
+ .child(StyleRule("[data-element=pause]")
106
+ .display("none")
107
+ )
108
+ )
109
+ )
110
+ .and(StyleRule("[data-state=play]")
111
+ .child(StyleRule("[data-element=play-element]")
112
+ .display("none")
113
+ )
114
+ .child(StyleRule("[data-element=toolbar]")
115
+ .child(StyleRule("[data-element=play]")
116
+ .display("none")
117
+ )
118
+ )
119
+ .and(StyleRule("[data-controls=false]")
120
+ .child(StyleRule("[data-element=toolbar]")
121
+ .display("none")
122
+ )
123
+ .child(StyleRule("[data-element=progress]")
124
+ .display("none")
125
+ )
126
+ )
127
+ )
128
+ , "*[data-video-player=video-player]");
129
+
130
+ const gatherElements = (e: HTMLElement, data = {}) => {
131
+ const ce = ChildEnumerator.enumerate(e);
132
+ for (const iterator of ce) {
133
+ const elementName = iterator.dataset.element?.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
134
+ if (elementName) {
135
+ data[elementName] = iterator;
136
+ }
137
+ gatherElements(iterator, data);
138
+ }
139
+ return data;
140
+ }
141
+
142
+ const numberToText = (n: number) => {
143
+ if (n < 10) {
144
+ return "0" + n;
145
+ }
146
+ return n.toString();
147
+ };
148
+
149
+ const durationText = (n: number, total: number) => {
150
+ if (n === null || n === undefined) {
151
+ return "";
152
+ }
153
+ const minutes = Math.floor(n / 60);
154
+ const seconds = numberToText(Math.ceil(n % 60));
155
+ const totalMinutes = Math.floor(total / 60);
156
+ const totalSeconds = numberToText(Math.ceil(total % 60));
157
+ return `${minutes}:${seconds} / ${totalMinutes}:${totalSeconds}`;
158
+ };
159
+
160
+ const noSoundIcon = "fa-duotone fa-volume-slash",
161
+ mute = "fa-duotone fa-volume-xmark",
162
+ low = "fa-duotone fa-volume-low",
163
+ mid = "fa-duotone fa-volume",
164
+ high = "fa-duotone fa-volume-high";
165
+
166
+ export default class AtomVideoPlayer extends AtomControl {
167
+
168
+ @BindableProperty
169
+ public source: any;
170
+
171
+ public duration: number;
172
+
173
+ public time: number;
174
+
175
+ private video: HTMLVideoElement;
176
+
177
+ private progress: HTMLCanvasElement;
178
+
179
+ private poster: HTMLImageElement;
180
+
181
+ private currentTimeSpan: HTMLSpanElement;
182
+ private soundIcon: HTMLElement;
183
+ private volumeRange: HTMLInputElement;
184
+
185
+ public onPropertyChanged(name: keyof AtomVideoPlayer): void {
186
+ switch (name) {
187
+ case "source":
188
+ this.updateSource();
189
+ break;
190
+ }
191
+ }
192
+
193
+ protected create(): void {
194
+ this.element.dataset.videoPlayer = "video-player";
195
+ this.bindEvent(this.element, "togglePlay", (e: CustomEvent) => {
196
+ if (this.video.paused) {
197
+ this.video.play();
198
+ } else {
199
+ this.video.pause();
200
+ }
201
+ });
202
+ this.bindEvent(this.element, "volume", (e: CustomEvent) => {
203
+ this.video.muted = !this.video.muted;
204
+ this.updateVolume();
205
+ });
206
+ this.bindEvent(this.element, "fullScreen", (e: CustomEvent) => {
207
+ const f = e.target as HTMLElement;
208
+ if (this.element === document.fullscreenElement) {
209
+ document.exitFullscreen();
210
+ return;
211
+ }
212
+ document.onfullscreenchange = () => {
213
+ if (document.fullscreenElement !== this.element) {
214
+ f.className = "fa-solid fa-expand";
215
+ document.onfullscreenchange = undefined;
216
+ }
217
+ };
218
+ this.element.requestFullscreen({ navigationUI: "show" });
219
+ f.className = "fa-solid fa-compress";
220
+ });
221
+ this.render(<div data-click-event="toggle-play" data-state="pause">
222
+ <video
223
+ event-abort={() => this.element.dataset.state = "abort"}
224
+ event-durationchange={(e: Event) => this.duration = this.video.duration}
225
+ event-ended={() => this.element.dataset.state = "ended"}
226
+ event-loadedmetadata={() => {
227
+ this.duration = this.video.duration;
228
+ this.updateVolume();
229
+ this.currentTimeSpan.textContent = durationText(0, this.duration);
230
+ this.updateProgress();
231
+ }}
232
+ event-pause={() => this.element.dataset.state = "pause"}
233
+ event-play={() => this.element.dataset.state = "play"}
234
+ event-progress={(e) => this.updateProgress()}
235
+ event-timeupdate={() => {
236
+ this.time = this.video.currentTime;
237
+ this.currentTimeSpan.textContent = durationText(this.time, this.duration);
238
+ this.element.dataset.state = "play";
239
+ this.updateProgress();
240
+ }}
241
+ event-waiting={() => this.element.dataset.state = "waiting"}
242
+ event-volumechange={() => this.updateVolume()}
243
+ autoplay={false}
244
+ data-element="video"/>
245
+ <canvas
246
+ data-element="progress"
247
+ />
248
+ <img data-element="poster"/>
249
+ <div data-element="toolbar">
250
+ <i
251
+ data-element="play"
252
+ data-style="button"
253
+ class="fa-solid fa-play"/>
254
+ <i
255
+ data-element="pause"
256
+ data-style="button"
257
+ class="fa-solid fa-pause"/>
258
+ <i
259
+ data-click-event="volume"
260
+ data-style="button"
261
+ data-element="sound"
262
+ class="fa-duotone fa-volume-slash"></i>
263
+ <input
264
+ data-click-event="none"
265
+ data-element="volume-range"
266
+ type="range"
267
+ min={0}
268
+ max={1}
269
+ step={0.1}
270
+ />
271
+ <span
272
+ data-font-size="small"
273
+ data-element="current" text="0:00"/>
274
+ <i
275
+ data-click-event="full-screen"
276
+ data-style="button"
277
+ data-element="full-screen"
278
+ class="fa-solid fa-expand"></i>
279
+ </div>
280
+ <div
281
+ data-element="play-element">
282
+ <button class="play">
283
+ <i class="fa-solid fa-play"/>
284
+ </button>
285
+ </div>
286
+ </div>);
287
+
288
+ const all = gatherElements(this.element) as any;
289
+ this.video = all.video;
290
+ this.progress = all.progress;
291
+ this.currentTimeSpan = all.current;
292
+ this.soundIcon = all.sound;
293
+ this.volumeRange = all.volumeRange;
294
+ this.bindEvent(this.volumeRange, "input", () => {
295
+ setTimeout(() => {
296
+ this.video.volume = parseFloat(this.volumeRange.value);
297
+ }, 1);
298
+ });
299
+ this.bindEvent(this.element, "pointerenter", () => {
300
+ this.element.dataset.controls = "true";
301
+ });
302
+ this.bindEvent(this.element, "pointerleave", () => {
303
+ this.element.dataset.controls = "false";
304
+ });
305
+ this.bindEvent(this.progress, "click", (e: MouseEvent) => {
306
+ e.preventDefault();
307
+ const scale = e.clientX / this.progress.clientWidth;
308
+ this.video.currentTime = this.video.duration * scale;
309
+ });
310
+ }
311
+
312
+ private updateProgress() {
313
+ const context = this.progress.getContext("2d");
314
+ // context.fillStyle = "rgba(0,0,0,0)";
315
+ context.strokeStyle = "rgba(0,0,0,0)";
316
+ const width = this.progress.clientWidth;
317
+ const height = this.progress.clientHeight;
318
+ this.progress.width = width;
319
+ this.progress.height = height;
320
+ context.clearRect(0,0, width, height);
321
+ const max = this.video.duration;
322
+ const seekable = this.video.buffered;
323
+ const scale = width / max;
324
+ context.fillStyle = "rgba(255,255,255,0.5)";
325
+ for (let index = 0; index < seekable.length; index++) {
326
+ const start = seekable.start(index) * scale;
327
+ const end = seekable.end(index) * scale;
328
+ context.fillRect(start, 0, end, height);
329
+ }
330
+ context.fillStyle = "#ffffff";
331
+ context.fillRect(0, 0, this.video.currentTime * scale, height);
332
+ }
333
+
334
+ private updateVolume() {
335
+ if (this.video.muted) {
336
+ this.soundIcon.className = mute;
337
+ this.volumeRange.style.display = "none";
338
+ this.soundIcon.title = "Unmute";
339
+ return;
340
+ }
341
+ const audio = this.video.volume;
342
+ this.volumeRange.style.display = "";
343
+ this.volumeRange.value = audio?.toString();
344
+ this.soundIcon.title = "Mute";
345
+ if (audio > 0.8) {
346
+ this.soundIcon.className = high;
347
+ return;
348
+ }
349
+ if (audio < 0.2) {
350
+ this.soundIcon.className = low;
351
+ return;
352
+ }
353
+ this.soundIcon.className = mid;
354
+ }
355
+
356
+ protected updateSource() {
357
+ this.video.src = this.source;
358
+ }
359
+
360
+ }
@@ -0,0 +1,21 @@
1
+ import XNode from "@web-atoms/core/dist/core/XNode";
2
+ import StyleRule from "@web-atoms/core/dist/style/StyleRule";
3
+ import CSS from "@web-atoms/core/dist/web/styles/CSS";
4
+
5
+ CSS(StyleRule()
6
+ .height(5)
7
+ .child(StyleRule("*")
8
+ .position("absolute")
9
+ .left(0)
10
+ .top(0)
11
+ )
12
+ , "*[data-track-progress=track-progress]");
13
+
14
+ export default function TrackProgress(a) {
15
+ return <div
16
+ data-track-progress="track-progress">
17
+ <div class="available"/>
18
+ <div class="done"/>
19
+ <div class="thumb"/>
20
+ </div>;
21
+ }