@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,138 @@
1
+ function createRipple(e, r4) {
2
+ const button = r4;
3
+ const { color, opacity, duration } = r4.s.options;
4
+
5
+ // 可以更改點擊時生成效果樣式的結構
6
+ let ripples = document.createElement('span');
7
+
8
+ //偵測滑鼠點擊位置
9
+ let x = e.clientX - button.getBoundingClientRect().left;
10
+ let y = e.clientY - button.getBoundingClientRect().top;
11
+
12
+ ripples.style.cssText = `background: ${color};left: ${x}px;top: ${y}px;opacity: ${opacity};animation-duration: ${duration}ms`;
13
+ ripples.classList.add('circle');
14
+
15
+ // 生成
16
+ button.appendChild(ripples);
17
+
18
+ // 生成後消失
19
+ setTimeout(function () {
20
+ ripples.remove();
21
+ }, `${duration}`);
22
+ }
23
+
24
+ function removeStyle() {
25
+ document.querySelectorAll('ripple-btn').forEach(button => {
26
+ const ball = button.querySelector('.hover-ball');
27
+ button.style.setProperty('--r', '');
28
+ button.classList.remove('entered');
29
+ });
30
+ }
31
+
32
+ document.addEventListener('click', removeStyle);
33
+
34
+ class Ripple4 extends HTMLElement {
35
+ constructor() {
36
+ super();
37
+ this.initialize = false;
38
+ }
39
+
40
+ connectedCallback() {
41
+ const r4 = this;
42
+ if (r4.initialize || r4.classList.contains('r4-initialize')) return;
43
+ r4.initialize = true;
44
+ this.#create();
45
+ }
46
+
47
+ #create() {
48
+ const { SETTINGS } = fesdDB.ripple4;
49
+ this.s = {};
50
+ function attrToBoolean(str) {
51
+ let booleanVal = Boolean(str);
52
+ booleanVal = str === 'true';
53
+ return booleanVal;
54
+ }
55
+ const options = {
56
+ color: this.getAttribute('r4-color') || SETTINGS.color,
57
+ opacity: this.getAttribute('r4-opacity') || SETTINGS.opacity,
58
+ duration: Number(this.getAttribute('r4-duration')) || SETTINGS.duration,
59
+ speed: Number(this.getAttribute('r4-speed')) || SETTINGS.speed,
60
+ hover: this.getAttribute('r4-hover') ? attrToBoolean(this.getAttribute('r4-hover')) : SETTINGS.hover,
61
+ click: this.getAttribute('r4-hover-click') ? attrToBoolean(this.getAttribute('r4-hover-click')) : SETTINGS.click,
62
+ };
63
+
64
+ this.s.options = options;
65
+ if (this.s.options.hover) this.classList.add('hover-btn');
66
+
67
+ this.#init();
68
+ }
69
+
70
+ #init() {
71
+ this.#ball();
72
+ this.#event();
73
+ this.classList.add('r4-initialize');
74
+ }
75
+
76
+ #ball() {
77
+ const button = this;
78
+ const ball = document.createElement('i');
79
+ ball.classList.add('hover-ball');
80
+ ball.style.transitionDuration = `${button.s.options.speed}ms`;
81
+ button.appendChild(ball);
82
+ }
83
+
84
+ #event() {
85
+ const ball = this.querySelector('i.hover-ball');
86
+ const button = this;
87
+ // ripple點擊事件
88
+ button.addEventListener('click', function (e) {
89
+ e.stopPropagation();
90
+ if (button.s.options.click) {
91
+ createRipple(e, button);
92
+ }
93
+ });
94
+
95
+ // 判斷操作事件使用 mouse 或是 touch
96
+ let operateStart = 'ontouchstart' in document.documentElement ? 'touchstart' : 'mouseenter';
97
+ let operateEnd = 'ontouchend' in document.documentElement ? 'touchend' : 'mouseleave';
98
+ // hover
99
+ button.addEventListener(operateStart, function (e) {
100
+ if (operateStart === 'touchstart') {
101
+ removeStyle();
102
+ }
103
+ if (button.s.options.hover) {
104
+ const posX = operateStart === 'mouseenter' ? Math.round(e.clientX - button.getBoundingClientRect().left) : Math.round(e.changedTouches[0].clientX - button.getBoundingClientRect().x);
105
+ const posY = operateStart === 'mouseenter' ? Math.round(e.clientY - button.getBoundingClientRect().top) : Math.round(e.changedTouches[0].clientY - button.getBoundingClientRect().y);
106
+ const { offsetWidth, offsetHeight } = button;
107
+ // 最大可覆蓋之半徑
108
+ const rippleR = Math.ceil(Math.sqrt(offsetWidth ** 2 + offsetHeight ** 2) * 2);
109
+ button.style.setProperty('--r', rippleR);
110
+ ball.style.left = posX + 'px';
111
+ ball.style.top = posY + 'px';
112
+ button.classList.add('entered');
113
+ }
114
+ });
115
+ button.addEventListener(operateEnd, function (e) {
116
+ if (operateEnd === 'touchend') return;
117
+ if (button.s.options.hover) {
118
+ const posX = operateEnd === 'mouseleave' ? Math.round(e.clientX - button.getBoundingClientRect().left) : Math.round(e.changedTouches[0].clientX - button.getBoundingClientRect().x);
119
+ const posY = operateEnd === 'mouseleave' ? Math.round(e.clientY - button.getBoundingClientRect().top) : Math.round(e.changedTouches[0].clientY - button.getBoundingClientRect().y);
120
+ button.style.setProperty('--r', '');
121
+ ball.style.left = posX + 'px';
122
+ ball.style.top = posY + 'px';
123
+ button.classList.remove('entered');
124
+ }
125
+ });
126
+ }
127
+ update() {
128
+ this.classList.remove('r4-initialize');
129
+ this.querySelector('i.hover-ball').remove();
130
+ this.#create();
131
+ }
132
+ }
133
+
134
+ // if (!customElements.get('ripple-btn')) {
135
+ // customElements.define('ripple-btn', Ripple4);
136
+ // }
137
+
138
+ export default Ripple4;
@@ -0,0 +1,191 @@
1
+ import { insert, isString, isElementExist, getAllElements } from './../shared/utils';
2
+
3
+ ('use strict');
4
+
5
+ const getTargetUrl = type => {
6
+ // facebook
7
+ if (type == 'facebook') {
8
+ return 'https://www.facebook.com/sharer/sharer.php?u=';
9
+ }
10
+
11
+ // line
12
+ if (type == 'line') {
13
+ // desktop & mobile 網址不同
14
+ if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) {
15
+ return 'http://line.naver.jp/R/msg/text/?';
16
+ }
17
+ return 'https://lineit.line.me/share/ui?url=';
18
+ }
19
+
20
+ // twitter
21
+ if (type == 'twitter') {
22
+ return 'https://twitter.com/intent/tweet?url=';
23
+ }
24
+
25
+ // linkedin
26
+ if (type == 'linkedin') {
27
+ return `http://www.linkedin.com/shareArticle?mini=true&title=${document.title}&source=${document.title}&url=`;
28
+ // return `https://www.linkedin.com/sharing/share-offsite/?url=`;
29
+ }
30
+
31
+ // telegram
32
+ if (type == 'telegram') {
33
+ return 'https://telegram.me/share/url?url=';
34
+ }
35
+ };
36
+
37
+ class Share4 {
38
+ constructor(el, options = {}) {
39
+ this.__storage__ = {
40
+ el,
41
+ options,
42
+ };
43
+
44
+ this.#create();
45
+ }
46
+
47
+ #create() {
48
+ const { el, options } = this.__storage__;
49
+ // 從 fesdDB 提取設定
50
+ const { SETTINGS } = fesdDB.share4;
51
+ if (!isString(el) || !isElementExist(el)) return;
52
+
53
+ this.elements = getAllElements(el);
54
+
55
+ this.options = Object.assign({}, SETTINGS, options);
56
+
57
+ this.#init();
58
+ }
59
+
60
+ #init() {
61
+ const { elements, options } = this;
62
+
63
+ elements.forEach(targets => {
64
+ targets.querySelectorAll('[share-target]').forEach(el => {
65
+ el.share = {};
66
+ el.share.instance = this;
67
+ el.share.params = options;
68
+ el.share.eventHandler = this.#trigger;
69
+ el.addEventListener('click', el.share.eventHandler);
70
+ });
71
+ });
72
+ }
73
+
74
+ #trigger() {
75
+ /** the keyword `this` in this method is pointed to the click target */
76
+ const { eventHandler, params } = this.share;
77
+ const type = this.getAttribute('share-target');
78
+
79
+ const utm = {
80
+ source: this.getAttribute('utm-source'),
81
+ medium: this.getAttribute('utm-medium'),
82
+ campaign: this.getAttribute('utm-campaign'),
83
+ };
84
+
85
+ const copy = {
86
+ success: this.getAttribute('copy-success') || params.success,
87
+ text: this.getAttribute('copy-text') || params.text,
88
+ className: this.getAttribute('copy-class') || params.className,
89
+ duration: this.getAttribute('copy-duration') || params.duration,
90
+ };
91
+
92
+ const baseUrl = encodeURIComponent(document.URL);
93
+ let shareUrl = baseUrl;
94
+
95
+ // 若為 wechat 則另開視窗掃描 QRcode
96
+ if (type == 'wechat') {
97
+ // https://api.qrserver.com/v1/create-qr-code/?data=${url}&size=300x300
98
+ window.open(`https://api.qrserver.com/v1/create-qr-code/?data=${baseUrl}&size=250x250`, 'share to wechat', 'width=300,height=300');
99
+ return;
100
+ }
101
+
102
+ // 若為 url 則複製連結
103
+ if (type == 'url') {
104
+ if (isElementExist('.copied-wrapper')) return;
105
+
106
+ document.querySelector('body').insertAdjacentHTML(insert.append, `<div class='copied-wrapper'><div class='text'>${copy.success ? copy.success : ''}</div><input id='clipboard' type='text' readonly></div>`);
107
+
108
+ const $copied = document.querySelector('.copied-wrapper');
109
+
110
+ if (copy.className) {
111
+ $copied.classList.add(copy.className);
112
+ }
113
+ $copied.style.top = this.getBoundingClientRect().top + window.scrollY + 'px';
114
+ $copied.style.left = this.getBoundingClientRect().left + this.getBoundingClientRect().width / 2 + 'px';
115
+ $copied.style.display = 'block';
116
+
117
+ const $clipboard = document.querySelector('#clipboard');
118
+
119
+ $clipboard.value = copy.text ?? window.location.href;
120
+ $clipboard.setSelectionRange(0, 9999);
121
+ $clipboard.select();
122
+
123
+ if (document.execCommand('copy')) {
124
+ // 複製對應內容
125
+ document.execCommand('copy');
126
+ // 動畫效果
127
+ const copiedWrapper = document.querySelector('.copied-wrapper');
128
+ const tooltipsText = document.querySelector('.copied-wrapper .text');
129
+ tooltipsText.style.display = 'block';
130
+ tooltipsText.style.opacity = 0;
131
+ const fadeIn = (element, duration) => {
132
+ let opacity = 0;
133
+ const interval = 50;
134
+ const gap = interval / duration;
135
+
136
+ const fading = setInterval(function () {
137
+ opacity += gap;
138
+ element.style.opacity = opacity;
139
+
140
+ if (opacity >= 1) {
141
+ clearInterval(fading);
142
+ setTimeout(function () {
143
+ fadeOut(element, 300, function () {
144
+ copiedWrapper.remove();
145
+ });
146
+ }, copy.duration);
147
+ }
148
+ }, interval);
149
+ };
150
+ const fadeOut = (element, duration, callback) => {
151
+ let opacity = 1;
152
+ const interval = 50;
153
+ const gap = interval / duration;
154
+
155
+ const fading = setInterval(() => {
156
+ opacity -= gap;
157
+ element.style.opacity = opacity;
158
+
159
+ if (opacity <= 0) {
160
+ clearInterval(fading);
161
+ element.style.display = 'none';
162
+ if (callback) callback();
163
+ }
164
+ }, interval);
165
+ };
166
+ fadeIn(tooltipsText, 300);
167
+ }
168
+
169
+ return;
170
+ }
171
+
172
+ // 其餘則使用對應網址另開新分頁
173
+ // url + '?utm_source=Facebook' + '&utm_medium=social' + '&utm_campaign=' + campaign
174
+ shareUrl = `${shareUrl}${utm.source ? `?utm_source=${utm.source}` : ''}${utm.medium ? `&utm_medium${utm.medium}` : ''}${utm.campaign ? `&utm_campaign${utm.campaign}` : ''}`;
175
+ shareUrl = shareUrl.replace('?', '%3F').replace(new RegExp('&', 'g'), '%26');
176
+
177
+ if (type == 'line') {
178
+ // line 需帶入原始網址
179
+ window.open(`${getTargetUrl(type)}${baseUrl}`);
180
+ } else {
181
+ window.open(`${getTargetUrl(type)}${shareUrl}&quote=${shareUrl}`);
182
+ }
183
+
184
+ this.removeEventListener('click', eventHandler);
185
+ setTimeout(() => {
186
+ this.addEventListener('click', eventHandler);
187
+ }, 100);
188
+ }
189
+ }
190
+
191
+ export default Share4;
@@ -0,0 +1,59 @@
1
+ import {
2
+ isFunction
3
+ } from './utils'
4
+
5
+ export default {
6
+ on(events, handler) {
7
+ if (!isFunction(handler)) return this;
8
+
9
+ const { __events__ } = this
10
+
11
+ events.split(' ').forEach(evt => {
12
+ if (!__events__[evt]) __events__[evt] = []
13
+ __events__[evt].push(handler)
14
+ })
15
+
16
+ return this
17
+ },
18
+
19
+ off(events, handler) {
20
+ const { __events__ } = this
21
+
22
+ events.split(' ').forEach(evt => {
23
+ if (typeof handler === 'undefined') __events__[evt] = []
24
+ else if (__events__[evt]) {
25
+ __events__[evt].forEach((evtHandler, index) => {
26
+ if (evtHandler === handler) __events__[evt].splice(index, 1)
27
+ })
28
+ }
29
+ })
30
+
31
+ return this
32
+ },
33
+
34
+ once(events, handler) {
35
+ if (!isFunction(handler)) return this;
36
+
37
+ const onceHandler = (...args) => {
38
+ this.off(events, onceHandler)
39
+ handler.apply(this, args)
40
+ }
41
+
42
+ return this.on(events, onceHandler)
43
+ },
44
+
45
+ emit(...args) {
46
+ const { __events__ } = this
47
+
48
+ const event = args[0]
49
+ const data = args.slice(1, args.length)
50
+
51
+ if (!__events__[event]) return this
52
+
53
+ __events__[event].forEach(handler => {
54
+ if (isFunction(handler)) handler.apply(this, data)
55
+ })
56
+
57
+ return this
58
+ }
59
+ }
@@ -0,0 +1,98 @@
1
+
2
+ // insertAdjacentHTML 語意化
3
+ export const insert = {
4
+ before: 'beforebegin',
5
+ after: 'afterend',
6
+ append: 'beforeend',
7
+ prepend: 'afterbegin'
8
+ }
9
+
10
+ // check value is string and not empty
11
+ export const isString = (value) => {
12
+ return typeof value === 'string' && value !== '';
13
+ };
14
+
15
+ // check value is number and not NaN
16
+ export const isNumber = (value) => {
17
+ return typeof value === 'number' && !isNaN(value);
18
+ };
19
+
20
+ // check is HTML element
21
+ export const isElement = (value) => {
22
+ return value instanceof HTMLElement;
23
+ };
24
+
25
+ // check is Node List
26
+ export const isNodeList = (value) => {
27
+ return value instanceof NodeList;
28
+ };
29
+
30
+ // check element is exist
31
+ export const isElementExist = (value) => {
32
+ return getElement(value) !== null;
33
+ };
34
+
35
+ // check value is a function
36
+ export const isFunction = (value) => {
37
+ return typeof value === 'function';
38
+ };
39
+
40
+ // if value is an element then return value, if not then query value
41
+ export const getElement = (value) => {
42
+ return isElement(value) ? value : document.querySelector(value);
43
+ };
44
+
45
+ // if value is an element then return value, if not then query value
46
+ export const getAllElements = (value) => {
47
+ return isNodeList(value) ? value : document.querySelectorAll(value);
48
+ };
49
+
50
+ // create a unique id
51
+ export const createUid = () => {
52
+ return Math.random().toString(36).substr(2, 9);
53
+ };
54
+
55
+ // parse string to HTML element
56
+ export const toHTMLElement = (str) => {
57
+ const dom = document.createElement('div');
58
+ dom.innerHTML = str;
59
+ return dom.childNodes;
60
+ };
61
+
62
+ // parse json to object
63
+ export const jsonParse = (json) => {
64
+ try {
65
+ JSON.parse(json);
66
+ } catch (e) {
67
+ return json;
68
+ }
69
+ return JSON.parse(json);
70
+ };
71
+
72
+ // get element transform X
73
+ export const getTransformX = (target) => {
74
+ const transform = getComputedStyle(target).transform;
75
+ let mat = transform.match(/^matrix3d\((.+)\)$/);
76
+ if (mat) return parseFloat(mat[1].split(', ')[12]);
77
+ mat = transform.match(/^matrix\((.+)\)$/);
78
+ return mat ? parseFloat(mat[1].split(', ')[4]) : 0;
79
+ };
80
+
81
+ // get element transform Y
82
+ export const getTransformY = (target) => {
83
+ const transform = getComputedStyle(target).transform;
84
+ let mat = transform.match(/^matrix3d\((.+)\)$/);
85
+ if (mat) return parseFloat(mat[1].split(', ')[13]);
86
+ mat = transform.match(/^matrix\((.+)\)$/);
87
+ return mat ? parseFloat(mat[1].split(', ')[5]) : 0;
88
+ };
89
+
90
+ // warn
91
+ export const warn = (target, msg) => {
92
+ console.warn(`[${target} warn]: ${msg}`);
93
+ };
94
+
95
+ // error
96
+ export const error = (target, msg) => {
97
+ console.error(`[${target} error]: ${msg}`);
98
+ };
@@ -0,0 +1,25 @@
1
+ tab-el
2
+ [t4-role="tabPanel"]
3
+ width: 100%
4
+ height: 100%
5
+ opacity: 0
6
+ &[t4-display="silde"]
7
+ // 要用slide一定要加喔 不然會壞掉
8
+ overflow: hidden
9
+ // 可自訂 - 鍵頭按鈕樣式
10
+ [t4-role="prev"],
11
+ [t4-role="next"]
12
+ opacity: 1
13
+ transition: 0.5s opacity
14
+ cursor: pointer
15
+ &:hover
16
+ opacity: 0.7
17
+ &[disabled]
18
+ opacity: 0.1
19
+ pointer-events: none
20
+ // 頁籤樣式
21
+ [aria-selected="false"]
22
+ opacity: 0.7
23
+ [aria-selected="true"]
24
+ opacity: 1
25
+ transition: 0.5s opacity