@k3000/ce 0.2.0 → 0.2.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.
@@ -0,0 +1,127 @@
1
+ import { useAttr, bind, useRef } from "../../ce.mjs";
2
+
3
+ const htmlStr = `
4
+ <div class="relative overflow-hidden inline-flex items-center justify-center {{wrapClass}} {{isLoading ? 'bg-white/20 dark:bg-gray-800/20 animate-pulse' : 'bg-white/10 dark:bg-gray-800/10'}} transition-colors" style="{{wrapStyle}}">
5
+ <!-- Image -->
6
+ <img ref="img" src="{{src}}" alt="{{alt}}" class="w-full h-full object-{{fit}} {{isLoaded ? 'opacity-100' : 'opacity-0'}} transition-opacity duration-300 {{imgClass}}" style="{{imgStyle}}" {{lazy ? 'loading="lazy"' : ''}} />
7
+
8
+ <!-- Loading State -->
9
+ <div class="absolute inset-0 flex flex-col items-center justify-center text-gray-400 dark:text-gray-500 {{isLoading ? '' : 'hidden'}} bg-white/40 dark:bg-gray-800/40 backdrop-blur-sm">
10
+ <slot name="loading">
11
+ <svg class="w-6 h-6 animate-spin text-blue-500/70" fill="none" viewBox="0 0 24 24">
12
+ <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
13
+ <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
14
+ </svg>
15
+ </slot>
16
+ </div>
17
+
18
+ <!-- Error State -->
19
+ <div class="absolute inset-0 flex flex-col items-center justify-center text-gray-400 dark:text-gray-500 {{isError ? '' : 'hidden'}} bg-white/40 dark:bg-gray-800/40 backdrop-blur-sm">
20
+ <slot name="error">
21
+ <g-icon name="view" class="w-6 h-6 mb-1 opacity-50"></g-icon>
22
+ <span class="text-[10px] sm:text-xs">加载失败</span>
23
+ </slot>
24
+ </div>
25
+ </div>
26
+ `
27
+
28
+ export default class extends HTMLElement {
29
+ src = ''
30
+ alt = ''
31
+ fit = 'cover' // 'fill', 'contain', 'cover', 'none', 'scale-down'
32
+ position = '' // 'top', 'bottom', 'center', 'left', 'right', or string coords
33
+ lazy = false
34
+ wrapClass = ''
35
+ wrapStyle = ''
36
+ imgClass = ''
37
+ imgStyle = ''
38
+ width = ''
39
+ height = ''
40
+
41
+ isLoaded = false
42
+ isError = false
43
+ isLoading = true
44
+
45
+ constructor() {
46
+ super()
47
+ this.style.display = 'inline-block'
48
+ const attr = useAttr(this)
49
+
50
+ this.src = attr.src || ''
51
+ this.alt = attr.alt || ''
52
+ this.fit = attr.fit || 'cover'
53
+ this.position = attr.position || ''
54
+ this.lazy = attr.lazy !== undefined && attr.lazy !== 'false'
55
+ this.width = attr.width || ''
56
+ this.height = attr.height || ''
57
+
58
+ let baseClass = attr.class || ''
59
+
60
+ // If neither width/height nor explicit class dimensions are provided, default to a sensible size
61
+ if (!this.width && !this.height && !baseClass.includes('w-') && !baseClass.includes('h-')) {
62
+ baseClass += ' w-24 h-24 sm:w-32 sm:h-32'
63
+ }
64
+
65
+ if (this.width) this.wrapStyle += `width: ${this.width}; `
66
+ if (this.height) this.wrapStyle += `height: ${this.height}; `
67
+
68
+ if (this.position) this.imgStyle += `object-position: ${this.position}; `
69
+
70
+ // Handling shapes
71
+ if (attr.round === 'true') {
72
+ baseClass += ' rounded-full'
73
+ } else if (attr.radius) {
74
+ baseClass += ` rounded-[${attr.radius}]`
75
+ } else if (!baseClass.includes('rounded')) {
76
+ baseClass += ' rounded-2xl' // Default glass rounded corner
77
+ }
78
+
79
+ // Add glass border
80
+ this.wrapClass = baseClass + ' backdrop-blur-md border border-white/60 dark:border-white/10 shadow-[inner_0_1px_1px_rgba(255,255,255,0.8),0_4px_12px_rgba(0,0,0,0.05)] dark:shadow-[inner_0_1px_1px_rgba(255,255,255,0.1),0_4px_12px_rgba(0,0,0,0.2)]'
81
+
82
+ // Pass some specific classes directly to img if needed based on shape constraints
83
+ if (baseClass.includes('rounded-full')) {
84
+ this.imgClass = 'rounded-full'
85
+ } else if (baseClass.includes('rounded-2xl')) {
86
+ this.imgClass = 'rounded-2xl'
87
+ } else if (attr.radius) {
88
+ this.imgClass = `rounded-[${attr.radius}]`
89
+ }
90
+
91
+ if (!this.src) {
92
+ this.isLoading = false
93
+ this.isError = true
94
+ }
95
+ }
96
+
97
+ ready() {
98
+ const nodes = bind(htmlStr).create(this)
99
+ this.appendChild(nodes)
100
+
101
+ const { img } = useRef(this)
102
+
103
+ if (this.src) {
104
+ // Check if image is already cached/loaded completely
105
+ if (img.complete && img.naturalHeight !== 0) {
106
+ this.setLoaded()
107
+ } else {
108
+ img.onload = () => this.setLoaded()
109
+ img.onerror = () => this.setError()
110
+ }
111
+ }
112
+ }
113
+
114
+ setLoaded() {
115
+ this.isLoading = false
116
+ this.isLoaded = true
117
+ this.isError = false
118
+ if (this.update) this.update()
119
+ }
120
+
121
+ setError() {
122
+ this.isLoading = false
123
+ this.isLoaded = false
124
+ this.isError = true
125
+ if (this.update) this.update()
126
+ }
127
+ }
@@ -0,0 +1,130 @@
1
+ import { useAttr, bind } from "../../ce.mjs";
2
+ import {useWatch} from "../../index.mjs";
3
+
4
+ let zIndex = 2000
5
+
6
+ export const innerHTML = `
7
+ <div class="fixed inset-0 flex {{wrapClass}} transition-all duration-300 {{visible ? 'opacity-100 pointer-events-auto' : 'opacity-0 pointer-events-none'}}" style="z-index: {{zIndex}};">
8
+ <!-- Overlay -->
9
+ <div class="absolute inset-0 bg-black/40 backdrop-blur-sm {{overlay !== 'false' ? '' : 'hidden'}}" onclick="{{onOverlayClick}}"></div>
10
+
11
+ <!-- Content pane -->
12
+ <div class="relative bg-white/80 dark:bg-gray-800/80 backdrop-blur-2xl shadow-2xl transition-all duration-300 {{contentClass}} {{visible ? visibleClass : hiddenClass}}" onclick="event.stopPropagation()">
13
+ <slot></slot>
14
+ </div>
15
+ </div>
16
+ `
17
+
18
+ export default class extends HTMLElement {
19
+ visible = false
20
+ position = 'center'
21
+ overlay = 'true'
22
+ closeOnClickOverlay = 'true'
23
+ round = 'false'
24
+ zIndex = 2000
25
+
26
+ wrapClass = 'items-center justify-center p-4'
27
+ contentClass = 'rounded-2xl max-w-full max-h-full overflow-y-auto'
28
+ visibleClass = 'opacity-100 scale-100 translate-y-0 translate-x-0'
29
+ hiddenClass = 'opacity-0 scale-95'
30
+
31
+ constructor() {
32
+ super()
33
+ this.style.display = 'contents'
34
+ const attr = useAttr(this)
35
+
36
+ this.position = attr.position || 'center'
37
+ this.overlay = attr.overlay || 'true'
38
+ this.closeOnClickOverlay = attr['close-on-click-overlay'] || 'true'
39
+ this.round = attr.round || 'false'
40
+
41
+ this._initClasses()
42
+
43
+ const watch = useWatch(this)
44
+
45
+ watch({
46
+ status: (status, value) => {
47
+
48
+ if (status === value) return
49
+
50
+ if (status) {
51
+
52
+ this.open()
53
+
54
+ } else {
55
+
56
+ this.close()
57
+ }
58
+ }
59
+ })
60
+ }
61
+
62
+ ready() {
63
+ }
64
+
65
+ _initClasses() {
66
+ const isRound = this.round === 'true'
67
+ switch (this.position) {
68
+ case 'bottom':
69
+ this.wrapClass = 'items-end justify-center'
70
+ this.contentClass = `w-full max-h-[90vh] overflow-y-auto ${isRound ? 'rounded-t-3xl' : ''}`
71
+ this.visibleClass = 'translate-y-0 opacity-100'
72
+ this.hiddenClass = 'translate-y-full opacity-0'
73
+ break
74
+ case 'top':
75
+ this.wrapClass = 'items-start justify-center'
76
+ this.contentClass = `w-full max-h-[90vh] overflow-y-auto ${isRound ? 'rounded-b-3xl' : ''}`
77
+ this.visibleClass = 'translate-y-0 opacity-100'
78
+ this.hiddenClass = '-translate-y-full opacity-0'
79
+ break
80
+ case 'left':
81
+ this.wrapClass = 'items-center justify-start'
82
+ this.contentClass = `h-full max-w-[90vw] overflow-y-auto ${isRound ? 'rounded-r-3xl' : ''}`
83
+ this.visibleClass = 'translate-x-0 opacity-100'
84
+ this.hiddenClass = '-translate-x-full opacity-0'
85
+ break
86
+ case 'right':
87
+ this.wrapClass = 'items-center justify-end'
88
+ this.contentClass = `h-full max-w-[90vw] overflow-y-auto ${isRound ? 'rounded-l-3xl' : ''}`
89
+ this.visibleClass = 'translate-x-0 opacity-100'
90
+ this.hiddenClass = 'translate-x-full opacity-0'
91
+ break
92
+ case 'center':
93
+ default:
94
+ this.wrapClass = 'items-center justify-center p-4' // safe area
95
+ this.contentClass = `max-w-[90vw] max-h-[90vh] overflow-y-auto ${isRound ? 'rounded-3xl' : ''}`
96
+ this.visibleClass = 'opacity-100 scale-100'
97
+ this.hiddenClass = 'opacity-0 scale-95'
98
+ break
99
+ }
100
+ }
101
+
102
+ open() {
103
+ this.zIndex = zIndex++
104
+ this.visible = true
105
+ if (this.update) this.update()
106
+ if (this.onopen) this.onopen()
107
+
108
+ // Prevent body scroll
109
+ document.body.style.overflow = 'hidden'
110
+ }
111
+
112
+ close() {
113
+ this.visible = false
114
+ if (this.update) this.update()
115
+ if (this.onclose) this.onclose()
116
+
117
+ setTimeout(() => {
118
+ const hasOpenPopups = Array.from(document.querySelectorAll('g-popup')).some(p => p.visible)
119
+ if (!hasOpenPopups) {
120
+ document.body.style.overflow = ''
121
+ }
122
+ }, 300)
123
+ }
124
+
125
+ onOverlayClick() {
126
+ if (this.closeOnClickOverlay === 'true') {
127
+ this.close()
128
+ }
129
+ }
130
+ }
@@ -0,0 +1,120 @@
1
+ import { useAttr } from "../../ce.mjs";
2
+
3
+ export default class extends HTMLElement {
4
+ constructor() {
5
+ super()
6
+ this.style.display = 'flex'
7
+ this.style.flexWrap = 'wrap'
8
+ this.style.width = '100%'
9
+ this.style.boxSizing = 'border-box'
10
+
11
+ const attr = useAttr(this)
12
+
13
+ const justify = attr.justify || 'start'
14
+ const align = attr.align || 'top'
15
+ const gutter = attr.gutter || '0'
16
+ const classStr = attr.class || ''
17
+
18
+ // Map justify to flexbox
19
+ const justifyMap = {
20
+ 'start': 'flex-start',
21
+ 'end': 'flex-end',
22
+ 'center': 'center',
23
+ 'space-around': 'space-around',
24
+ 'space-between': 'space-between'
25
+ }
26
+ this.style.justifyContent = justifyMap[justify] || 'flex-start'
27
+
28
+ // Map align to flexbox
29
+ const alignMap = {
30
+ 'top': 'flex-start',
31
+ 'middle': 'center',
32
+ 'bottom': 'flex-end'
33
+ }
34
+ this.style.alignItems = alignMap[align] || 'flex-start'
35
+
36
+ // Handle gutter logic via context
37
+ // Gutter can be "20" or "[20, 30]"
38
+ let gutterX = 0
39
+ let gutterY = 0
40
+
41
+ if (gutter) {
42
+ if (gutter.startsWith('[')) {
43
+ try {
44
+ const parsed = JSON.parse(gutter)
45
+ if (Array.isArray(parsed) && parsed.length >= 2) {
46
+ gutterX = Number(parsed[0])
47
+ gutterY = Number(parsed[1])
48
+ }
49
+ } catch (e) { }
50
+ } else {
51
+ gutterX = Number(gutter)
52
+ }
53
+ }
54
+
55
+ if (gutterX || gutterY) {
56
+ const mx = gutterX ? `-${gutterX / 2}px` : '0'
57
+ const my = gutterY ? `-${gutterY / 2}px` : '0'
58
+
59
+ // Apply negative margins to row host
60
+ this.style.marginLeft = mx
61
+ this.style.marginRight = mx
62
+ this.style.marginTop = my
63
+ this.style.marginBottom = my
64
+
65
+ // Create a mutation observer to pass gutter context to children <g-col> dynamically or initially
66
+ this.xPadding = gutterX ? `${gutterX / 2}px` : '0'
67
+ this.yPadding = gutterY ? `${gutterY / 2}px` : '0'
68
+ }
69
+
70
+ if (classStr) {
71
+ this.className = this.className ? this.className + ' ' + classStr : classStr
72
+ }
73
+
74
+ // Listen to children additions since standard Web Components don't easily do React-style context down effortlessly unless via MutationObserver or passing via DOM
75
+ this._setupObserver()
76
+ }
77
+
78
+ _setupObserver() {
79
+ this._observer = new MutationObserver((mutations) => {
80
+ if (!this.xPadding && !this.yPadding) return;
81
+
82
+ mutations.forEach((mutation) => {
83
+ if (mutation.type === 'childList') {
84
+ mutation.addedNodes.forEach(node => {
85
+ this._applyGutterToChild(node)
86
+ })
87
+ }
88
+ })
89
+ })
90
+ }
91
+
92
+ _applyGutterToChild(node) {
93
+ if (node.tagName === 'G-COL') {
94
+ if (this.xPadding) {
95
+ node.style.paddingLeft = this.xPadding
96
+ node.style.paddingRight = this.xPadding
97
+ }
98
+ if (this.yPadding) {
99
+ node.style.paddingTop = this.yPadding
100
+ node.style.paddingBottom = this.yPadding
101
+ }
102
+ }
103
+ }
104
+
105
+ connectedCallback() {
106
+ if (this._observer) {
107
+ this._observer.observe(this, { childList: true, subtree: false })
108
+ }
109
+ // Initially apply to existing children
110
+ if (this.xPadding || this.yPadding) {
111
+ Array.from(this.children).forEach(child => this._applyGutterToChild(child))
112
+ }
113
+ }
114
+
115
+ disconnectedCallback() {
116
+ if (this._observer) {
117
+ this._observer.disconnect()
118
+ }
119
+ }
120
+ }
@@ -0,0 +1,21 @@
1
+ import {useAttr} from "../../index.mjs";
2
+
3
+ export const shadowRoot = `
4
+ <style>
5
+ div {
6
+ display: flex;
7
+ gap: {{gap || '8px'}};
8
+ flex-direction: {{direction}}
9
+ }
10
+ </style>
11
+ <div>
12
+ <slot></slot>
13
+ </div>
14
+ `
15
+
16
+ export default class extends HTMLElement {
17
+ constructor() {
18
+ super()
19
+ Object.assign(this, useAttr(this))
20
+ }
21
+ }
@@ -0,0 +1,116 @@
1
+ import { useAttr } from "../../ce.mjs";
2
+
3
+ let zIndex = 5000
4
+
5
+ export const innerHTML = `
6
+ <div class="toast-container fixed top-[45%] left-1/2 -translate-x-1/2 -translate-y-1/2 flex flex-col items-center gap-3 pointer-events-none" style="z-index: {{zIndex}};">
7
+ <slot></slot>
8
+ </div>
9
+ `
10
+
11
+ export default class extends HTMLElement {
12
+ constructor() {
13
+ super()
14
+ // this.style.display = 'contents'
15
+ this.zIndex = zIndex;
16
+ }
17
+
18
+ ready() {
19
+ // Expose to window globally
20
+ window.$toast = {
21
+ show: (msg, type = 'text', duration = 2000) => this.show(msg, type, duration),
22
+ success: (msg, duration = 2000) => this.show(msg, 'success', duration),
23
+ error: (msg, duration = 2000) => this.show(msg, 'error', duration),
24
+ warning: (msg, duration = 2000) => this.show(msg, 'warning', duration),
25
+ loading: (msg, duration = 0) => this.show(msg, 'loading', duration),
26
+ hide: () => this.hideAll()
27
+ }
28
+ }
29
+
30
+ show(msg, type = 'text', duration = 2000) {
31
+ let container = this.querySelector('.toast-container') || this.firstElementChild;
32
+ if (!container) return;
33
+
34
+ const item = document.createElement('div');
35
+
36
+ let iconHtml = '';
37
+ if (type === 'success') {
38
+ iconHtml = '<svg class="w-5 h-5 text-green-400" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" /></svg>';
39
+ } else if (type === 'error') {
40
+ iconHtml = '<svg class="w-5 h-5 text-red-500" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" /></svg>';
41
+ } else if (type === 'warning') {
42
+ iconHtml = '<svg class="w-5 h-5 text-yellow-500" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" /></svg>';
43
+ } else if (type === 'loading') {
44
+ iconHtml = `<svg class="w-5 h-5 text-blue-400 animate-spin" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>`;
45
+ }
46
+
47
+ item.className = 'bg-gray-800/90 dark:bg-gray-200/90 text-white dark:text-gray-900 px-5 py-3 rounded-xl shadow-2xl backdrop-blur-md flex items-center gap-3 pointer-events-auto transition-all duration-300 transform scale-95 opacity-0 max-w-[80vw] !m-0 !mt-0 !mb-0 overflow-hidden';
48
+
49
+ // Starts with 0 height so the container shifts smoothly
50
+ item.style.maxHeight = '0px';
51
+ item.style.paddingTop = '0px';
52
+ item.style.paddingBottom = '0px';
53
+ item.style.opacity = '0';
54
+
55
+ item.innerHTML = `
56
+ ${iconHtml}
57
+ <span class="text-sm font-medium whitespace-pre-wrap break-words inline-block">${msg}</span>
58
+ `;
59
+
60
+ container.appendChild(item);
61
+
62
+ // Animate in
63
+ requestAnimationFrame(() => {
64
+ requestAnimationFrame(() => {
65
+ item.classList.remove('scale-95');
66
+ item.classList.add('scale-100');
67
+ item.style.maxHeight = '150px'; // sufficiently large value
68
+ item.style.paddingTop = '';
69
+ item.style.paddingBottom = '';
70
+ item.style.opacity = '1';
71
+ });
72
+ });
73
+
74
+ if (duration > 0) {
75
+ setTimeout(() => {
76
+ this._removeItem(item);
77
+ }, duration);
78
+ }
79
+
80
+ return () => this._removeItem(item);
81
+ }
82
+
83
+ _removeItem(item) {
84
+ if (!item.parentNode) return;
85
+ item.classList.remove('scale-100');
86
+ item.classList.add('scale-75');
87
+ item.style.opacity = '0';
88
+
89
+ setTimeout(() => {
90
+ if (!item.parentNode) return;
91
+
92
+ // 固定当前高度以便开始过渡动画
93
+ item.style.height = item.offsetHeight + 'px';
94
+ item.style.overflow = 'hidden';
95
+ item.offsetHeight; // 强制重绘
96
+
97
+ // 收缩高度和边距
98
+ item.style.height = '0px';
99
+ item.style.paddingTop = '0px';
100
+ item.style.paddingBottom = '0px';
101
+ item.style.marginTop = '0px';
102
+ item.style.marginBottom = '0px';
103
+
104
+ setTimeout(() => {
105
+ if (item.parentNode) item.parentNode.removeChild(item);
106
+ }, 300);
107
+ }, 150);
108
+ }
109
+
110
+ hideAll() {
111
+ const container = this.querySelector('.toast-container') || this.firstElementChild;
112
+ if (container) {
113
+ Array.from(container.children).forEach(item => this._removeItem(item));
114
+ }
115
+ }
116
+ }