@xwadex/fesd 0.0.1

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.
Files changed (47) hide show
  1. package/20240328-video4-setting.png +0 -0
  2. package/CHANGELOG.md +41 -0
  3. package/README.md +25 -0
  4. package/dist/assets/fesd-bundle.css +9 -0
  5. package/dist/assets/fesd-bundle.js +9800 -0
  6. package/dist/assets/fesd-bundle.js.map +1 -0
  7. package/index.html +25 -0
  8. package/package.json +23 -0
  9. package/prepros.config +883 -0
  10. package/src/fesd/anchor4/anchor4.js +179 -0
  11. package/src/fesd/aost4/_aost4.sass +64 -0
  12. package/src/fesd/aost4/aost4.js +138 -0
  13. package/src/fesd/article4/article4.js +280 -0
  14. package/src/fesd/article4/article4.md +1 -0
  15. package/src/fesd/category-slider/_category-slider.sass +33 -0
  16. package/src/fesd/category-slider/category-slider.js +332 -0
  17. package/src/fesd/collapse4/collapse4.js +159 -0
  18. package/src/fesd/detect4/detect4.js +70 -0
  19. package/src/fesd/dropdown4/_dropdown4.sass +185 -0
  20. package/src/fesd/dropdown4/cityData.js +830 -0
  21. package/src/fesd/dropdown4/dropdown4.js +647 -0
  22. package/src/fesd/image-preview/_image-preview.sass +26 -0
  23. package/src/fesd/image-preview/image-preview.js +209 -0
  24. package/src/fesd/image-validate/_image-validate.sass +21 -0
  25. package/src/fesd/image-validate/image-validate.js +84 -0
  26. package/src/fesd/marquee4/_marquee4.sass +45 -0
  27. package/src/fesd/marquee4/marquee4.js +371 -0
  28. package/src/fesd/modal4/_modal4.sass +134 -0
  29. package/src/fesd/modal4/modal4.js +236 -0
  30. package/src/fesd/modal4/modernModal.js +182 -0
  31. package/src/fesd/multipurpose4/_multipurpose4.sass +282 -0
  32. package/src/fesd/multipurpose4/multipurpose4.js +562 -0
  33. package/src/fesd/ripple4/_ripple4.sass +44 -0
  34. package/src/fesd/ripple4/ripple4.js +138 -0
  35. package/src/fesd/share4/share4.js +191 -0
  36. package/src/fesd/shared/shared.js +59 -0
  37. package/src/fesd/shared/utils.js +98 -0
  38. package/src/fesd/tab4/_tab4.sass +25 -0
  39. package/src/fesd/tab4/tab4.js +473 -0
  40. package/src/fesd/video4/README.md +3 -0
  41. package/src/fesd/video4/_video4.sass +117 -0
  42. package/src/fesd/video4/video4.js +237 -0
  43. package/src/fesd/video4/videoPlayer.js +195 -0
  44. package/src/fesd.js +53 -0
  45. package/src/fesd.sass +29 -0
  46. package/src/fesdDB.js +282 -0
  47. package/vite.config.js +37 -0
