@kubex/zinc 1.0.105 → 1.0.107
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/dist/custom-elements.json +386 -156
- package/dist/vscode.html-custom-data.json +32 -21
- package/dist/web-types.json +85 -67
- package/dist/zn.d.ts +87 -34
- package/dist/zn.min.css +1 -1
- package/dist/zn.min.js +1042 -1910
- package/docs/pages/components/chart.md +134 -8
- package/docs/pages/components/simple-chart.md +20 -5
- package/docs/pages/components/stat.md +531 -401
- package/docs/pages/components/translation-group.md +25 -0
- package/docs/pages/components/translations.md +17 -1
- package/package.json +5 -6
- package/src/components/chart/builders.test.ts +211 -0
- package/src/components/chart/builders.ts +221 -0
- package/src/components/chart/chart.component.ts +182 -173
- package/src/components/chart/chart.scss +0 -1
- package/src/components/chart/chart.test.ts +48 -4
- package/src/components/editor/modules/context-menu/context-menu.ts +2 -1
- package/src/components/inline-edit/inline-edit.component.ts +2 -1
- package/src/components/inline-edit/inline-edit.scss +9 -11
- package/src/components/rating/rating.component.ts +2 -1
- package/src/components/simple-chart/simple-chart.component.ts +72 -180
- package/src/components/textarea/textarea.component.ts +1 -1
- package/src/components/translation-group/translation-group.component.ts +113 -2
- package/src/components/translations/translations.component.ts +120 -3
|
@@ -1,204 +1,96 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as echarts from 'echarts/core';
|
|
2
|
+
import { BarChart } from 'echarts/charts';
|
|
3
|
+
import { CanvasRenderer } from 'echarts/renderers';
|
|
2
4
|
import { type CSSResultGroup, html, unsafeCSS } from 'lit';
|
|
3
|
-
import {
|
|
5
|
+
import { GridComponent, TooltipComponent } from 'echarts/components';
|
|
6
|
+
import { property } from 'lit/decorators.js';
|
|
7
|
+
import styles from './simple-chart.scss';
|
|
4
8
|
import ZincElement from '../../internal/zinc-element';
|
|
9
|
+
import type { ECharts } from 'echarts/core';
|
|
5
10
|
|
|
6
|
-
|
|
11
|
+
echarts.use([BarChart, GridComponent, TooltipComponent, CanvasRenderer]);
|
|
12
|
+
|
|
13
|
+
const DEFAULT_LABELS = [
|
|
14
|
+
'Jun 2016', 'Jul 2016', 'Aug 2016', 'Sep 2016', 'Oct 2016', 'Nov 2016',
|
|
15
|
+
'Dec 2016', 'Jan 2017', 'Feb 2017', 'Mar 2017', 'Apr 2017', 'May 2017',
|
|
16
|
+
];
|
|
17
|
+
const DEFAULT_DATA = [56.4, 39.8, 66.8, 66.4, 40.6, 55.2, 77.4, 69.8, 57.8, 76, 110.8, 142.6];
|
|
7
18
|
|
|
8
19
|
/**
|
|
9
|
-
* @summary
|
|
20
|
+
* @summary A simple, pre-styled bar chart.
|
|
10
21
|
* @documentation https://zinc.style/components/simple-chart
|
|
11
22
|
* @status experimental
|
|
12
23
|
* @since 1.0
|
|
13
|
-
*
|
|
14
|
-
* @dependency zn-example
|
|
15
|
-
*
|
|
16
|
-
* @event zn-event-name - Emitted as an example.
|
|
17
|
-
*
|
|
18
|
-
* @slot - The default slot.
|
|
19
|
-
* @slot example - An example slot.
|
|
20
|
-
*
|
|
21
|
-
* @csspart base - The component's base wrapper.
|
|
22
|
-
*
|
|
23
|
-
* @cssproperty --example - An example CSS custom property.
|
|
24
24
|
*/
|
|
25
25
|
export default class ZnSimpleChart extends ZincElement {
|
|
26
26
|
static styles: CSSResultGroup = unsafeCSS(styles);
|
|
27
27
|
|
|
28
|
-
@property({ attribute: 'datasets', type: Array }) datasets:
|
|
29
|
-
@property({ attribute: 'labels', type: Array }) labels
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
@property({ attribute: 'datasets', type: Array }) datasets?: { data: number[] }[];
|
|
29
|
+
@property({ attribute: 'labels', type: Array }) labels?: string[];
|
|
30
|
+
@property({
|
|
31
|
+
attribute: 'enable-animations',
|
|
32
|
+
converter: {
|
|
33
|
+
fromAttribute: (value: string | null) => {
|
|
34
|
+
if (value === null) return false;
|
|
35
|
+
if (value === '' || value === 'true') return true;
|
|
36
|
+
const num = parseFloat(value);
|
|
37
|
+
return Number.isNaN(num) ? true : num;
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
}) enableAnimations: boolean | number = false;
|
|
32
41
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
Chart.register(...registerables);
|
|
36
|
-
}
|
|
42
|
+
private chart?: ECharts;
|
|
43
|
+
private resizeObserver?: ResizeObserver;
|
|
37
44
|
|
|
38
45
|
firstUpdated() {
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
"Apr 2017",
|
|
56
|
-
"May 2017"
|
|
57
|
-
],
|
|
58
|
-
datasets: [
|
|
59
|
-
{
|
|
60
|
-
data: [
|
|
61
|
-
56.4,
|
|
62
|
-
39.8,
|
|
63
|
-
66.8,
|
|
64
|
-
66.4,
|
|
65
|
-
40.6,
|
|
66
|
-
55.2,
|
|
67
|
-
77.4,
|
|
68
|
-
69.8,
|
|
69
|
-
57.8,
|
|
70
|
-
76,
|
|
71
|
-
110.8,
|
|
72
|
-
142.6
|
|
73
|
-
],
|
|
74
|
-
}
|
|
75
|
-
]
|
|
46
|
+
const host = this.shadowRoot?.getElementById('chart') as HTMLElement | null;
|
|
47
|
+
if (!host) return;
|
|
48
|
+
this.chart = echarts.init(host);
|
|
49
|
+
const data = this.datasets?.[0]?.data ?? DEFAULT_DATA;
|
|
50
|
+
const labels = this.labels ?? DEFAULT_LABELS;
|
|
51
|
+
const animEnabled = this.enableAnimations !== false && this.enableAnimations !== 0;
|
|
52
|
+
const animDuration = typeof this.enableAnimations === 'number' ? this.enableAnimations : 1500;
|
|
53
|
+
this.chart.setOption({
|
|
54
|
+
animation: animEnabled,
|
|
55
|
+
animationDuration: animDuration,
|
|
56
|
+
animationEasing: 'cubicOut',
|
|
57
|
+
tooltip: {
|
|
58
|
+
trigger: 'axis',
|
|
59
|
+
backgroundColor: 'rgba(0,0,0,0.7)',
|
|
60
|
+
borderWidth: 0,
|
|
61
|
+
textStyle: { color: '#fff' },
|
|
76
62
|
},
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
// Tooltip Element
|
|
88
|
-
let tooltipEl = document.getElementById("chartjs-tooltip");
|
|
89
|
-
|
|
90
|
-
// Create element on first render
|
|
91
|
-
if (!tooltipEl) {
|
|
92
|
-
tooltipEl = document.createElement("div");
|
|
93
|
-
tooltipEl.id = "chartjs-tooltip";
|
|
94
|
-
tooltipEl.innerHTML = "<table></table>";
|
|
95
|
-
document.body.appendChild(tooltipEl);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// Hide if no tooltip
|
|
99
|
-
const tooltipModel = context.tooltip;
|
|
100
|
-
if (tooltipModel.opacity === 0) {
|
|
101
|
-
tooltipEl.style.opacity = "0";
|
|
102
|
-
return;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// Set caret Position
|
|
106
|
-
tooltipEl.classList.remove("above", "below", "no-transform");
|
|
107
|
-
if (tooltipModel.yAlign) {
|
|
108
|
-
tooltipEl.classList.add(tooltipModel.yAlign);
|
|
109
|
-
} else {
|
|
110
|
-
tooltipEl.classList.add("no-transform");
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function getBody(bodyItem: any) {
|
|
114
|
-
return bodyItem.lines;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// Set Text
|
|
118
|
-
if (tooltipModel.body) {
|
|
119
|
-
const titleLines = tooltipModel.title || [];
|
|
120
|
-
const bodyLines = tooltipModel.body.map(getBody);
|
|
121
|
-
|
|
122
|
-
let innerHtml = "<thead>";
|
|
123
|
-
|
|
124
|
-
titleLines.forEach(function (title: any) {
|
|
125
|
-
innerHtml += "<tr><th>" + title + "</th></tr>";
|
|
126
|
-
});
|
|
127
|
-
innerHtml += "</thead><tbody>";
|
|
128
|
-
|
|
129
|
-
bodyLines.forEach(function (body: any, i: any) {
|
|
130
|
-
const colors = tooltipModel.labelColors[i];
|
|
131
|
-
let style = "background:" + colors.backgroundColor;
|
|
132
|
-
style += "; border-color:" + colors.borderColor;
|
|
133
|
-
style += "; border-width: 2px";
|
|
134
|
-
const span = '<span style="' + style + '"></span>';
|
|
135
|
-
innerHtml += "<tr><td>" + span + body + "</td></tr>";
|
|
136
|
-
});
|
|
137
|
-
innerHtml += "</tbody>";
|
|
138
|
-
|
|
139
|
-
const tableRoot = tooltipEl.querySelector("table");
|
|
140
|
-
if (tableRoot) {
|
|
141
|
-
tableRoot.innerHTML = innerHtml;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
const position = context.chart.canvas.getBoundingClientRect();
|
|
146
|
-
const bodyFont = Chart.defaults.font;
|
|
147
|
-
|
|
148
|
-
tooltipModel.padding = 3;
|
|
149
|
-
|
|
150
|
-
// Display, position, and set styles for font
|
|
151
|
-
tooltipEl.style.opacity = "1";
|
|
152
|
-
tooltipEl.style.position = "absolute";
|
|
153
|
-
tooltipEl.style.left =
|
|
154
|
-
position.left +
|
|
155
|
-
window.pageXOffset +
|
|
156
|
-
tooltipModel.caretX +
|
|
157
|
-
5 +
|
|
158
|
-
"px";
|
|
159
|
-
tooltipEl.style.top =
|
|
160
|
-
position.top +
|
|
161
|
-
window.pageYOffset +
|
|
162
|
-
tooltipModel.caretY +
|
|
163
|
-
10 +
|
|
164
|
-
"px";
|
|
165
|
-
tooltipEl.style.font = bodyFont.family ?? "Helvetica Neue";
|
|
166
|
-
tooltipEl.style.color = "white";
|
|
167
|
-
tooltipEl.style.padding = tooltipModel.padding + "px " + tooltipModel.padding + "px";
|
|
168
|
-
tooltipEl.style.pointerEvents = "none";
|
|
169
|
-
tooltipEl.style.background = "rgba(0, 0, 0, 0.7)";
|
|
170
|
-
tooltipEl.style.borderRadius = "10px";
|
|
171
|
-
}
|
|
172
|
-
}
|
|
63
|
+
xAxis: { type: 'category', data: labels, show: false },
|
|
64
|
+
yAxis: { type: 'value', show: false },
|
|
65
|
+
grid: { left: 0, right: 0, top: 0, bottom: 0 },
|
|
66
|
+
series: [{
|
|
67
|
+
type: 'bar',
|
|
68
|
+
data,
|
|
69
|
+
barWidth: 7,
|
|
70
|
+
itemStyle: {
|
|
71
|
+
color: '#29C1BC',
|
|
72
|
+
borderRadius: 10,
|
|
173
73
|
},
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
borderRadius: 10,
|
|
177
|
-
barThickness: 2,
|
|
178
|
-
maxBarThickness: 2,
|
|
179
|
-
backgroundColor: "#29C1BC",
|
|
180
|
-
hoverBackgroundColor: "#19837f",
|
|
181
|
-
}
|
|
74
|
+
emphasis: {
|
|
75
|
+
itemStyle: { color: '#19837f' },
|
|
182
76
|
},
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
};
|
|
77
|
+
}],
|
|
78
|
+
});
|
|
79
|
+
let firstResize = true;
|
|
80
|
+
this.resizeObserver = new ResizeObserver(() => {
|
|
81
|
+
if (firstResize) { firstResize = false; return; }
|
|
82
|
+
this.chart?.resize();
|
|
83
|
+
});
|
|
84
|
+
this.resizeObserver.observe(host);
|
|
85
|
+
}
|
|
193
86
|
|
|
194
|
-
|
|
87
|
+
disconnectedCallback() {
|
|
88
|
+
super.disconnectedCallback();
|
|
89
|
+
this.resizeObserver?.disconnect();
|
|
90
|
+
this.chart?.dispose();
|
|
195
91
|
}
|
|
196
92
|
|
|
197
93
|
render() {
|
|
198
|
-
return html
|
|
199
|
-
<div>
|
|
200
|
-
<canvas id="myChart"></canvas>
|
|
201
|
-
</div>
|
|
202
|
-
`;
|
|
94
|
+
return html`<div id="chart"></div>`;
|
|
203
95
|
}
|
|
204
96
|
}
|
|
@@ -78,7 +78,7 @@ export default class ZnTextarea extends ZincElement implements ZincFormControl {
|
|
|
78
78
|
@property() placeholder = '';
|
|
79
79
|
|
|
80
80
|
/** The number of rows to display by default. */
|
|
81
|
-
@property({type: Number}) rows = 4;
|
|
81
|
+
@property({attribute: 'rows',type: Number}) rows = 4;
|
|
82
82
|
|
|
83
83
|
/** Controls how the textarea can be resized. */
|
|
84
84
|
@property() resize: 'none' | 'vertical' | 'auto' = 'vertical';
|
|
@@ -54,9 +54,39 @@ export default class ZnTranslationGroup extends ZnPanel {
|
|
|
54
54
|
/** Tracks all language codes that have been activated across children. */
|
|
55
55
|
@state() private _activatedLanguages: string[] = ['en'];
|
|
56
56
|
|
|
57
|
+
@state() private _overflowIndex = -1;
|
|
58
|
+
|
|
59
|
+
private _resizeObserver?: ResizeObserver;
|
|
60
|
+
private _lastObservedWidth = 0;
|
|
61
|
+
private _measureRafId = 0;
|
|
62
|
+
|
|
63
|
+
connectedCallback() {
|
|
64
|
+
super.connectedCallback();
|
|
65
|
+
this._resizeObserver = new ResizeObserver(entries => {
|
|
66
|
+
const width = entries[0]?.contentRect.width ?? 0;
|
|
67
|
+
if (Math.abs(width - this._lastObservedWidth) < 1) return;
|
|
68
|
+
this._lastObservedWidth = width;
|
|
69
|
+
if (this._overflowIndex !== -1) {
|
|
70
|
+
this._overflowIndex = -1;
|
|
71
|
+
} else {
|
|
72
|
+
this._scheduleLangOverflow();
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
disconnectedCallback() {
|
|
78
|
+
super.disconnectedCallback();
|
|
79
|
+
this._resizeObserver?.disconnect();
|
|
80
|
+
if (this._measureRafId) {
|
|
81
|
+
cancelAnimationFrame(this._measureRafId);
|
|
82
|
+
this._measureRafId = 0;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
57
86
|
protected firstUpdated(_changedProperties: PropertyValues) {
|
|
58
87
|
super.firstUpdated(_changedProperties);
|
|
59
88
|
this.syncChildren();
|
|
89
|
+
this._resizeObserver?.observe(this);
|
|
60
90
|
}
|
|
61
91
|
|
|
62
92
|
protected updated(changedProperties: PropertyValues) {
|
|
@@ -64,6 +94,52 @@ export default class ZnTranslationGroup extends ZnPanel {
|
|
|
64
94
|
if (changedProperties.has('languages')) {
|
|
65
95
|
this.syncChildLanguages();
|
|
66
96
|
}
|
|
97
|
+
this._scheduleLangOverflow();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
private _scheduleLangOverflow() {
|
|
101
|
+
if (this._measureRafId) return;
|
|
102
|
+
this._measureRafId = requestAnimationFrame(() => {
|
|
103
|
+
this._measureRafId = 0;
|
|
104
|
+
this._computeLangOverflow();
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
private _computeLangOverflow() {
|
|
109
|
+
const group = this.shadowRoot?.querySelector<HTMLElement>('.translation-group__languages zn-button-group');
|
|
110
|
+
if (!group) return;
|
|
111
|
+
|
|
112
|
+
const buttons = Array.from(group.querySelectorAll<HTMLElement>('zn-button[data-lang-btn]'));
|
|
113
|
+
if (buttons.length < 2) {
|
|
114
|
+
if (this._overflowIndex !== -1) this._overflowIndex = -1;
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const firstTop = buttons[0].getBoundingClientRect().top;
|
|
119
|
+
let wrapAt = -1;
|
|
120
|
+
for (let i = 1; i < buttons.length; i++) {
|
|
121
|
+
if (buttons[i].getBoundingClientRect().top > firstTop + 1) {
|
|
122
|
+
wrapAt = i;
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (wrapAt === -1) {
|
|
128
|
+
const trailing = group.querySelector<HTMLElement>('[data-lang-overflow], [data-lang-add]');
|
|
129
|
+
if (trailing && trailing.getBoundingClientRect().top > firstTop + 1) {
|
|
130
|
+
wrapAt = buttons.length;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (wrapAt === -1) return;
|
|
135
|
+
|
|
136
|
+
const newIndex = this._overflowIndex === -1
|
|
137
|
+
? wrapAt
|
|
138
|
+
: Math.max(1, Math.min(wrapAt, this._overflowIndex - 1));
|
|
139
|
+
|
|
140
|
+
if (newIndex !== this._overflowIndex) {
|
|
141
|
+
this._overflowIndex = newIndex;
|
|
142
|
+
}
|
|
67
143
|
}
|
|
68
144
|
|
|
69
145
|
private getAllTranslations(): ZnTranslations[] {
|
|
@@ -124,6 +200,15 @@ export default class ZnTranslationGroup extends ZnPanel {
|
|
|
124
200
|
}
|
|
125
201
|
};
|
|
126
202
|
|
|
203
|
+
private handleOverflowSelect = (e: ZnMenuSelectEvent) => {
|
|
204
|
+
e.stopPropagation();
|
|
205
|
+
const element = e.detail.element as HTMLElement;
|
|
206
|
+
const languageCode = element.getAttribute('data-path');
|
|
207
|
+
if (languageCode) {
|
|
208
|
+
this.switchLanguage(languageCode);
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
|
|
127
212
|
render() {
|
|
128
213
|
const hasActionSlot = this._slotController.test('actions');
|
|
129
214
|
const hasFooterSlot = this._slotController.test('footer');
|
|
@@ -142,6 +227,16 @@ export default class ZnTranslationGroup extends ZnPanel {
|
|
|
142
227
|
visibleTabs.unshift('en');
|
|
143
228
|
}
|
|
144
229
|
|
|
230
|
+
const overflowCutoff = this._overflowIndex === -1 ? visibleTabs.length : this._overflowIndex;
|
|
231
|
+
const visibleLangTabs = visibleTabs.slice(0, overflowCutoff);
|
|
232
|
+
const overflowLangTabs = visibleTabs.slice(overflowCutoff);
|
|
233
|
+
const overflowActions = overflowLangTabs.map(code => ({
|
|
234
|
+
title: code.toUpperCase(),
|
|
235
|
+
type: 'dropdown',
|
|
236
|
+
path: code,
|
|
237
|
+
icon: code === this._activeLanguage ? 'check' : ''
|
|
238
|
+
}));
|
|
239
|
+
|
|
145
240
|
const hasMultipleLanguages = Object.keys(this.languages).length > 1;
|
|
146
241
|
const hasHeader = headerCaption || hasMultipleLanguages || hasActionSlot;
|
|
147
242
|
|
|
@@ -163,8 +258,9 @@ export default class ZnTranslationGroup extends ZnPanel {
|
|
|
163
258
|
${hasMultipleLanguages ? html`
|
|
164
259
|
<div slot="actions" class="translation-group__languages">
|
|
165
260
|
<zn-button-group>
|
|
166
|
-
${
|
|
261
|
+
${visibleLangTabs.map(code => html`
|
|
167
262
|
<zn-button
|
|
263
|
+
data-lang-btn
|
|
168
264
|
size="x-small"
|
|
169
265
|
color="default"
|
|
170
266
|
?outline="${code !== this._activeLanguage}"
|
|
@@ -172,8 +268,23 @@ export default class ZnTranslationGroup extends ZnPanel {
|
|
|
172
268
|
>${code.toUpperCase()}
|
|
173
269
|
</zn-button>
|
|
174
270
|
`)}
|
|
271
|
+
${overflowActions.length > 0 ? html`
|
|
272
|
+
<zn-dropdown placement="bottom-end" data-lang-overflow>
|
|
273
|
+
<zn-button
|
|
274
|
+
slot="trigger"
|
|
275
|
+
size="x-small"
|
|
276
|
+
color="default"
|
|
277
|
+
icon="keyboard_arrow_down"
|
|
278
|
+
?outline="${!overflowLangTabs.includes(this._activeLanguage)}"
|
|
279
|
+
></zn-button>
|
|
280
|
+
<zn-menu
|
|
281
|
+
.actions=${overflowActions}
|
|
282
|
+
@zn-menu-select="${this.handleOverflowSelect}"
|
|
283
|
+
></zn-menu>
|
|
284
|
+
</zn-dropdown>
|
|
285
|
+
` : nothing}
|
|
175
286
|
${availableLanguages.length > 0 ? html`
|
|
176
|
-
<zn-dropdown placement="bottom-end">
|
|
287
|
+
<zn-dropdown placement="bottom-end" data-lang-add>
|
|
177
288
|
<zn-button
|
|
178
289
|
slot="trigger"
|
|
179
290
|
size="x-small"
|
|
@@ -2,6 +2,7 @@ import {classMap} from 'lit/directives/class-map.js';
|
|
|
2
2
|
import {FormControlController, validValidityState} from '../../internal/form';
|
|
3
3
|
import {HasSlotController} from '../../internal/slot';
|
|
4
4
|
import {html, nothing, unsafeCSS} from 'lit';
|
|
5
|
+
import {ifDefined} from 'lit/directives/if-defined.js';
|
|
5
6
|
import {keyed} from 'lit/directives/keyed.js';
|
|
6
7
|
import {live} from 'lit/directives/live.js';
|
|
7
8
|
import {property, state} from 'lit/decorators.js';
|
|
@@ -39,6 +40,7 @@ export default class ZnTranslations extends ZincElement implements ZincFormContr
|
|
|
39
40
|
@property({type: Boolean, reflect: true}) required = false;
|
|
40
41
|
@property({type: Boolean, reflect: true}) flush = false;
|
|
41
42
|
@property({attribute: "input-type"}) inputType: 'select' | 'text' | 'number' | 'textarea' = 'text';
|
|
43
|
+
@property({attribute: "textarea-rows", type: Number}) textareaRows: number | undefined;
|
|
42
44
|
|
|
43
45
|
/** When true, hides the individual language navbar and defers language control to a parent zn-translation-group. */
|
|
44
46
|
@property({type: Boolean, reflect: true}) grouped = false;
|
|
@@ -49,6 +51,11 @@ export default class ZnTranslations extends ZincElement implements ZincFormContr
|
|
|
49
51
|
@property({type: Object}) values: Record<string, string> = {};
|
|
50
52
|
|
|
51
53
|
@state() private _activeLanguage = 'en';
|
|
54
|
+
@state() private _overflowIndex = -1;
|
|
55
|
+
|
|
56
|
+
private _resizeObserver?: ResizeObserver;
|
|
57
|
+
private _lastObservedWidth = 0;
|
|
58
|
+
private _measureRafId = 0;
|
|
52
59
|
|
|
53
60
|
get validity(): ValidityState {
|
|
54
61
|
return validValidityState;
|
|
@@ -99,8 +106,82 @@ export default class ZnTranslations extends ZincElement implements ZincFormContr
|
|
|
99
106
|
return Object.keys(this.values);
|
|
100
107
|
}
|
|
101
108
|
|
|
109
|
+
connectedCallback() {
|
|
110
|
+
super.connectedCallback();
|
|
111
|
+
this._resizeObserver = new ResizeObserver(entries => {
|
|
112
|
+
const width = entries[0]?.contentRect.width ?? 0;
|
|
113
|
+
if (Math.abs(width - this._lastObservedWidth) < 1) return;
|
|
114
|
+
this._lastObservedWidth = width;
|
|
115
|
+
if (this._overflowIndex !== -1) {
|
|
116
|
+
this._overflowIndex = -1;
|
|
117
|
+
} else {
|
|
118
|
+
this._scheduleLangOverflow();
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
disconnectedCallback() {
|
|
124
|
+
super.disconnectedCallback();
|
|
125
|
+
this._resizeObserver?.disconnect();
|
|
126
|
+
if (this._measureRafId) {
|
|
127
|
+
cancelAnimationFrame(this._measureRafId);
|
|
128
|
+
this._measureRafId = 0;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
102
132
|
protected firstUpdated() {
|
|
103
133
|
this.formControlController.updateValidity();
|
|
134
|
+
this._resizeObserver?.observe(this);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
protected updated(changedProperties: PropertyValues) {
|
|
138
|
+
super.updated(changedProperties);
|
|
139
|
+
this._scheduleLangOverflow();
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
private _scheduleLangOverflow() {
|
|
143
|
+
if (this._measureRafId) return;
|
|
144
|
+
this._measureRafId = requestAnimationFrame(() => {
|
|
145
|
+
this._measureRafId = 0;
|
|
146
|
+
this._computeLangOverflow();
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
private _computeLangOverflow() {
|
|
151
|
+
const group = this.shadowRoot?.querySelector<HTMLElement>('.translations__language-group');
|
|
152
|
+
if (!group) return;
|
|
153
|
+
|
|
154
|
+
const buttons = Array.from(group.querySelectorAll<HTMLElement>('zn-button[data-lang-btn]'));
|
|
155
|
+
if (buttons.length < 2) {
|
|
156
|
+
if (this._overflowIndex !== -1) this._overflowIndex = -1;
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const firstTop = buttons[0].getBoundingClientRect().top;
|
|
161
|
+
let wrapAt = -1;
|
|
162
|
+
for (let i = 1; i < buttons.length; i++) {
|
|
163
|
+
if (buttons[i].getBoundingClientRect().top > firstTop + 1) {
|
|
164
|
+
wrapAt = i;
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (wrapAt === -1) {
|
|
170
|
+
const trailing = group.querySelector<HTMLElement>('[data-lang-overflow], [data-lang-add]');
|
|
171
|
+
if (trailing && trailing.getBoundingClientRect().top > firstTop + 1) {
|
|
172
|
+
wrapAt = buttons.length;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (wrapAt === -1) return;
|
|
177
|
+
|
|
178
|
+
const newIndex = this._overflowIndex === -1
|
|
179
|
+
? wrapAt
|
|
180
|
+
: Math.max(1, Math.min(wrapAt, this._overflowIndex - 1));
|
|
181
|
+
|
|
182
|
+
if (newIndex !== this._overflowIndex) {
|
|
183
|
+
this._overflowIndex = newIndex;
|
|
184
|
+
}
|
|
104
185
|
}
|
|
105
186
|
|
|
106
187
|
willUpdate(changedProperties: PropertyValues) {
|
|
@@ -164,6 +245,15 @@ export default class ZnTranslations extends ZincElement implements ZincFormContr
|
|
|
164
245
|
}
|
|
165
246
|
};
|
|
166
247
|
|
|
248
|
+
private handleOverflowSelect = (e: ZnMenuSelectEvent) => {
|
|
249
|
+
e.stopPropagation();
|
|
250
|
+
const element = e.detail.element as HTMLElement;
|
|
251
|
+
const languageCode = element.getAttribute('data-path');
|
|
252
|
+
if (languageCode) {
|
|
253
|
+
this.switchLanguage(languageCode);
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
|
|
167
257
|
private switchLanguage = (lang: string) => {
|
|
168
258
|
this._activeLanguage = lang;
|
|
169
259
|
this.requestUpdate();
|
|
@@ -223,6 +313,16 @@ export default class ZnTranslations extends ZincElement implements ZincFormContr
|
|
|
223
313
|
visibleTabs.unshift('en');
|
|
224
314
|
}
|
|
225
315
|
|
|
316
|
+
const overflowCutoff = this._overflowIndex === -1 ? visibleTabs.length : this._overflowIndex;
|
|
317
|
+
const visibleLangTabs = visibleTabs.slice(0, overflowCutoff);
|
|
318
|
+
const overflowLangTabs = visibleTabs.slice(overflowCutoff);
|
|
319
|
+
const overflowActions = overflowLangTabs.map(code => ({
|
|
320
|
+
title: code.toUpperCase(),
|
|
321
|
+
type: 'dropdown',
|
|
322
|
+
path: code,
|
|
323
|
+
icon: code === this._activeLanguage ? 'check' : ''
|
|
324
|
+
}));
|
|
325
|
+
|
|
226
326
|
const currentTranslation = this.values[this._activeLanguage] ?? '';
|
|
227
327
|
const isRTL = this.isRTLLanguage(this._activeLanguage);
|
|
228
328
|
|
|
@@ -252,9 +352,10 @@ export default class ZnTranslations extends ZincElement implements ZincFormContr
|
|
|
252
352
|
<div class="translations__actions">
|
|
253
353
|
${hasExpandSlot ? html`<slot name="expand"></slot>` : nothing}
|
|
254
354
|
${hasMultipleLanguages ? html`
|
|
255
|
-
<zn-button-group>
|
|
256
|
-
${
|
|
355
|
+
<zn-button-group class="translations__language-group">
|
|
356
|
+
${visibleLangTabs.map(code => html`
|
|
257
357
|
<zn-button
|
|
358
|
+
data-lang-btn
|
|
258
359
|
size="x-small"
|
|
259
360
|
color="default"
|
|
260
361
|
?outline="${code !== this._activeLanguage}"
|
|
@@ -262,8 +363,23 @@ export default class ZnTranslations extends ZincElement implements ZincFormContr
|
|
|
262
363
|
>${code.toUpperCase()}
|
|
263
364
|
</zn-button>
|
|
264
365
|
`)}
|
|
366
|
+
${overflowActions.length > 0 ? html`
|
|
367
|
+
<zn-dropdown placement="bottom-end" data-lang-overflow>
|
|
368
|
+
<zn-button
|
|
369
|
+
slot="trigger"
|
|
370
|
+
size="x-small"
|
|
371
|
+
color="default"
|
|
372
|
+
icon="keyboard_arrow_down"
|
|
373
|
+
?outline="${!overflowLangTabs.includes(this._activeLanguage)}"
|
|
374
|
+
></zn-button>
|
|
375
|
+
<zn-menu
|
|
376
|
+
.actions=${overflowActions}
|
|
377
|
+
@zn-menu-select="${this.handleOverflowSelect}"
|
|
378
|
+
></zn-menu>
|
|
379
|
+
</zn-dropdown>
|
|
380
|
+
` : nothing}
|
|
265
381
|
${availableLanguages.length > 0 ? html`
|
|
266
|
-
<zn-dropdown placement="bottom-end">
|
|
382
|
+
<zn-dropdown placement="bottom-end" data-lang-add>
|
|
267
383
|
<zn-button
|
|
268
384
|
slot="trigger"
|
|
269
385
|
size="x-small"
|
|
@@ -287,6 +403,7 @@ export default class ZnTranslations extends ZincElement implements ZincFormContr
|
|
|
287
403
|
${keyed(this._activeLanguage, html`
|
|
288
404
|
<zn-inline-edit
|
|
289
405
|
input-type=${this.inputType}
|
|
406
|
+
textarea-rows=${ifDefined(this.textareaRows)}
|
|
290
407
|
.value=${live(currentTranslation)}
|
|
291
408
|
name="${this.name}"
|
|
292
409
|
placeholder="Enter translation..."
|