@kubex/zinc 1.1.117 → 1.1.119
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 +1733 -1567
- package/dist/vscode.html-custom-data.json +146 -136
- package/dist/web-types.json +329 -309
- package/dist/zn.d.ts +74 -37
- package/dist/zn.min.js +278 -258
- package/docs/pages/components/form-group.md +2 -0
- package/docs/pages/components/translation-group.md +20 -18
- package/docs/pages/components/translations.md +3 -2
- package/package.json +1 -1
- package/src/components/form-group/form-group.component.ts +51 -77
- package/src/components/form-group/form-group.test.ts +36 -0
- package/src/components/remarkd-editor/actions.ts +2 -1
- package/src/components/remarkd-editor/remarkd-editor.component.ts +251 -9
- package/src/components/remarkd-editor/remarkd-editor.scss +49 -0
- package/src/components/remarkd-editor/remarkd-editor.test.ts +329 -5
- package/src/components/translation-group/translation-group.component.ts +55 -51
- package/src/components/translation-group/translation-group.scss +10 -74
- package/src/components/translation-group/translation-group.test.ts +76 -38
- package/src/components/translations/translations.component.ts +3 -7
|
@@ -2,81 +2,15 @@
|
|
|
2
2
|
display: block;
|
|
3
3
|
}
|
|
4
4
|
|
|
5
|
-
// The body
|
|
6
|
-
// controls. The slotted children are `display: contents` through the slot, putting them in this same flex flow.
|
|
5
|
+
// The body holds the form group and the actions row; the gap is what separates them.
|
|
7
6
|
.panel__body {
|
|
8
7
|
gap: var(--zn-spacing-medium);
|
|
9
8
|
}
|
|
10
9
|
|
|
11
|
-
// The
|
|
12
|
-
// the
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
.panel__header {
|
|
18
|
-
padding-bottom: var(--zn-spacing-medium);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
.translation-group--inline .panel__header {
|
|
22
|
-
padding: 0 0 var(--zn-spacing-medium);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// zn-header holds its content row at 36px so a caption sits level with action buttons. The select is taller than
|
|
26
|
-
// that and sets the height on its own, so the row hugs whichever of the two is bigger.
|
|
27
|
-
.panel__header::part(content) {
|
|
28
|
-
min-height: 0;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// The actions container is a plain block that clips horizontally: its content would sit at the top rather than level
|
|
32
|
-
// with the caption, and a select flush against its right edge would lose the focus ring.
|
|
33
|
-
.panel__header::part(header-right) {
|
|
34
|
-
display: flex;
|
|
35
|
-
align-items: center;
|
|
36
|
-
justify-content: flex-end;
|
|
37
|
-
overflow: visible;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
.translation-group__language-field {
|
|
41
|
-
display: flex;
|
|
42
|
-
align-items: center;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// The trigger shows one language, so it sizes to that.
|
|
46
|
-
.translation-group__language-select {
|
|
47
|
-
width: max-content;
|
|
48
|
-
max-width: 100%;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// Left alone the display input hands the trigger its default 20-character intrinsic width, however short the language
|
|
52
|
-
// name is. Where field-sizing is unsupported that width is what is left, which only makes the trigger a little wider.
|
|
53
|
-
.translation-group__language-select::part(display-input) {
|
|
54
|
-
width: auto;
|
|
55
|
-
min-width: 0;
|
|
56
|
-
field-sizing: content;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// The popup syncs its width to the trigger, which is now sized to the current language rather than the longest one in
|
|
60
|
-
// the list. zn-select runs the popup with auto-size="vertical", so --auto-size-available-width is never set and the
|
|
61
|
-
// listbox's own max-width is inert — this is the only thing keeping a long list on screen.
|
|
62
|
-
.translation-group__language-select::part(listbox) {
|
|
63
|
-
min-width: 320px;
|
|
64
|
-
max-width: calc(100vw - var(--zn-spacing-large));
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// The select is chrome in the panel header rather than a field to fill in, so it drops the input's fill and shadow.
|
|
68
|
-
// The border stays, marking it out as something to open.
|
|
69
|
-
.translation-group__language-select::part(combobox) {
|
|
70
|
-
background-color: transparent;
|
|
71
|
-
box-shadow: none;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
.translation-group__language-select:hover::part(combobox) {
|
|
75
|
-
background-color: var(--zn-input-background-color-hover);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
.translation-group__language-select:focus-within::part(combobox) {
|
|
79
|
-
box-shadow: 0 0 0 var(--zn-focus-ring-width) var(--zn-input-focus-ring-color);
|
|
10
|
+
// The fields land in the form group's 6-column input grid, but they are assigned to this slot rather than the
|
|
11
|
+
// group's, so its span rules cannot reach them — each takes the full row from here.
|
|
12
|
+
::slotted(:not([slot])) {
|
|
13
|
+
grid-column: 1 / -1;
|
|
80
14
|
}
|
|
81
15
|
|
|
82
16
|
// The select's label is its accessible name only; the caption names the section on screen.
|
|
@@ -92,11 +26,13 @@
|
|
|
92
26
|
border: 0;
|
|
93
27
|
}
|
|
94
28
|
|
|
95
|
-
// Matches zinc's form action rows (.form-actions, zn-form-actions): right-aligned, 16px apart.
|
|
29
|
+
// Matches zinc's form action rows (.form-actions, zn-form-actions): right-aligned, 16px apart. The cap holds the
|
|
30
|
+
// buttons level with the fields' right edge, which zn-form-group sizes to the same container.
|
|
96
31
|
.translation-group__actions {
|
|
97
32
|
display: flex;
|
|
98
33
|
align-items: center;
|
|
99
34
|
gap: var(--zn-spacing-small);
|
|
35
|
+
max-width: var(--zn-container-lg);
|
|
100
36
|
}
|
|
101
37
|
|
|
102
38
|
// The spacer, not an auto margin, is what splits the two sides: an auto margin on every right-hand button would open
|
|
@@ -114,8 +50,8 @@
|
|
|
114
50
|
order: 0;
|
|
115
51
|
}
|
|
116
52
|
|
|
117
|
-
// The panel draws a border between any two body children, and the actions row
|
|
118
|
-
//
|
|
53
|
+
// The panel draws a border between any two body children, and the actions row follows the form group. Matched on
|
|
54
|
+
// .panel--has-actions to out-specify that rule.
|
|
119
55
|
.panel--has-actions .panel__body > .translation-group__actions {
|
|
120
56
|
border: 0;
|
|
121
57
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import '../../../dist/zn.min.js';
|
|
2
2
|
import { expect, fixture, html } from '@open-wc/testing';
|
|
3
|
+
import type ZnFormGroup from '../form-group/form-group.component';
|
|
3
4
|
import type ZnInput from '../input/input.component';
|
|
5
|
+
import type ZnOption from '../option/option.component';
|
|
4
6
|
import type ZnSelect from '../select/select.component';
|
|
5
7
|
import type ZnTranslationGroup from './translation-group.component';
|
|
6
8
|
import type ZnTranslations from '../translations/translations.component';
|
|
@@ -110,24 +112,30 @@ describe('<zn-translation-group>', () => {
|
|
|
110
112
|
|
|
111
113
|
const body = group.shadowRoot!.querySelector<HTMLElement>('.panel__body')!;
|
|
112
114
|
expect(getComputedStyle(body).paddingLeft).to.equal('0px');
|
|
113
|
-
|
|
114
|
-
|
|
115
|
+
|
|
116
|
+
const formGroup = group.shadowRoot!.querySelector<ZnFormGroup>('zn-form-group')!;
|
|
117
|
+
expect(formGroup.getBoundingClientRect().left, 'flush with the form around it')
|
|
118
|
+
.to.equal(group.getBoundingClientRect().left);
|
|
115
119
|
});
|
|
116
120
|
|
|
117
|
-
it('leaves the same gap
|
|
121
|
+
it('leaves the same gap between the fields as between the group and its actions', async () => {
|
|
118
122
|
const group = await fixture<ZnTranslationGroup>(html`
|
|
119
123
|
<zn-translation-group inline label="Content" .languages=${{en: 'English', fr: 'French'}}>
|
|
120
124
|
<zn-translations name="heading"></zn-translations>
|
|
121
125
|
</zn-translation-group>`);
|
|
122
|
-
// The spacing tokens live in the theme stylesheet, which the bundle under test does not carry.
|
|
126
|
+
// The spacing tokens live in the theme stylesheet, which the bundle under test does not carry. The form
|
|
127
|
+
// group's gap is one shorthand across both axes, so an unset column gap would void the row gap with it.
|
|
123
128
|
group.style.setProperty('--zn-spacing-medium', '24px');
|
|
129
|
+
group.style.setProperty('--zn-spacing-small', '16px');
|
|
124
130
|
await group.updateComplete;
|
|
125
131
|
|
|
126
|
-
const
|
|
132
|
+
const formGroup = group.shadowRoot!.querySelector<ZnFormGroup>('zn-form-group')!;
|
|
133
|
+
await formGroup.updateComplete;
|
|
134
|
+
const fields = formGroup.shadowRoot!.querySelector<HTMLElement>('.form-control-input')!;
|
|
127
135
|
const body = group.shadowRoot!.querySelector<HTMLElement>('.panel__body')!;
|
|
128
|
-
|
|
129
|
-
expect(getComputedStyle(
|
|
130
|
-
expect(getComputedStyle(
|
|
136
|
+
|
|
137
|
+
expect(getComputedStyle(fields).rowGap, "the form group's gap between fields").to.equal('24px');
|
|
138
|
+
expect(getComputedStyle(body).rowGap, 'and down to the actions row').to.equal('24px');
|
|
131
139
|
});
|
|
132
140
|
|
|
133
141
|
|
|
@@ -150,7 +158,7 @@ describe('<zn-translation-group>', () => {
|
|
|
150
158
|
return group.shadowRoot!.querySelector<ZnSelect>('zn-select')!;
|
|
151
159
|
}
|
|
152
160
|
|
|
153
|
-
it(
|
|
161
|
+
it("sits under the caption, in the form group's chip slot", async () => {
|
|
154
162
|
const group = await fixture<ZnTranslationGroup>(html`
|
|
155
163
|
<zn-translation-group label="Content" .languages=${{en: 'English', fr: 'French'}}>
|
|
156
164
|
<zn-translations name="heading"></zn-translations>
|
|
@@ -158,66 +166,92 @@ describe('<zn-translation-group>', () => {
|
|
|
158
166
|
await group.updateComplete;
|
|
159
167
|
|
|
160
168
|
const field = group.shadowRoot!.querySelector<HTMLElement>('.translation-group__language-field')!;
|
|
161
|
-
expect(field.
|
|
162
|
-
|
|
163
|
-
|
|
169
|
+
expect(field.assignedSlot?.name, 'assigned to the chip slot').to.equal('chip');
|
|
170
|
+
|
|
171
|
+
const formGroup = group.shadowRoot!.querySelector<ZnFormGroup>('zn-form-group')!;
|
|
172
|
+
await formGroup.updateComplete;
|
|
173
|
+
const label = formGroup.shadowRoot!.querySelector<HTMLElement>('[part="form-control-label"]')!;
|
|
174
|
+
expect(field.getBoundingClientRect().top, 'below the caption')
|
|
175
|
+
.to.be.greaterThan(label.getBoundingClientRect().bottom - 1);
|
|
164
176
|
});
|
|
165
177
|
|
|
166
|
-
it('leaves the caption to the label
|
|
178
|
+
it('leaves the caption to the form group label, with the count on the chip', async () => {
|
|
167
179
|
const group = await fixture<ZnTranslationGroup>(html`
|
|
168
180
|
<zn-translation-group label="Content" .languages=${{en: 'English', fr: 'French'}}>
|
|
169
181
|
<zn-translations name="heading" value='{"en":"Hi","fr":"Salut"}'></zn-translations>
|
|
170
182
|
</zn-translation-group>`);
|
|
171
183
|
await group.updateComplete;
|
|
172
184
|
|
|
173
|
-
const
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
expect(
|
|
185
|
+
const formGroup = group.shadowRoot!.querySelector<ZnFormGroup>('zn-form-group')!;
|
|
186
|
+
await formGroup.updateComplete;
|
|
187
|
+
const label = formGroup.shadowRoot!.querySelector<HTMLElement>('[part="form-control-label"]')!;
|
|
188
|
+
expect(label.textContent?.trim(), 'nothing else rides the caption').to.equal('Content');
|
|
189
|
+
|
|
190
|
+
const summary = selectOf(group).querySelector('zn-chip[slot="suffix"]')!;
|
|
191
|
+
expect(summary.textContent?.trim()).to.equal('1/1');
|
|
177
192
|
});
|
|
178
193
|
|
|
179
|
-
it('
|
|
194
|
+
it('wears the same chrome as any other select', async () => {
|
|
180
195
|
const group = await fixture<ZnTranslationGroup>(html`
|
|
181
196
|
<zn-translation-group label="Content" .languages=${{en: 'English', fr: 'French'}}>
|
|
182
197
|
<zn-translations name="heading"></zn-translations>
|
|
183
198
|
</zn-translation-group>`);
|
|
184
199
|
// The input tokens live in the theme stylesheet, which the bundle under test does not carry.
|
|
200
|
+
group.style.setProperty('--zn-input-background-color', 'rgb(1, 2, 3)');
|
|
201
|
+
group.style.setProperty('--zn-shadow-x-small', '0 1px 2px rgb(7, 8, 9)');
|
|
185
202
|
group.style.setProperty('--zn-input-border-width', '1px');
|
|
186
203
|
group.style.setProperty('--zn-input-border-color', 'rgb(9, 9, 9)');
|
|
187
|
-
group.style.setProperty('--zn-focus-ring-width', '3px');
|
|
188
|
-
group.style.setProperty('--zn-input-focus-ring-color', 'rgb(4, 5, 6)');
|
|
189
204
|
await group.updateComplete;
|
|
190
205
|
|
|
191
206
|
const select = selectOf(group);
|
|
192
207
|
await select.updateComplete;
|
|
193
|
-
const
|
|
208
|
+
const styles = getComputedStyle(select.shadowRoot!.querySelector<HTMLElement>('[part~="combobox"]')!);
|
|
194
209
|
|
|
195
|
-
|
|
196
|
-
expect(styles.
|
|
197
|
-
expect(styles.
|
|
198
|
-
expect(styles.
|
|
199
|
-
|
|
210
|
+
expect(styles.backgroundColor, 'the fill').to.equal('rgb(1, 2, 3)');
|
|
211
|
+
expect(styles.boxShadow, 'the shadow').to.include('rgb(7, 8, 9)');
|
|
212
|
+
expect(styles.borderTopWidth, 'the border').to.equal('1px');
|
|
213
|
+
expect(styles.borderTopColor, 'the border').to.equal('rgb(9, 9, 9)');
|
|
214
|
+
});
|
|
200
215
|
|
|
201
|
-
|
|
216
|
+
it('fills the width of the label column', async () => {
|
|
217
|
+
const el = await fixture<HTMLElement>(html`
|
|
218
|
+
<div style="width: 900px">
|
|
219
|
+
<zn-translation-group label="Content" .languages=${{en: 'English', fr: 'French'}}>
|
|
220
|
+
<zn-translations name="heading"></zn-translations>
|
|
221
|
+
</zn-translation-group>
|
|
222
|
+
</div>`);
|
|
223
|
+
const group = el.querySelector<ZnTranslationGroup>('zn-translation-group')!;
|
|
224
|
+
await group.updateComplete;
|
|
225
|
+
|
|
226
|
+
const formGroup = group.shadowRoot!.querySelector<ZnFormGroup>('zn-form-group')!;
|
|
227
|
+
await formGroup.updateComplete;
|
|
228
|
+
const column = formGroup.shadowRoot!.querySelector<HTMLElement>('[part="form-control-chip"]')!;
|
|
229
|
+
|
|
230
|
+
const select = selectOf(group);
|
|
202
231
|
await select.updateComplete;
|
|
203
|
-
expect(
|
|
232
|
+
expect(select.getBoundingClientRect().width).to.be.closeTo(column.getBoundingClientRect().width, 1);
|
|
204
233
|
});
|
|
205
234
|
|
|
206
|
-
it('
|
|
235
|
+
it('filters the list on the name or the code, which stays off the option', async () => {
|
|
207
236
|
const group = await fixture<ZnTranslationGroup>(html`
|
|
208
|
-
<zn-translation-group label="Content" .languages=${{en: 'English', fr: 'French'}}>
|
|
237
|
+
<zn-translation-group label="Content" .languages=${{en: 'English', fr: 'French', de: 'German'}}>
|
|
209
238
|
<zn-translations name="heading"></zn-translations>
|
|
210
239
|
</zn-translation-group>`);
|
|
211
240
|
await group.updateComplete;
|
|
212
241
|
|
|
213
242
|
const select = selectOf(group);
|
|
214
243
|
await select.updateComplete;
|
|
244
|
+
const option = (code: string) => select.querySelector<ZnOption>(`zn-option[value="${code}"]`)!;
|
|
245
|
+
expect(option('de').getTextLabel().trim(), 'the code is not on show').to.equal('German');
|
|
215
246
|
|
|
216
|
-
|
|
217
|
-
|
|
247
|
+
await select.show();
|
|
248
|
+
const input = select.shadowRoot!.querySelector<HTMLInputElement>('[part~="display-input"]')!;
|
|
249
|
+
input.value = 'de';
|
|
250
|
+
input.dispatchEvent(new InputEvent('input', {bubbles: true, composed: true}));
|
|
251
|
+
await select.updateComplete;
|
|
218
252
|
|
|
219
|
-
|
|
220
|
-
expect(
|
|
253
|
+
expect(option('de').hidden, 'matched on its code').to.be.false;
|
|
254
|
+
expect(option('fr').hidden, 'and nothing else').to.be.true;
|
|
221
255
|
});
|
|
222
256
|
|
|
223
257
|
it('names the select for a screen reader without showing the label', async () => {
|
|
@@ -234,18 +268,22 @@ describe('<zn-translation-group>', () => {
|
|
|
234
268
|
expect(label.getBoundingClientRect().width).to.be.lessThan(2);
|
|
235
269
|
});
|
|
236
270
|
|
|
237
|
-
it('
|
|
271
|
+
it('stands the select on its own with no caption', async () => {
|
|
238
272
|
const group = await fixture<ZnTranslationGroup>(html`
|
|
239
273
|
<zn-translation-group .languages=${{en: 'English', fr: 'French'}}>
|
|
240
274
|
<zn-translations name="heading"></zn-translations>
|
|
241
275
|
</zn-translation-group>`);
|
|
242
276
|
await group.updateComplete;
|
|
243
277
|
|
|
244
|
-
|
|
278
|
+
const formGroup = group.shadowRoot!.querySelector<ZnFormGroup>('zn-form-group')!;
|
|
279
|
+
await formGroup.updateComplete;
|
|
280
|
+
|
|
245
281
|
expect(selectOf(group)).to.exist;
|
|
282
|
+
expect(formGroup.shadowRoot!.querySelector('[part="form-control-label"]'), 'no empty caption').to.be.null;
|
|
283
|
+
expect(formGroup.shadowRoot!.querySelector('[part="form-control-chip"]'), 'the select keeps its column').to.exist;
|
|
246
284
|
});
|
|
247
285
|
|
|
248
|
-
it('keeps the
|
|
286
|
+
it('keeps the form group to the fields, with slotted actions going to the bottom', async () => {
|
|
249
287
|
const group = await fixture<ZnTranslationGroup>(html`
|
|
250
288
|
<zn-translation-group label="Content" .languages=${{en: 'English', fr: 'French'}}>
|
|
251
289
|
<zn-button slot="actions">Auto translate</zn-button>
|
|
@@ -254,7 +292,7 @@ describe('<zn-translation-group>', () => {
|
|
|
254
292
|
await group.updateComplete;
|
|
255
293
|
|
|
256
294
|
const actionsSlot = group.shadowRoot!.querySelector('slot[name="actions"]')!;
|
|
257
|
-
expect(actionsSlot.closest('zn-
|
|
295
|
+
expect(actionsSlot.closest('zn-form-group'), 'the form group takes no actions').to.be.null;
|
|
258
296
|
expect(actionsSlot.closest('.panel__body'), 'they go to the body').to.exist;
|
|
259
297
|
});
|
|
260
298
|
|
|
@@ -223,14 +223,9 @@ export default class ZnTranslations extends ZincElement implements ZincFormContr
|
|
|
223
223
|
return {type: 'error', label: language === 'en' ? 'Empty' : 'English'};
|
|
224
224
|
}
|
|
225
225
|
|
|
226
|
-
/**
|
|
227
|
-
* `English (EN)` — the configured name plus its code, unless the name already is the code, in which case the code
|
|
228
|
-
* alone. `languages` is written both ways: `{"en": "English"}` and `{"en": "EN"}`.
|
|
229
|
-
*/
|
|
226
|
+
/** The configured name, or the code where `languages` does not name the language. */
|
|
230
227
|
private languageLabel(language: string): string {
|
|
231
|
-
|
|
232
|
-
const code = language.toUpperCase();
|
|
233
|
-
return name.toUpperCase() === code ? name : `${name} (${code})`;
|
|
228
|
+
return this.languages[language] ?? language.toUpperCase();
|
|
234
229
|
}
|
|
235
230
|
|
|
236
231
|
/**
|
|
@@ -496,6 +491,7 @@ export default class ZnTranslations extends ZincElement implements ZincFormContr
|
|
|
496
491
|
class="translations__language-select"
|
|
497
492
|
part="language-select"
|
|
498
493
|
hoist
|
|
494
|
+
search
|
|
499
495
|
?disabled="${this.disabled}"
|
|
500
496
|
.value="${this._activeLanguage}"
|
|
501
497
|
@zn-change="${this.handleLanguageSelect}"
|