@kubex/zinc 1.0.18 → 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 +721 -8
- package/dist/vscode.html-custom-data.json +98 -3
- package/dist/web-types.json +195 -5
- package/dist/zn.d.ts +156 -4
- package/dist/zn.min.js +744 -668
- 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 +131 -0
- package/src/components/audio-select/audio-select.scss +4 -0
- package/src/components/audio-select/audio-select.test.ts +10 -0
- package/src/components/audio-select/index.ts +12 -0
- package/src/components/checkbox/checkbox.component.ts +66 -11
- package/src/components/checkbox/checkbox.scss +102 -15
- 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/select/select.component.ts +7 -11
- package/src/components/select/select.scss +3 -3
- 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 +3 -0
|
@@ -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';
|
|
@@ -82,6 +83,8 @@ export {default as FilterWrapper} from './components/filter-wrapper';
|
|
|
82
83
|
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';
|
|
86
|
+
export { default as AudioSelect } from './components/audio-select';
|
|
87
|
+
export { default as Translations } from './components/translations';
|
|
85
88
|
/* plop:component */
|
|
86
89
|
|
|
87
90
|
// Base Component
|