@@ -0,0 +1,562 @@
1
+ import { isNodeList, insert } from './../shared/utils';
2
+
3
+ // debounce
4
+ const debounce = function (func) {
5
+ let timer;
6
+ return function (event) {
7
+ if (timer) clearTimeout(timer);
8
+ timer = setTimeout(func, 200, event);
9
+ };
10
+ };
11
+
12
+ // add class
13
+ const addClass = (target, className) => {
14
+ if (isNodeList(target)) {
15
+ target.forEach(el => {
16
+ el.classList.add(className);
17
+ });
18
+ return;
19
+ }
20
+
21
+ target.classList.add(className);
22
+ };
23
+
24
+ // remove class
25
+ const removeClass = (target, className) => {
26
+ if (isNodeList(target)) {
27
+ target.forEach(el => {
28
+ el.classList.remove(className);
29
+ });
30
+ return;
31
+ }
32
+
33
+ target.classList.remove(className);
34
+ };
35
+
36
+ // 拖拉類型控制
37
+ class dragControl {
38
+ constructor(el) {
39
+ this.$element = el;
40
+ this.option = el.s.option.drag;
41
+ this.#init();
42
+ }
43
+
44
+ // 初始化
45
+ #init() {
46
+ const $this = this;
47
+ if (!$this.$element) return;
48
+
49
+ $this.$container = $this.$element.querySelector('.drag-container');
50
+ $this.$wrapper = $this.$container.querySelector('.wrapper');
51
+
52
+ // scroll & resize event
53
+ const updateEvent = () => {
54
+ $this.#scrollerDetect();
55
+ $this.#buttonDetect();
56
+ };
57
+
58
+ // scroll event handle
59
+ $this.$wrapper.removeEventListener('scroll', updateEvent);
60
+ $this.$wrapper.addEventListener('scroll', updateEvent);
61
+
62
+ // resize event handle
63
+ window.removeEventListener('resize', debounce(updateEvent));
64
+ window.addEventListener('resize', debounce(updateEvent));
65
+
66
+ // 左右拖拉事件綁定,若為 collapse 則強制啟用 draggable
67
+ if ($this.option.draggable || $this.$element.s.type == 'collapse') $this.#bindDragEvent();
68
+ // 左右箭頭事件綁定,若為 collapse 則不輸出 .navigation
69
+ if ($this.option.navigation && $this.$element.s.type !== 'collapse') {
70
+ $this.$container.insertAdjacentHTML(
71
+ insert.prepend,
72
+ `<div class="navigation">
73
+ <div class="button prev">
74
+ <div></div>
75
+ </div>
76
+ <div class="button next">
77
+ <div></div>
78
+ </div>
79
+ </div>`,
80
+ );
81
+
82
+ $this.$button = $this.$container?.querySelectorAll('.button');
83
+ $this.#bindButtonEvent();
84
+ }
85
+ // 卷軸位置判斷
86
+ $this.#scrollerDetect();
87
+ // 隱藏按鈕判斷
88
+ $this.#buttonDetect();
89
+ // 選項事件綁定
90
+ $this.#bindItemEvent();
91
+ // 更新 active 位置
92
+ // $this.update();
93
+ }
94
+ // 更新 active 位置
95
+ update(element) {
96
+ const $wrapper = element.querySelector('.wrapper');
97
+ const $active = $wrapper?.querySelector('.active');
98
+ if ($active) {
99
+ // 移動至 active class 處
100
+ const moveDistance = $active.offsetLeft + $active.getBoundingClientRect().width / 2 - $wrapper.getBoundingClientRect().width / 2;
101
+ $wrapper.scrollTo({
102
+ left: moveDistance,
103
+ behavior: 'smooth',
104
+ });
105
+ }
106
+ }
107
+ // 左右箭頭事件綁定
108
+ #bindButtonEvent() {
109
+ const $this = this;
110
+ const { $wrapper, $button } = $this;
111
+ // button event
112
+ const buttonEvent = function () {
113
+ const type = this.classList.contains('next');
114
+ const fix = parseInt($wrapper.getBoundingClientRect().width * 0.7);
115
+
116
+ $wrapper.scrollTo({
117
+ left: type ? $wrapper.scrollLeft + fix : $wrapper.scrollLeft - fix,
118
+ behavior: 'smooth',
119
+ });
120
+
121
+ setTimeout(() => {
122
+ $this.#buttonDetect();
123
+ }, 100);
124
+ };
125
+
126
+ // button event handle
127
+ $button.forEach($el => {
128
+ $el.removeEventListener('click', buttonEvent);
129
+ $el.addEventListener('click', buttonEvent);
130
+ });
131
+ }
132
+ // 左右拖拉事件綁定
133
+ #bindDragEvent() {
134
+ const { $wrapper } = this;
135
+
136
+ // 是否為點擊狀態
137
+ let isDown = false;
138
+ // 是否為移動狀態
139
+ let isMoved = false;
140
+ // 起始 X 軸數值
141
+ let startX = 0;
142
+ // 卷軸數值
143
+ let scrollLeft = 0;
144
+
145
+ // mousedown event
146
+ const mousedownEvent = function (event) {
147
+ event.preventDefault();
148
+
149
+ isMoved = false;
150
+ isDown = true;
151
+
152
+ startX = event.pageX - $wrapper.offsetLeft;
153
+ scrollLeft = $wrapper.scrollLeft;
154
+ };
155
+
156
+ // mousedown event handle
157
+ $wrapper.removeEventListener('mousedown', mousedownEvent);
158
+ $wrapper.addEventListener('mousedown', mousedownEvent);
159
+
160
+ // mouseleave event
161
+ const mouseleaveEvent = function () {
162
+ isMoved = false;
163
+ isDown = false;
164
+ };
165
+
166
+ // mouseleave event handle
167
+ $wrapper.removeEventListener('mouseleave', mouseleaveEvent);
168
+ $wrapper.addEventListener('mouseleave', mouseleaveEvent);
169
+
170
+ // mouseup event
171
+ const mouseupEvent = function (event) {
172
+ event.preventDefault();
173
+ isDown = false;
174
+ };
175
+
176
+ // mouseup event handle
177
+ $wrapper.removeEventListener('mouseup', mouseupEvent);
178
+ $wrapper.addEventListener('mouseup', mouseupEvent);
179
+
180
+ // mousemove event
181
+ const mousemoveEvent = function (event) {
182
+ event.preventDefault();
183
+
184
+ isMoved = true;
185
+
186
+ if (!isDown) return;
187
+
188
+ const x = event.pageX - $wrapper.offsetLeft;
189
+ const walk = x - startX;
190
+ $wrapper.scrollTo({
191
+ left: scrollLeft - walk,
192
+ });
193
+ };
194
+
195
+ // mousemove event handle
196
+ $wrapper.removeEventListener('mousemove', mousemoveEvent);
197
+ $wrapper.addEventListener('mousemove', mousemoveEvent);
198
+
199
+ const reset = function (event) {
200
+ if (isMoved) {
201
+ event.preventDefault();
202
+ event.stopPropagation();
203
+ }
204
+ };
205
+
206
+ $wrapper.querySelectorAll('a').forEach($el => {
207
+ $el.removeEventListener('click', reset);
208
+ $el.addEventListener('click', reset);
209
+ });
210
+ }
211
+ // 卷軸位置判斷
212
+ #scrollerDetect() {
213
+ const { $container, $wrapper } = this;
214
+
215
+ const gate = $wrapper.scrollWidth - $wrapper.clientWidth;
216
+ const value = $wrapper.scrollLeft;
217
+
218
+ if (gate <= 0) return;
219
+
220
+ addClass($container, 'scrollable');
221
+
222
+ // 最前
223
+ if (value == 0) {
224
+ removeClass($container, 'scrollable');
225
+ addClass($wrapper, 'start');
226
+ removeClass($wrapper, 'end');
227
+ }
228
+ // 最後
229
+ else if (value >= gate) {
230
+ removeClass($container, 'scrollable');
231
+ removeClass($wrapper, 'start');
232
+ addClass($wrapper, 'end');
233
+ }
234
+ // 中間
235
+ else {
236
+ addClass($wrapper, 'center');
237
+ removeClass($wrapper, 'start');
238
+ removeClass($wrapper, 'end');
239
+ }
240
+ }
241
+ // 隱藏按鈕判斷
242
+ #buttonDetect() {
243
+ const { $wrapper, $button, $element } = this;
244
+
245
+ if (!$button) return;
246
+
247
+ const gate = $wrapper.scrollWidth - $wrapper.clientWidth;
248
+ const value = $wrapper.scrollLeft;
249
+
250
+ if (gate <= 0) {
251
+ addClass($button, 'hide');
252
+ addClass($element, 'noScrollable');
253
+ return;
254
+ }
255
+
256
+ // 最前
257
+ if (value == 0) {
258
+ $button.forEach($el => {
259
+ if ($el.classList.contains('next')) {
260
+ removeClass($el, 'hide');
261
+ return;
262
+ }
263
+
264
+ addClass($el, 'hide');
265
+ });
266
+ }
267
+ // 最後
268
+ else if (value >= gate) {
269
+ $button.forEach($el => {
270
+ if ($el.classList.contains('next')) {
271
+ addClass($el, 'hide');
272
+ return;
273
+ }
274
+
275
+ removeClass($el, 'hide');
276
+ });
277
+ }
278
+ // 中間
279
+ else {
280
+ removeClass($button, 'hide');
281
+ }
282
+ }
283
+ // 選項事件綁定
284
+ #bindItemEvent() {
285
+ const { $element } = this;
286
+
287
+ const itemEvent = $el => {
288
+ if (this.option.selected) {
289
+ const selectOption = $el.getAttribute('data-option').trim();
290
+ $element.setAttribute('m4-status', selectOption);
291
+ }
292
+ };
293
+
294
+ function clickHandler() {
295
+ itemEvent(this);
296
+ }
297
+
298
+ this.$container.querySelectorAll('.item').forEach(el => {
299
+ el.removeEventListener('click', clickHandler);
300
+ el.addEventListener('click', clickHandler);
301
+ });
302
+ }
303
+ }
304
+
305
+ // 收合類型控制
306
+ class collapseControl {
307
+ constructor(el) {
308
+ this.$element = el;
309
+ this.option = el.s.option.collapse;
310
+ this.#init();
311
+ }
312
+
313
+ // 初始化
314
+ #init() {
315
+ if (!this.$element) return;
316
+
317
+ this.$container = this.$element.querySelector('.collapse-container');
318
+ this.$wrapper = this.$container.querySelector('.wrapper');
319
+
320
+ // 隱藏按鈕判斷
321
+ if (this.#buttonDetect()) {
322
+ // 展開箭頭事件綁定
323
+ this.#bindButtonEvent();
324
+ }
325
+
326
+ // 選項事件綁定
327
+ this.#bindItemEvent();
328
+ }
329
+ // 隱藏按鈕判斷
330
+ #buttonDetect() {
331
+ const $drag = this.$element.querySelector('.drag-container');
332
+ const $wrapper = $drag.querySelector('.wrapper');
333
+
334
+ if ($wrapper.scrollWidth - $wrapper.clientWidth > 0) {
335
+ $drag.insertAdjacentHTML(insert.append, `<div class="open-collapse"></div>`);
336
+ this.$button = $drag.querySelector('.open-collapse');
337
+ return true;
338
+ }
339
+
340
+ return false;
341
+ }
342
+ // 展開箭頭事件綁定
343
+ #bindButtonEvent() {
344
+ const { $element, $button } = this;
345
+
346
+ // button event
347
+ const buttonEvent = function () {
348
+ if ($element.classList.contains('expand')) {
349
+ $element.classList.remove('expand');
350
+ } else {
351
+ $element.classList.add('expand');
352
+ }
353
+ };
354
+
355
+ // button event handle
356
+ $button.removeEventListener('click', buttonEvent);
357
+ $button.addEventListener('click', buttonEvent);
358
+ }
359
+ // 選項事件綁定
360
+ #bindItemEvent() {
361
+ const { $element } = this;
362
+
363
+ const itemEvent = ($el, collapse) => {
364
+ if (collapse) {
365
+ if ($element?.classList.contains('expand')) {
366
+ $element?.classList.remove('expand');
367
+ } else {
368
+ $element?.classList.add('expand');
369
+ }
370
+ }
371
+
372
+ if (this.option.selected) {
373
+ const selectOption = $el.getAttribute('data-option').trim();
374
+ $element.setAttribute('m4-status', selectOption);
375
+ }
376
+ };
377
+
378
+ function clickHandler() {
379
+ itemEvent(this, true);
380
+ }
381
+
382
+ this.$container.querySelectorAll('.item').forEach(el => {
383
+ el.removeEventListener('click', clickHandler);
384
+ el.addEventListener('click', clickHandler);
385
+ });
386
+ }
387
+ }
388
+
389
+ // create template method
390
+ const createTemplate = m4 => {
391
+ const { type, option, originalDomString } = m4.s;
392
+
393
+ const $storage = document.createElement('div');
394
+ if (type == 'drag') {
395
+ $storage.innerHTML = fesdDB.multipurpose4.TEMPLATE[type]().trim();
396
+
397
+ const $wrapper = $storage.querySelector('.drag-container .wrapper');
398
+ $wrapper.insertAdjacentHTML(insert.append, originalDomString);
399
+ }
400
+ if (type == 'collapse') {
401
+ $storage.innerHTML = fesdDB.multipurpose4.TEMPLATE[type](option?.collapse).trim();
402
+ // drag
403
+ const $container = $storage.querySelector('.drag-container .wrapper');
404
+ $container.insertAdjacentHTML(insert.append, originalDomString);
405
+ // collapse
406
+ const $model = $storage.querySelector('.collapse-container .wrapper');
407
+ $model.insertAdjacentHTML(insert.append, originalDomString);
408
+ }
409
+ if (type == 'dropdown') {
410
+ $storage.innerHTML = fesdDB.multipurpose4.TEMPLATE[type](option?.dropdown).trim();
411
+
412
+ const $dropdown = $storage.querySelector('dropdown-el');
413
+ $dropdown.insertAdjacentHTML(insert.append, originalDomString);
414
+ }
415
+
416
+ return $storage.children;
417
+ };
418
+
419
+ class Multipurpose4 extends HTMLElement {
420
+ constructor() {
421
+ super();
422
+ this.initialize = false;
423
+ this.__events__ = {};
424
+ this.s = {};
425
+ this.s.originalDomString = this.innerHTML.trim().replace(/\n/g, '');
426
+ this.previousWidth = window.innerWidth;
427
+ }
428
+ static get observedAttributes() {
429
+ return ['m4-type', 'm4-status'];
430
+ }
431
+ attributeChangedCallback(attr, oldVal, newVal) {
432
+ switch (attr) {
433
+ case 'm4-type':
434
+ if (oldVal === null || oldVal === newVal) return;
435
+ this.s.type = newVal;
436
+ this.#distribute();
437
+ break;
438
+ case 'm4-status':
439
+ if (oldVal === newVal) return;
440
+ const type = this.getAttribute('m4-type');
441
+ if (type !== 'drag' && type !== 'collapse') return;
442
+ // 左右拖拉
443
+ this.querySelectorAll('.drag-container .item').forEach(el => {
444
+ el.classList.remove('active');
445
+ });
446
+ this.querySelector(`.drag-container .item[data-option="${newVal}"]`)?.classList.add('active');
447
+ // 展開選項
448
+ if (type === 'collapse') {
449
+ this.querySelectorAll('.collapse-container .item').forEach(el => {
450
+ el.classList.remove('active');
451
+ });
452
+ this.querySelector(`.collapse-container .item[data-option="${newVal}"]`)?.classList.add('active');
453
+ }
454
+ this.constructor.drag?.update(this);
455
+ break;
456
+ }
457
+ }
458
+ connectedCallback() {
459
+ if (this.initialize || this.classList.contains('m4-init')) return;
460
+ this.initialize = true;
461
+ this.#init();
462
+ }
463
+ update() {
464
+ // 斷點設定
465
+ this.#breakpoint();
466
+ }
467
+ #init() {
468
+ let customize = {};
469
+
470
+ if (!this.hasAttribute('m4-value')) {
471
+ this.setAttribute('m4-value', '');
472
+ }
473
+
474
+ this.s.type = this.getAttribute('m4-type') ?? fesdDB.multipurpose4.SETTINGS.type;
475
+ if (this.hasAttribute('m4-option')) {
476
+ customize = this.getAttribute('m4-option') ? JSON.parse(this.getAttribute('m4-option')) : {};
477
+ this.removeAttribute('m4-option');
478
+ }
479
+
480
+ const updateEvent = event => {
481
+ if (event.type === 'resize') {
482
+ if (window.innerWidth === this.previousWidth) return;
483
+ this.previousWidth = window.innerWidth;
484
+ }
485
+ this.update();
486
+ };
487
+
488
+ // resize event handle
489
+ window.removeEventListener('resize', debounce(updateEvent));
490
+ window.addEventListener('resize', debounce(updateEvent));
491
+
492
+ this.s.option = {};
493
+ this.s.option.drag = Object.assign({}, fesdDB.multipurpose4.SETTINGS.drag, customize?.drag);
494
+ this.s.option.collapse = Object.assign({}, fesdDB.multipurpose4.SETTINGS.collapse, customize?.collapse);
495
+ this.s.option.dropdown = Object.assign({}, fesdDB.multipurpose4.SETTINGS.dropdown, customize?.dropdown);
496
+ this.s.option.breakpoint = Object.assign({}, fesdDB.multipurpose4.SETTINGS.breakpoint, customize?.breakpoint);
497
+
498
+ this.#render();
499
+ }
500
+ #render() {
501
+ this.classList.add('m4-init');
502
+ // 斷點設定
503
+ this.#breakpoint();
504
+ }
505
+ // 斷點設定
506
+ #breakpoint() {
507
+ const obj = Object.keys(this.s.option.breakpoint);
508
+ if (!obj.length) {
509
+ this.#distribute();
510
+ return;
511
+ }
512
+ obj
513
+ .map(keys => Number(keys))
514
+ .sort((a, b) => b - a)
515
+ .some(point => {
516
+ if (window.innerWidth >= point) {
517
+ this.s.type = this.s.option.breakpoint[point]?.type;
518
+ this.setAttribute('m4-type', this.s.type);
519
+ this.s.option.drag = Object.assign({}, this.s.option.drag, this.s.option.breakpoint[point]?.drag);
520
+ this.s.option.collapse = Object.assign({}, this.s.option.collapse, this.s.option.breakpoint[point]?.collapse);
521
+ this.s.option.dropdown = Object.assign({}, this.s.option.dropdown, this.s.option.breakpoint[point]?.dropdown);
522
+ }
523
+ this.#distribute();
524
+ return window.innerWidth >= point;
525
+ });
526
+ }
527
+ // check type
528
+ #distribute() {
529
+ const { type } = this.s;
530
+
531
+ this.innerHTML = '';
532
+ [...createTemplate(this)].forEach(el => {
533
+ this.append(el);
534
+ });
535
+
536
+ // 新增預設 active class
537
+ const dragContainer = this.querySelector(`.drag-container .item[data-option="${this.getAttribute('m4-status')}"]`);
538
+ const collapseContainer = this.querySelector(`.collapse-container .item[data-option="${this.getAttribute('m4-status')}"]`);
539
+ dragContainer?.classList.add('active');
540
+ collapseContainer?.classList.add('active');
541
+
542
+ switch (type) {
543
+ case 'drag':
544
+ this.constructor.drag = new dragControl(this);
545
+ break;
546
+ case 'collapse':
547
+ this.constructor.drag = new dragControl(this);
548
+ this.constructor.collapse = new collapseControl(this);
549
+ break;
550
+ case 'dropdown':
551
+ // this.#dropdown();
552
+ break;
553
+ }
554
+ this.constructor.drag?.update(this);
555
+ }
556
+ }
557
+
558
+ // if (!customElements.get('multipurpose-nav')) {
559
+ // customElements.define('multipurpose-nav', Multipurpose4);
560
+ // }
561
+
562
+ export default Multipurpose4;
@@ -0,0 +1,44 @@
1
+ ripple-btn
2
+ position: relative
3
+ display: inline-flex
4
+ justify-content: center
5
+ align-items: center
6
+ overflow: hidden
7
+ cursor: pointer
8
+ .circle
9
+ width: 0
10
+ height: 0
11
+ position: absolute
12
+ transform: translate3d(-50%, -50%,0)
13
+ pointer-events: none
14
+ border-radius: 50%
15
+ animation-name: animate
16
+ animation-timing-function: linear
17
+
18
+ @keyframes animate
19
+ 100%
20
+ width: 400px
21
+ height: 400px
22
+ opacity: 0
23
+
24
+ // hover event's hover-btn
25
+ .hover-btn
26
+ position: relative
27
+ z-index: 1
28
+ overflow: hidden
29
+ // &:hover
30
+ // .hover-ball
31
+ // transform: translate(-50%, -50%) scale(1)
32
+ // hover時的球球
33
+ .hover-ball
34
+ position: absolute
35
+ z-index: -1
36
+ left: 0
37
+ top: 0
38
+ width: 1px
39
+ height: 1px
40
+ border-radius: 50%
41
+ background-color: rgba(#fff,.3)
42
+ pointer-events: none
43
+ transform: translate3d(-50%, -50%,0) scale(var(--r,1))
44
+ transition: transform .8s cubic-bezier(0.6, 0.25, 0, 1)