@k3000/ce 0.1.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.
Files changed (44) hide show
  1. package/README.md +12 -6
  2. package/app/comp/comp-test.mjs +1 -1
  3. package/app/comp/nav-bar.mjs +1 -1
  4. package/app/comp/page-container.mjs +1 -1
  5. package/app/comp/tab-bar.mjs +1 -1
  6. package/app/index.html +1 -1
  7. package/bin.mjs +48 -0
  8. package/ce.mjs +1 -0
  9. package/comm/router-view.mjs +1 -1
  10. package/console/components/common-action-button.mjs +1 -1
  11. package/console/components/common-header.mjs +1 -1
  12. package/console/components/common-input.mjs +1 -1
  13. package/console/components/common-modal.mjs +1 -1
  14. package/console/components/common-pagination.mjs +1 -1
  15. package/console/components/common-toolbar.mjs +1 -1
  16. package/console/components/div-test.mjs +11 -0
  17. package/console/components/g-button.mjs +79 -0
  18. package/console/components/g-cell.mjs +129 -0
  19. package/console/components/g-col.mjs +38 -0
  20. package/console/components/g-field.mjs +162 -0
  21. package/console/components/g-form.mjs +11 -0
  22. package/console/components/g-icon.mjs +82 -0
  23. package/console/components/g-image.mjs +127 -0
  24. package/console/components/g-popup.mjs +130 -0
  25. package/console/components/g-row.mjs +120 -0
  26. package/console/components/g-space.mjs +21 -0
  27. package/console/components/g-toast.mjs +116 -0
  28. package/console/components/layout-header.mjs +0 -179
  29. package/console/components/layout-menu.mjs +99 -2
  30. package/console/index.html +2 -1
  31. package/console/index2.html +46 -0
  32. package/console/pages/demo-button.mjs +116 -0
  33. package/console/pages/demo-cell.mjs +59 -0
  34. package/console/pages/demo-field.mjs +67 -0
  35. package/console/pages/demo-icon.mjs +200 -0
  36. package/console/pages/demo-image.mjs +113 -0
  37. package/console/pages/demo-layout.mjs +141 -0
  38. package/console/pages/demo-popup.mjs +158 -0
  39. package/console/pages/demo-space.mjs +51 -0
  40. package/console/pages/demo-toast.mjs +66 -0
  41. package/index.mjs +8 -1
  42. package/package.json +8 -2
  43. package/test.html +15 -0
  44. package/console/components/console-header.mjs +0 -26
@@ -0,0 +1,82 @@
1
+ import { useAttr, useRef } from "../../ce.mjs";
2
+
3
+ export const innerHTML = `
4
+ <svg ref="svg" class="{{classStr}}" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
5
+ <path ref="path" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"></path>
6
+ </svg>
7
+ <div class="{{hasDot ? '' : 'hidden'}} absolute top-0 right-0 transform translate-x-[20%] -translate-y-[20%] flex h-2.5 w-2.5">
8
+ <span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-red-400 opacity-75"></span>
9
+ <span class="relative inline-flex rounded-full h-2.5 w-2.5 bg-red-500 border-[1.5px] border-white dark:border-gray-800"></span>
10
+ </div>
11
+ <div class="{{badge ? '' : 'hidden'}} absolute top-0 right-0 transform translate-x-[30%] -translate-y-[30%] flex items-center justify-center z-10">
12
+ <span class="relative inline-flex rounded-full bg-red-500 text-white text-[10px] items-center justify-center px-1 min-w-[16px] h-[16px] border-[1.5px] border-white dark:border-gray-800 font-medium whitespace-nowrap leading-none shadow-sm">{{badge}}</span>
13
+ </div>
14
+ `
15
+
16
+ // Icon paths mapped by name
17
+ const icons = {
18
+ system: 'M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z',
19
+ system_inner: 'M15 12a3 3 0 11-6 0 3 3 0 016 0z', // Need to add multiple paths if needed, here we simplify or append
20
+ user: 'M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z',
21
+ role: 'M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.956 11.956 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z',
22
+ menu: 'M4 6h16M4 12h16M4 18h16',
23
+ tools: 'M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z M15 12a3 3 0 11-6 0 3 3 0 016 0z', // Combining paths for simplicity for now
24
+ search: 'M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z',
25
+ button: 'M8 11V7a4 4 0 118 0m-4 8v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2z',
26
+ home: 'M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6',
27
+ edit: 'M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z',
28
+ delete: 'M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16',
29
+ add: 'M12 4v16m8-8H4',
30
+ check: 'M5 13l4 4L19 7',
31
+ close: 'M6 18L18 6M6 6l12 12',
32
+ info: 'M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z',
33
+ warning: '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',
34
+ view: 'M15 12a3 3 0 11-6 0 3 3 0 016 0zM2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z',
35
+ logout: 'M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1',
36
+ upload: 'M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12',
37
+ download: 'M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4',
38
+ refresh: 'M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15'
39
+ }
40
+
41
+ export default class extends HTMLElement {
42
+ // name = ''
43
+ classStr = ''
44
+ size = ''
45
+ color = ''
46
+ hasDot = false
47
+ badge = ''
48
+
49
+ constructor() {
50
+ super()
51
+ this.style.display = 'inline-flex'
52
+ this.style.alignItems = 'center'
53
+ this.style.justifyContent = 'center'
54
+ this.style.position = 'relative'
55
+
56
+ const attr = useAttr(this)
57
+ this.name = this.name || attr.name || ''
58
+ this.size = attr.size || ''
59
+ this.color = attr.color || ''
60
+ this.badge = attr.badge || ''
61
+ this.hasDot = !this.badge && attr.dot !== undefined && attr.dot !== 'false'
62
+ this.classStr = attr.class || (this.size ? '' : 'w-5 h-5')
63
+ }
64
+
65
+ ready() {
66
+ const { path, svg } = useRef(this)
67
+
68
+ if (this.size) {
69
+ svg.style.width = this.size
70
+ svg.style.height = this.size
71
+ }
72
+ if (this.color) {
73
+ svg.style.color = this.color
74
+ }
75
+
76
+ if (this.name && icons[this.name]) {
77
+ path.setAttribute('d', icons[this.name])
78
+ } else {
79
+ path.setAttribute('d', 'M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z') // default info icon
80
+ }
81
+ }
82
+ }
@@ -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
+ }