@dile/ui 2.18.2 → 2.18.3
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.
- package/components/copy-text/copy-text.js +3 -0
- package/components/copy-text/index.js +5 -0
- package/components/copy-text/src/DileCopyText.js +143 -0
- package/components/floating-feedback/floating-feedback.js +3 -0
- package/components/floating-feedback/index.js +5 -0
- package/components/floating-feedback/src/DileFloatingFeedback.js +196 -0
- package/package.json +3 -3
- package/styles/colors.css +1 -1
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { LitElement, html, css } from 'lit';
|
|
2
|
+
import { contentCopyIcon } from '@dile/icons';
|
|
3
|
+
import '../../floating-feedback/floating-feedback.js';
|
|
4
|
+
|
|
5
|
+
export class DileCopyText extends LitElement {
|
|
6
|
+
static styles = [
|
|
7
|
+
css`
|
|
8
|
+
:host {
|
|
9
|
+
display: inline-block;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
a {
|
|
13
|
+
cursor: pointer;
|
|
14
|
+
text-decoration: var(--dile-copy-text-text-decoration, none);
|
|
15
|
+
color: var(--dile-copy-text-color, inherit);
|
|
16
|
+
transition: all var(--dile-copy-text-transition-duration, 0.3s) ease;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
a:hover {
|
|
20
|
+
color: var(--dile-copy-text-hover-color, #0066cc);
|
|
21
|
+
text-decoration: var(--dile-copy-text-hover-text-decoration, underline);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
a:focus {
|
|
25
|
+
outline: var(--dile-copy-text-focus-outline, 2px solid #0066cc);
|
|
26
|
+
outline-offset: 2px;
|
|
27
|
+
}
|
|
28
|
+
`
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
static get properties() {
|
|
32
|
+
return {
|
|
33
|
+
copy: { type: String },
|
|
34
|
+
copiedDuration: { type: Number },
|
|
35
|
+
feedbackText: { type: String },
|
|
36
|
+
hideIcon: { type: Boolean }
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
constructor() {
|
|
41
|
+
super();
|
|
42
|
+
this.copy = '';
|
|
43
|
+
this.copiedDuration = 1000; // 2 seconds by default
|
|
44
|
+
this.feedbackText = 'Copied!';
|
|
45
|
+
this.hideIcon = false;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
render() {
|
|
49
|
+
return html`
|
|
50
|
+
<a
|
|
51
|
+
href="#"
|
|
52
|
+
role="button"
|
|
53
|
+
tabindex="0"
|
|
54
|
+
@click="${this.handleCopy}"
|
|
55
|
+
@keydown="${this.handleKeydown}"
|
|
56
|
+
>
|
|
57
|
+
<slot></slot>
|
|
58
|
+
</a>
|
|
59
|
+
<dile-floating-feedback
|
|
60
|
+
id="copy-feedback"
|
|
61
|
+
feedback="${this.feedbackText}"
|
|
62
|
+
duration="${this.copiedDuration}"
|
|
63
|
+
?hideIcon=${this.hideIcon}
|
|
64
|
+
.icon=${contentCopyIcon}
|
|
65
|
+
></dile-floating-feedback>
|
|
66
|
+
`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
handleCopy(e) {
|
|
70
|
+
e.preventDefault();
|
|
71
|
+
this.copyToClipboard();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
handleKeydown(e) {
|
|
75
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
76
|
+
e.preventDefault();
|
|
77
|
+
this.copyToClipboard();
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async copyToClipboard() {
|
|
82
|
+
try {
|
|
83
|
+
// Try to use Clipboard API first (modern browsers)
|
|
84
|
+
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
85
|
+
await navigator.clipboard.writeText(this.copy);
|
|
86
|
+
} else {
|
|
87
|
+
// Fallback for older browsers
|
|
88
|
+
this.copyWithExecCommand();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Dispatch custom event
|
|
92
|
+
this.dispatchEvent(
|
|
93
|
+
new CustomEvent('dile-text-copied', {
|
|
94
|
+
detail: { text: this.copy },
|
|
95
|
+
bubbles: true,
|
|
96
|
+
composed: true
|
|
97
|
+
})
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
// Show visual feedback
|
|
101
|
+
this.showCopiedFeedback();
|
|
102
|
+
} catch (error) {
|
|
103
|
+
console.error('Failed to copy text:', error);
|
|
104
|
+
// Try fallback if Clipboard API fails
|
|
105
|
+
try {
|
|
106
|
+
this.copyWithExecCommand();
|
|
107
|
+
this.dispatchEvent(
|
|
108
|
+
new CustomEvent('dile-text-copied', {
|
|
109
|
+
detail: { text: this.copy },
|
|
110
|
+
bubbles: true,
|
|
111
|
+
composed: true
|
|
112
|
+
})
|
|
113
|
+
);
|
|
114
|
+
this.showCopiedFeedback();
|
|
115
|
+
} catch (fallbackError) {
|
|
116
|
+
console.error('Fallback copy method also failed:', fallbackError);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
copyWithExecCommand() {
|
|
122
|
+
const textArea = document.createElement('textarea');
|
|
123
|
+
textArea.value = this.copy;
|
|
124
|
+
textArea.style.position = 'fixed';
|
|
125
|
+
textArea.style.left = '-999999px';
|
|
126
|
+
textArea.style.top = '-999999px';
|
|
127
|
+
document.body.appendChild(textArea);
|
|
128
|
+
textArea.focus();
|
|
129
|
+
textArea.select();
|
|
130
|
+
const successful = document.execCommand('copy');
|
|
131
|
+
document.body.removeChild(textArea);
|
|
132
|
+
if (!successful) {
|
|
133
|
+
throw new Error('execCommand copy failed');
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
showCopiedFeedback() {
|
|
138
|
+
const feedback = this.shadowRoot.querySelector('#copy-feedback');
|
|
139
|
+
if (feedback) {
|
|
140
|
+
feedback.show();
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { LitElement, html, css } from 'lit';
|
|
2
|
+
import '../../icon/icon.js';
|
|
3
|
+
|
|
4
|
+
export class DileFloatingFeedback extends LitElement {
|
|
5
|
+
static styles = [
|
|
6
|
+
css`
|
|
7
|
+
:host {
|
|
8
|
+
display: inline-block;
|
|
9
|
+
position: relative;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.feedback-anchor {
|
|
13
|
+
display: inline-block;
|
|
14
|
+
}
|
|
15
|
+
`
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
static get properties() {
|
|
19
|
+
return {
|
|
20
|
+
feedback: { type: String },
|
|
21
|
+
duration: { type: Number },
|
|
22
|
+
icon: { type: Object },
|
|
23
|
+
hideIcon: { type: Boolean }
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
constructor() {
|
|
28
|
+
super();
|
|
29
|
+
this.feedback = '';
|
|
30
|
+
this.duration = 1000;
|
|
31
|
+
this.icon = null;
|
|
32
|
+
this.hideIcon = false;
|
|
33
|
+
this.activeInstances = new Set();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
render() {
|
|
37
|
+
return html`
|
|
38
|
+
<div class="feedback-anchor"></div>
|
|
39
|
+
`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
show(text) {
|
|
43
|
+
const feedbackText = text || this.feedback;
|
|
44
|
+
if (!feedbackText) {
|
|
45
|
+
console.warn('dile-floating-feedback: No text provided');
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
this.createFloatingElement(feedbackText);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
createFloatingElement(text) {
|
|
53
|
+
// Create instance ID and add to active set
|
|
54
|
+
const instanceId = Math.random();
|
|
55
|
+
this.activeInstances.add(instanceId);
|
|
56
|
+
|
|
57
|
+
// Calculate delay based on number of active instances
|
|
58
|
+
const delay = (this.activeInstances.size - 1) * 50;
|
|
59
|
+
|
|
60
|
+
// Create the floating element
|
|
61
|
+
const floatingElement = document.createElement('div');
|
|
62
|
+
floatingElement.className = 'dile-floating-feedback-text';
|
|
63
|
+
|
|
64
|
+
// Get computed CSS variables from the component
|
|
65
|
+
const computedStyle = window.getComputedStyle(this);
|
|
66
|
+
const bgColor = computedStyle.getPropertyValue('--dile-floating-feedback-background-color').trim() || 'rgba(0, 0, 0, 0.8)';
|
|
67
|
+
const textColor = computedStyle.getPropertyValue('--dile-floating-feedback-text-color').trim() || '#fff';
|
|
68
|
+
const fontSize = computedStyle.getPropertyValue('--dile-floating-feedback-font-size').trim() || '0.875rem';
|
|
69
|
+
const fontWeight = computedStyle.getPropertyValue('--dile-floating-feedback-font-weight').trim() || '500';
|
|
70
|
+
const padding = computedStyle.getPropertyValue('--dile-floating-feedback-padding').trim() || '0.5rem 0.75rem';
|
|
71
|
+
const borderRadius = computedStyle.getPropertyValue('--dile-floating-feedback-border-radius').trim() || '4px';
|
|
72
|
+
|
|
73
|
+
// Style the floating element with computed values
|
|
74
|
+
Object.assign(floatingElement.style, {
|
|
75
|
+
position: 'fixed',
|
|
76
|
+
pointerEvents: 'none',
|
|
77
|
+
zIndex: '10000',
|
|
78
|
+
fontFamily: 'inherit',
|
|
79
|
+
fontSize: fontSize,
|
|
80
|
+
fontWeight: fontWeight,
|
|
81
|
+
backgroundColor: bgColor,
|
|
82
|
+
color: textColor,
|
|
83
|
+
padding: padding,
|
|
84
|
+
borderRadius: borderRadius,
|
|
85
|
+
animation: `dile-floating-feedback-animation ${this.duration}ms ease-out forwards`,
|
|
86
|
+
animationDelay: delay + 'ms'
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// Create content container (flexbox if there's an icon)
|
|
90
|
+
const contentContainer = document.createElement('div');
|
|
91
|
+
if (!this.hideIcon && this.icon) {
|
|
92
|
+
Object.assign(contentContainer.style, {
|
|
93
|
+
display: 'flex',
|
|
94
|
+
alignItems: 'center',
|
|
95
|
+
gap: '0.5rem',
|
|
96
|
+
whiteSpace: 'nowrap'
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// Create and add icon element
|
|
100
|
+
const iconElement = document.createElement('dile-icon');
|
|
101
|
+
iconElement.icon = this.icon;
|
|
102
|
+
iconElement.style.setProperty('--dile-icon-size', 'var(--dile-floating-feedback-icon-size, 1.2em)');
|
|
103
|
+
iconElement.style.setProperty('--dile-icon-color', textColor);
|
|
104
|
+
contentContainer.appendChild(iconElement);
|
|
105
|
+
} else {
|
|
106
|
+
Object.assign(contentContainer.style, {
|
|
107
|
+
whiteSpace: 'nowrap'
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Create and add text element
|
|
112
|
+
const textSpan = document.createElement('span');
|
|
113
|
+
textSpan.textContent = text;
|
|
114
|
+
contentContainer.appendChild(textSpan);
|
|
115
|
+
|
|
116
|
+
// Add content container to floating element
|
|
117
|
+
floatingElement.appendChild(contentContainer);
|
|
118
|
+
|
|
119
|
+
// Calculate position relative to this component
|
|
120
|
+
const rect = this.getBoundingClientRect();
|
|
121
|
+
const x = rect.left + rect.width / 2;
|
|
122
|
+
const y = rect.top;
|
|
123
|
+
|
|
124
|
+
floatingElement.style.left = (x - 50) + 'px'; // Center roughly
|
|
125
|
+
floatingElement.style.top = y + 'px';
|
|
126
|
+
|
|
127
|
+
// Add animation keyframes to document if not already present
|
|
128
|
+
this.ensureAnimationStyles();
|
|
129
|
+
|
|
130
|
+
// Add to document body
|
|
131
|
+
document.body.appendChild(floatingElement);
|
|
132
|
+
|
|
133
|
+
// Remove element after animation completes
|
|
134
|
+
const totalTime = this.duration + delay;
|
|
135
|
+
setTimeout(() => {
|
|
136
|
+
floatingElement.remove();
|
|
137
|
+
this.activeInstances.delete(instanceId);
|
|
138
|
+
}, totalTime);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
ensureAnimationStyles() {
|
|
142
|
+
// Check if styles are already injected
|
|
143
|
+
if (document.getElementById('dile-floating-feedback-styles')) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const styleElement = document.createElement('style');
|
|
148
|
+
styleElement.id = 'dile-floating-feedback-styles';
|
|
149
|
+
styleElement.textContent = `
|
|
150
|
+
@keyframes dile-floating-feedback-animation {
|
|
151
|
+
0% {
|
|
152
|
+
opacity: 0;
|
|
153
|
+
transform: translateY(0) scale(0.8);
|
|
154
|
+
}
|
|
155
|
+
50% {
|
|
156
|
+
opacity: 1;
|
|
157
|
+
transform: translateY(-15px) scale(1);
|
|
158
|
+
}
|
|
159
|
+
100% {
|
|
160
|
+
opacity: 0;
|
|
161
|
+
transform: translateY(-30px) scale(1);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
@media (prefers-reduced-motion: reduce) {
|
|
166
|
+
@keyframes dile-floating-feedback-animation {
|
|
167
|
+
0% {
|
|
168
|
+
opacity: 0;
|
|
169
|
+
transform: translateY(0);
|
|
170
|
+
}
|
|
171
|
+
50% {
|
|
172
|
+
opacity: 1;
|
|
173
|
+
transform: translateY(0);
|
|
174
|
+
}
|
|
175
|
+
100% {
|
|
176
|
+
opacity: 0;
|
|
177
|
+
transform: translateY(0);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.dile-floating-feedback-text {
|
|
183
|
+
--dile-floating-feedback-duration: 1000ms;
|
|
184
|
+
--dile-floating-feedback-font-size: 0.875rem;
|
|
185
|
+
--dile-floating-feedback-font-weight: 500;
|
|
186
|
+
--dile-floating-feedback-color: #fff;
|
|
187
|
+
--dile-floating-feedback-background-color: rgba(0, 0, 0, 0.8);
|
|
188
|
+
--dile-floating-feedback-padding: 0.5rem 0.75rem;
|
|
189
|
+
--dile-floating-feedback-border-radius: 4px;
|
|
190
|
+
--dile-floating-feedback-translate-y: 30px;
|
|
191
|
+
}
|
|
192
|
+
`;
|
|
193
|
+
|
|
194
|
+
document.head.appendChild(styleElement);
|
|
195
|
+
}
|
|
196
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dile/ui",
|
|
3
|
-
"version": "2.18.
|
|
3
|
+
"version": "2.18.3",
|
|
4
4
|
"description": "UI Core components from dile-components.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -20,11 +20,11 @@
|
|
|
20
20
|
},
|
|
21
21
|
"homepage": "https://dile-components.com/",
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@dile/icons": "^2.4.
|
|
23
|
+
"@dile/icons": "^2.4.3",
|
|
24
24
|
"lit": "^2.7.0 || ^3.0.0"
|
|
25
25
|
},
|
|
26
26
|
"publishConfig": {
|
|
27
27
|
"access": "public"
|
|
28
28
|
},
|
|
29
|
-
"gitHead": "
|
|
29
|
+
"gitHead": "5a5d851418d9a02f6cf24e937005ee9361085678"
|
|
30
30
|
}
|
package/styles/colors.css
CHANGED
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
--dile-on-primary-light-color: #303030;
|
|
25
25
|
--dile-primary-dark-color: #d7d353;
|
|
26
26
|
--dile-on-primary-dark-color: #303030;
|
|
27
|
-
--dile-secondary-light-color: #
|
|
27
|
+
--dile-secondary-light-color: #d2f6ae;
|
|
28
28
|
--dile-on-secondary-light-color: #303030;
|
|
29
29
|
--dile-secondary-dark-color: #43842e;
|
|
30
30
|
--dile-on-secondary-dark-color: #ffffff;
|