@kubex/zinc 1.0.19 → 1.0.20
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 +462 -13
- package/dist/vscode.html-custom-data.json +56 -1
- package/dist/web-types.json +117 -4
- package/dist/zn.d.ts +95 -4
- package/dist/zn.min.js +423 -375
- package/docs/pages/components/input-group.md +61 -0
- package/docs/pages/components/translations.md +53 -0
- package/package.json +1 -1
- package/src/components/audio-select/audio-select.component.ts +37 -34
- package/src/components/checkbox/checkbox.component.ts +12 -1
- package/src/components/data-table/data-table.component.ts +7 -0
- package/src/components/inline-edit/inline-edit.component.ts +2 -1
- package/src/components/input-group/index.ts +12 -0
- package/src/components/input-group/input-group.component.ts +73 -0
- package/src/components/input-group/input-group.scss +65 -0
- package/src/components/input-group/input-group.test.ts +98 -0
- package/src/components/item/item.component.ts +5 -1
- package/src/components/item/item.scss +4 -0
- package/src/components/navbar/navbar.component.ts +21 -6
- package/src/components/navbar/navbar.scss +17 -1
- package/src/components/tabs/tabs.component.ts +33 -24
- package/src/components/translations/index.ts +6 -0
- package/src/components/translations/translations.component.ts +214 -0
- package/src/components/translations/translations.scss +15 -0
- package/src/components/translations/translations.test.ts +39 -0
- package/src/zinc.ts +2 -0
|
@@ -146,6 +146,22 @@ ul.navbar {
|
|
|
146
146
|
|
|
147
147
|
}
|
|
148
148
|
|
|
149
|
+
.navbar__container--stacked {
|
|
150
|
+
display: inline;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
ul.navbar.navbar--stacked.navbar--icon-bar {
|
|
154
|
+
flex-direction: column;
|
|
155
|
+
padding: 0;
|
|
156
|
+
gap: 0;
|
|
157
|
+
|
|
158
|
+
li {
|
|
159
|
+
border-radius: 0;
|
|
160
|
+
align-items: flex-start;
|
|
161
|
+
padding: 8px;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
149
165
|
|
|
150
166
|
:host(:not([stacked]):not([icon-bar])) {
|
|
151
167
|
ul.navbar {
|
|
@@ -236,7 +252,7 @@ ul.navbar {
|
|
|
236
252
|
height: 2px;
|
|
237
253
|
}
|
|
238
254
|
|
|
239
|
-
&:hover:before {
|
|
255
|
+
&:hover:before:not(#dropdown-item) {
|
|
240
256
|
// TODO Hover Before colors
|
|
241
257
|
left: -2px;
|
|
242
258
|
right: -2px;
|
|
@@ -254,7 +254,12 @@ export default class ZnTabs extends ZincElement {
|
|
|
254
254
|
// ts-ignore
|
|
255
255
|
const target = (event.relatedTarget ?? event.target) as HTMLElement;
|
|
256
256
|
if (target) {
|
|
257
|
-
|
|
257
|
+
if ('startViewTransition' in document) {
|
|
258
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
259
|
+
(document as any).startViewTransition(() => this.clickTab(target, event.altKey));
|
|
260
|
+
} else {
|
|
261
|
+
this.clickTab(target, event.altKey)
|
|
262
|
+
}
|
|
258
263
|
}
|
|
259
264
|
}
|
|
260
265
|
|
|
@@ -333,8 +338,9 @@ export default class ZnTabs extends ZincElement {
|
|
|
333
338
|
return false;
|
|
334
339
|
}
|
|
335
340
|
|
|
341
|
+
const sameTab = this._current === tabName;
|
|
336
342
|
// Multi click on an active tab will refresh the tab
|
|
337
|
-
if (
|
|
343
|
+
if (sameTab) {
|
|
338
344
|
this._activeClicks++;
|
|
339
345
|
if (this._activeClicks > 2) {
|
|
340
346
|
refresh = true;
|
|
@@ -345,32 +351,35 @@ export default class ZnTabs extends ZincElement {
|
|
|
345
351
|
}
|
|
346
352
|
|
|
347
353
|
let inSlot = true;
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
element.toggleAttribute('selected', isActive);
|
|
355
|
-
if (isActive && refresh) {
|
|
356
|
-
let uri = "";
|
|
357
|
-
let gaid = "";
|
|
358
|
-
if (this._activeTab && this._activeTab.hasAttribute('tab-uri')) {
|
|
359
|
-
uri = this._activeTab.getAttribute('tab-uri')!;
|
|
360
|
-
gaid = this._activeTab.getAttribute('gaid')!;
|
|
354
|
+
const updateTabs = () => {
|
|
355
|
+
this._panels.forEach((elements, key) => {
|
|
356
|
+
const isActive = key === tabName;
|
|
357
|
+
elements.forEach((element) => {
|
|
358
|
+
if (isActive && element.parentNode !== this) {
|
|
359
|
+
inSlot = false;
|
|
361
360
|
}
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
361
|
+
element.toggleAttribute('selected', isActive);
|
|
362
|
+
if (isActive && refresh) {
|
|
363
|
+
let uri = "";
|
|
364
|
+
let gaid = "";
|
|
365
|
+
if (this._activeTab && this._activeTab.hasAttribute('tab-uri')) {
|
|
366
|
+
uri = this._activeTab.getAttribute('tab-uri')!;
|
|
367
|
+
gaid = this._activeTab.getAttribute('gaid')!;
|
|
368
|
+
}
|
|
369
|
+
document.dispatchEvent(new CustomEvent('zn-refresh-element', {
|
|
370
|
+
detail: {element: element, uri: uri, gaid: gaid}
|
|
371
|
+
}));
|
|
372
|
+
}
|
|
373
|
+
});
|
|
366
374
|
});
|
|
367
|
-
});
|
|
368
375
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
376
|
+
if (this._panel) {
|
|
377
|
+
this._panel.classList.toggle('contents-slot', !inSlot);
|
|
378
|
+
}
|
|
379
|
+
this._current = tabName;
|
|
380
|
+
};
|
|
372
381
|
|
|
373
|
-
|
|
382
|
+
updateTabs();
|
|
374
383
|
|
|
375
384
|
return true;
|
|
376
385
|
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import {classMap} from 'lit/directives/class-map.js';
|
|
2
|
+
import {FormControlController, validValidityState} from '../../internal/form';
|
|
3
|
+
import {HasSlotController} from '../../internal/slot';
|
|
4
|
+
import {html, unsafeCSS} from 'lit';
|
|
5
|
+
import {property, state} from 'lit/decorators.js';
|
|
6
|
+
import ZincElement from '../../internal/zinc-element';
|
|
7
|
+
import ZnInlineEdit from '../inline-edit';
|
|
8
|
+
import ZnInput from '../input';
|
|
9
|
+
import ZnNavbar from '../navbar';
|
|
10
|
+
import type {PropertyValues} from 'lit';
|
|
11
|
+
import type {ZincFormControl} from '../../internal/zinc-element';
|
|
12
|
+
import type {ZnMenuSelectEvent} from '../../events/zn-menu-select';
|
|
13
|
+
|
|
14
|
+
import styles from './translations.scss';
|
|
15
|
+
|
|
16
|
+
export default class ZnTranslations extends ZincElement implements ZincFormControl {
|
|
17
|
+
static styles = unsafeCSS(styles);
|
|
18
|
+
static dependencies = {
|
|
19
|
+
'zn-navbar': ZnNavbar,
|
|
20
|
+
'zn-input': ZnInput,
|
|
21
|
+
'zn-inline-edit': ZnInlineEdit
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
private readonly formControlController: FormControlController = new FormControlController(this);
|
|
25
|
+
private readonly hasSlotController = new HasSlotController(this, 'label');
|
|
26
|
+
|
|
27
|
+
@property() name = '';
|
|
28
|
+
@property() value = '{"en":""}';
|
|
29
|
+
@property() label: string = '';
|
|
30
|
+
@property({type: Boolean, reflect: true}) disabled = false;
|
|
31
|
+
@property({type: Boolean, reflect: true}) required = false;
|
|
32
|
+
|
|
33
|
+
@property({type: Object}) languages: Record<string, string> = {
|
|
34
|
+
'en': 'English'
|
|
35
|
+
};
|
|
36
|
+
@property({type: Object}) values: Record<string, string> = {};
|
|
37
|
+
|
|
38
|
+
@state() private _activeLanguage = '';
|
|
39
|
+
|
|
40
|
+
get validity(): ValidityState {
|
|
41
|
+
return validValidityState;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
get validationMessage() {
|
|
45
|
+
return '';
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
checkValidity() {
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
getForm() {
|
|
53
|
+
return this.formControlController.getForm();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
reportValidity() {
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
setCustomValidity() {
|
|
61
|
+
// no-op
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
protected firstUpdated() {
|
|
65
|
+
this.formControlController.updateValidity();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
willUpdate(changedProperties: PropertyValues) {
|
|
69
|
+
let processValue = changedProperties.has('value');
|
|
70
|
+
let processValues = changedProperties.has('values');
|
|
71
|
+
|
|
72
|
+
if (processValue && processValues) {
|
|
73
|
+
const isValueDefault = this.value === '{"en":""}';
|
|
74
|
+
const isValuesEmpty = Object.keys(this.values).length === 0;
|
|
75
|
+
|
|
76
|
+
if (isValueDefault && !isValuesEmpty) {
|
|
77
|
+
processValue = false;
|
|
78
|
+
} else if (!isValueDefault && isValuesEmpty) {
|
|
79
|
+
processValues = false;
|
|
80
|
+
} else if (this.hasAttribute('values') && !this.hasAttribute('value')) {
|
|
81
|
+
processValue = false;
|
|
82
|
+
} else if (this.hasAttribute('value') && !this.hasAttribute('values')) {
|
|
83
|
+
processValues = false;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (processValue) {
|
|
88
|
+
try {
|
|
89
|
+
const newValues = JSON.parse(this.value || '{}') as Record<string, string>;
|
|
90
|
+
if (JSON.stringify(newValues) !== JSON.stringify(this.values)) {
|
|
91
|
+
this.values = newValues;
|
|
92
|
+
}
|
|
93
|
+
} catch (e) {
|
|
94
|
+
// no-op
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (processValues) {
|
|
99
|
+
this.value = JSON.stringify(this.values);
|
|
100
|
+
|
|
101
|
+
// Ensure active language is valid
|
|
102
|
+
if (!this._activeLanguage || !Object.prototype.hasOwnProperty.call(this.values, this._activeLanguage)) {
|
|
103
|
+
const keys = Object.keys(this.values);
|
|
104
|
+
if (keys.length > 0) {
|
|
105
|
+
this._activeLanguage = keys[0];
|
|
106
|
+
} else {
|
|
107
|
+
this._activeLanguage = '';
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
private handleLanguageAdd(e: ZnMenuSelectEvent) {
|
|
114
|
+
const element = e.detail.element as HTMLElement;
|
|
115
|
+
const languageCode = element.getAttribute('data-path');
|
|
116
|
+
if (languageCode) {
|
|
117
|
+
// Add new language with empty string
|
|
118
|
+
this.values = {...this.values, [languageCode]: ''};
|
|
119
|
+
this._activeLanguage = languageCode;
|
|
120
|
+
this.updateValue();
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
private handleNavbarClick(e: MouseEvent) {
|
|
125
|
+
const path = e.composedPath();
|
|
126
|
+
const li = path.find(el => el instanceof HTMLElement && el.tagName === 'LI' && el.hasAttribute('tab')) as HTMLElement;
|
|
127
|
+
if (li) {
|
|
128
|
+
const tab = li.getAttribute('tab');
|
|
129
|
+
if (tab) {
|
|
130
|
+
this._activeLanguage = tab;
|
|
131
|
+
this.requestUpdate();
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
private handleValueUpdate(e: CustomEvent) {
|
|
137
|
+
const target = e.target as (ZnInput | ZnInlineEdit);
|
|
138
|
+
if (this._activeLanguage) {
|
|
139
|
+
const newValue: string = target.value as string;
|
|
140
|
+
if (newValue !== this.values[this._activeLanguage]) {
|
|
141
|
+
this.values = {...this.values, [this._activeLanguage]: newValue};
|
|
142
|
+
this.updateValue();
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
private updateValue() {
|
|
148
|
+
this.value = JSON.stringify(this.values);
|
|
149
|
+
this.emit('zn-change');
|
|
150
|
+
this.emit('zn-input');
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
render() {
|
|
154
|
+
const availableLanguages = Object.entries(this.languages)
|
|
155
|
+
.filter(([code]) => !Object.prototype.hasOwnProperty.call(this.values, code))
|
|
156
|
+
.map(([code, name]) => ({
|
|
157
|
+
title: name,
|
|
158
|
+
type: 'dropdown',
|
|
159
|
+
path: code
|
|
160
|
+
}));
|
|
161
|
+
|
|
162
|
+
const navigation = Object.keys(this.values).map(code => ({
|
|
163
|
+
title: this.languages[code] || code,
|
|
164
|
+
active: code === this._activeLanguage,
|
|
165
|
+
tab: code
|
|
166
|
+
}));
|
|
167
|
+
|
|
168
|
+
const currentTranslation = this.values[this._activeLanguage];
|
|
169
|
+
const useInlineEdit = currentTranslation && currentTranslation.length > 0;
|
|
170
|
+
|
|
171
|
+
const hasLabelSlot = this.hasSlotController.test('label');
|
|
172
|
+
const hasLabel = this.label ? true : hasLabelSlot;
|
|
173
|
+
|
|
174
|
+
return html`
|
|
175
|
+
<div part="form-control"
|
|
176
|
+
class="${classMap({
|
|
177
|
+
'translations': true,
|
|
178
|
+
'form-control': true,
|
|
179
|
+
'form-control--medium': true,
|
|
180
|
+
'form-control--has-label': hasLabel,
|
|
181
|
+
})}">
|
|
182
|
+
<label part="form-control-label" class="form-control__label" for="input"
|
|
183
|
+
aria-hidden=${hasLabel ? 'false' : 'true'}>
|
|
184
|
+
<slot name="label">${this.label}</slot>
|
|
185
|
+
</label>
|
|
186
|
+
|
|
187
|
+
<zn-navbar
|
|
188
|
+
.navigation="${navigation}"
|
|
189
|
+
.dropdown="${availableLanguages}"
|
|
190
|
+
@click="${this.handleNavbarClick}"
|
|
191
|
+
@zn-menu-select="${this.handleLanguageAdd}"
|
|
192
|
+
manual-add-items
|
|
193
|
+
></zn-navbar>
|
|
194
|
+
<div class="input-container">
|
|
195
|
+
${this._activeLanguage ? html`
|
|
196
|
+
${useInlineEdit ? html`
|
|
197
|
+
<zn-inline-edit
|
|
198
|
+
.value=${currentTranslation}
|
|
199
|
+
@zn-change="${this.handleValueUpdate}"
|
|
200
|
+
@zn-input="${this.handleValueUpdate}"
|
|
201
|
+
></zn-inline-edit>
|
|
202
|
+
` : html`
|
|
203
|
+
<zn-input
|
|
204
|
+
.value="${currentTranslation || ''}"
|
|
205
|
+
placeholder="Enter translation..."
|
|
206
|
+
@zn-change="${this.handleValueUpdate}"
|
|
207
|
+
></zn-input>
|
|
208
|
+
`}
|
|
209
|
+
` : ''}
|
|
210
|
+
</div>
|
|
211
|
+
</div>
|
|
212
|
+
`;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import '../../../dist/zn.min.js';
|
|
2
|
+
import { expect, fixture, html } from '@open-wc/testing';
|
|
3
|
+
import type ZnTranslations from './translations.component';
|
|
4
|
+
|
|
5
|
+
describe('<zn-translations>', () => {
|
|
6
|
+
it('should render a component', async () => {
|
|
7
|
+
const el = await fixture(html` <zn-translations></zn-translations> `);
|
|
8
|
+
expect(el).to.exist;
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('should have default language en', async () => {
|
|
12
|
+
const el = await fixture<ZnTranslations>(html` <zn-translations></zn-translations> `);
|
|
13
|
+
expect(el.languages).to.have.property('en');
|
|
14
|
+
// We expect values to default to en: '' if strictly following requirements,
|
|
15
|
+
// or at least the UI should show it.
|
|
16
|
+
// Based on my plan, I will implement auto-population of 'en'.
|
|
17
|
+
expect(el.values).to.have.property('en');
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should update value when input changes', async () => {
|
|
21
|
+
const el = await fixture<ZnTranslations>(html` <zn-translations></zn-translations> `);
|
|
22
|
+
// Mock languages to ensure we have something to edit
|
|
23
|
+
el.languages = { 'en': 'English', 'fr': 'French' };
|
|
24
|
+
el.values = { 'en': 'Hello' };
|
|
25
|
+
await el.updateComplete;
|
|
26
|
+
|
|
27
|
+
const input = el.shadowRoot!.querySelector('zn-inline-edit');
|
|
28
|
+
expect(input).to.exist;
|
|
29
|
+
await expect(input!.getAttribute('value')).to.equal('Hello');
|
|
30
|
+
|
|
31
|
+
// Simulate change
|
|
32
|
+
input!.value = 'Hello World';
|
|
33
|
+
input!.dispatchEvent(new CustomEvent('zn-change'));
|
|
34
|
+
|
|
35
|
+
await el.updateComplete;
|
|
36
|
+
await expect(el.values['en']).to.equal('Hello World');
|
|
37
|
+
await expect(JSON.parse(el.value)).to.deep.equal({'en': 'Hello World'});
|
|
38
|
+
});
|
|
39
|
+
});
|
package/src/zinc.ts
CHANGED
|
@@ -57,6 +57,7 @@ export {default as Textarea} from './components/textarea';
|
|
|
57
57
|
export {default as Checkbox} from './components/checkbox';
|
|
58
58
|
export {default as Datepicker} from './components/datepicker';
|
|
59
59
|
export {default as FormGroup} from './components/form-group';
|
|
60
|
+
export {default as InputGroup} from './components/input-group';
|
|
60
61
|
export {default as LinkedSelect} from './components/linked-select';
|
|
61
62
|
export {default as Radio} from './components/radio';
|
|
62
63
|
export {default as Rating} from './components/rating';
|
|
@@ -83,6 +84,7 @@ export {default as SettingsContainer} from './components/settings-container';
|
|
|
83
84
|
export { default as FilterContainer } from './components/filter-container';
|
|
84
85
|
export { default as Reveal } from './components/reveal';
|
|
85
86
|
export { default as AudioSelect } from './components/audio-select';
|
|
87
|
+
export { default as Translations } from './components/translations';
|
|
86
88
|
/* plop:component */
|
|
87
89
|
|
|
88
90
|
// Base Component
|