@kubex/zinc 1.1.141 → 1.1.143
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 +242 -9
- package/dist/vscode.html-custom-data.json +34 -4
- package/dist/web-types.json +81 -8
- package/dist/zn.d.ts +558 -531
- package/dist/zn.min.css +1 -1
- package/dist/zn.min.js +895 -866
- package/docs/pages/components/form-actions.md +15 -0
- package/package.json +1 -1
- package/scss/_global-spacing.scss +7 -0
- package/src/components/form-actions/form-actions.component.ts +4 -1
- package/src/components/form-actions/form-actions.scss +17 -0
- package/src/components/form-actions/form-actions.test.ts +20 -0
- package/src/components/form-group/form-group.scss +11 -1
- package/src/components/form-group/form-group.test.ts +16 -0
- package/src/components/linked-select/linked-select.component.ts +37 -5
- package/src/components/linked-select/linked-select.test.ts +95 -9
- package/src/components/page-builder/page-builder.component.ts +56 -2
- package/src/components/page-builder/page-builder.scss +41 -0
- package/src/components/page-builder/page-builder.test.ts +210 -0
- package/src/components/remarkd-editor/remarkd-editor.component.ts +2 -0
- package/src/components/remarkd-editor/remarkd-editor.test.ts +35 -7
- package/src/components/sp/sp.scss +6 -0
- package/src/components/sp/sp.test.ts +31 -0
- package/src/components/translations/translations.component.ts +33 -2
- package/src/components/translations/translations.test.ts +41 -0
|
@@ -12,6 +12,21 @@ describe('<zn-page-builder>', () => {
|
|
|
12
12
|
expect(el.shadowRoot?.querySelector('[part="canvas"]')).to.exist;
|
|
13
13
|
});
|
|
14
14
|
|
|
15
|
+
it('should fill the height it is given inside a growing panel', async () => {
|
|
16
|
+
const parent = await fixture(html`
|
|
17
|
+
<div style="display: flex; flex-direction: column; height: 700px; width: 900px;">
|
|
18
|
+
<zn-sp grow flush>
|
|
19
|
+
<form>
|
|
20
|
+
<zn-page-builder name="config"></zn-page-builder>
|
|
21
|
+
</form>
|
|
22
|
+
</zn-sp>
|
|
23
|
+
</div>`);
|
|
24
|
+
const el = parent.querySelector<ZnPageBuilder>('zn-page-builder')!;
|
|
25
|
+
await el.updateComplete;
|
|
26
|
+
|
|
27
|
+
expect(el.getBoundingClientRect().height).to.equal(700);
|
|
28
|
+
});
|
|
29
|
+
|
|
15
30
|
it('should build the palette from slotted config templates', async () => {
|
|
16
31
|
const el = await fixture<ZnPageBuilder>(html`
|
|
17
32
|
<zn-page-builder>
|
|
@@ -312,6 +327,201 @@ describe('<zn-page-builder>', () => {
|
|
|
312
327
|
expect(unnamed?.querySelector('.inspector-head__type'), 'no duplicate type line').to.not.exist;
|
|
313
328
|
});
|
|
314
329
|
|
|
330
|
+
it('should resize the inspector by dragging its edge handle', async () => {
|
|
331
|
+
const el = await fixture<ZnPageBuilder>(html`
|
|
332
|
+
<zn-page-builder style="width: 1200px; height: 600px" config='{"sections":[{"id":"s1","type":"hero","data":{}}]}'>
|
|
333
|
+
<template type="hero" slot="config" label="Hero">
|
|
334
|
+
<zn-input name="title" label="Title"></zn-input>
|
|
335
|
+
</template>
|
|
336
|
+
</zn-page-builder>`);
|
|
337
|
+
await el.updateComplete;
|
|
338
|
+
|
|
339
|
+
el.shadowRoot?.querySelector('zn-page-section-card')?.dispatchEvent(new Event('click'));
|
|
340
|
+
await el.updateComplete;
|
|
341
|
+
|
|
342
|
+
const builder = el.shadowRoot!.querySelector<HTMLElement>('.builder')!;
|
|
343
|
+
const handle = el.shadowRoot!.querySelector<HTMLElement>('.inspector-resize');
|
|
344
|
+
expect(handle, 'resize handle').to.exist;
|
|
345
|
+
|
|
346
|
+
const before = el.shadowRoot!.querySelector('.inspector')!.getBoundingClientRect().width;
|
|
347
|
+
handle!.dispatchEvent(new PointerEvent('pointerdown', {bubbles: true, clientX: 800, pointerId: 1}));
|
|
348
|
+
window.dispatchEvent(new PointerEvent('pointermove', {bubbles: true, clientX: 600, pointerId: 1}));
|
|
349
|
+
window.dispatchEvent(new PointerEvent('pointerup', {bubbles: true, pointerId: 1}));
|
|
350
|
+
await el.updateComplete;
|
|
351
|
+
|
|
352
|
+
expect(builder.style.getPropertyValue('--inspector-col')).to.equal(`${Math.round(before + 200)}px`);
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
it('should hold the dragged inspector above its minimum width', async () => {
|
|
356
|
+
const el = await fixture<ZnPageBuilder>(html`
|
|
357
|
+
<zn-page-builder style="width: 1200px; height: 600px" config='{"sections":[{"id":"s1","type":"hero","data":{}}]}'>
|
|
358
|
+
<template type="hero" slot="config" label="Hero"></template>
|
|
359
|
+
</zn-page-builder>`);
|
|
360
|
+
await el.updateComplete;
|
|
361
|
+
|
|
362
|
+
el.shadowRoot?.querySelector('zn-page-section-card')?.dispatchEvent(new Event('click'));
|
|
363
|
+
await el.updateComplete;
|
|
364
|
+
|
|
365
|
+
const builder = el.shadowRoot!.querySelector<HTMLElement>('.builder')!;
|
|
366
|
+
const handle = el.shadowRoot!.querySelector<HTMLElement>('.inspector-resize')!;
|
|
367
|
+
handle.dispatchEvent(new PointerEvent('pointerdown', {bubbles: true, clientX: 800, pointerId: 1}));
|
|
368
|
+
window.dispatchEvent(new PointerEvent('pointermove', {bubbles: true, clientX: 5000, pointerId: 1}));
|
|
369
|
+
window.dispatchEvent(new PointerEvent('pointerup', {bubbles: true, pointerId: 1}));
|
|
370
|
+
await el.updateComplete;
|
|
371
|
+
|
|
372
|
+
expect(parseInt(builder.style.getPropertyValue('--inspector-col'), 10)).to.equal(300);
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
it('should resize the inspector from the keyboard', async () => {
|
|
376
|
+
const el = await fixture<ZnPageBuilder>(html`
|
|
377
|
+
<zn-page-builder style="width: 1200px; height: 600px" config='{"sections":[{"id":"s1","type":"hero","data":{}}]}'>
|
|
378
|
+
<template type="hero" slot="config" label="Hero"></template>
|
|
379
|
+
</zn-page-builder>`);
|
|
380
|
+
await el.updateComplete;
|
|
381
|
+
|
|
382
|
+
el.shadowRoot?.querySelector('zn-page-section-card')?.dispatchEvent(new Event('click'));
|
|
383
|
+
await el.updateComplete;
|
|
384
|
+
|
|
385
|
+
const builder = el.shadowRoot!.querySelector<HTMLElement>('.builder')!;
|
|
386
|
+
const handle = el.shadowRoot!.querySelector<HTMLElement>('.inspector-resize')!;
|
|
387
|
+
expect(handle.getAttribute('role')).to.equal('separator');
|
|
388
|
+
|
|
389
|
+
handle.dispatchEvent(new KeyboardEvent('keydown', {key: 'ArrowLeft', bubbles: true}));
|
|
390
|
+
await el.updateComplete;
|
|
391
|
+
expect(parseInt(builder.style.getPropertyValue('--inspector-col'), 10)).to.equal(363);
|
|
392
|
+
|
|
393
|
+
handle.dispatchEvent(new KeyboardEvent('keydown', {key: 'ArrowRight', bubbles: true}));
|
|
394
|
+
await el.updateComplete;
|
|
395
|
+
expect(parseInt(builder.style.getPropertyValue('--inspector-col'), 10)).to.equal(343);
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
it('should still tuck a resized inspector fully away', async () => {
|
|
399
|
+
const el = await fixture<ZnPageBuilder>(html`
|
|
400
|
+
<zn-page-builder style="width: 1200px; height: 600px" config='{"sections":[{"id":"s1","type":"hero","data":{}}]}'>
|
|
401
|
+
<template type="hero" slot="config" label="Hero"></template>
|
|
402
|
+
</zn-page-builder>`);
|
|
403
|
+
await el.updateComplete;
|
|
404
|
+
|
|
405
|
+
el.shadowRoot?.querySelector('zn-page-section-card')?.dispatchEvent(new Event('click'));
|
|
406
|
+
await el.updateComplete;
|
|
407
|
+
|
|
408
|
+
const handle = el.shadowRoot!.querySelector<HTMLElement>('.inspector-resize')!;
|
|
409
|
+
handle.dispatchEvent(new PointerEvent('pointerdown', {bubbles: true, clientX: 800, pointerId: 1}));
|
|
410
|
+
window.dispatchEvent(new PointerEvent('pointermove', {bubbles: true, clientX: 600, pointerId: 1}));
|
|
411
|
+
window.dispatchEvent(new PointerEvent('pointerup', {bubbles: true, pointerId: 1}));
|
|
412
|
+
await el.updateComplete;
|
|
413
|
+
|
|
414
|
+
const builder = el.shadowRoot!.querySelector<HTMLElement>('.builder')!;
|
|
415
|
+
expect(parseInt(builder.style.getPropertyValue('--inspector-col'), 10), 'resized').to.be.greaterThan(343);
|
|
416
|
+
|
|
417
|
+
// An inline width outranks the collapsed rule's 0px, so it has to step aside.
|
|
418
|
+
el.inspectorCollapsed = true;
|
|
419
|
+
await el.updateComplete;
|
|
420
|
+
expect(getComputedStyle(builder).getPropertyValue('--inspector-col').trim()).to.equal('0px');
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
const richBuilder = (height: number) => fixture<ZnPageBuilder>(html`
|
|
424
|
+
<zn-page-builder style="width: 1200px; height: ${height}px" config='{"sections":[{"id":"s1","type":"rich","data":{}}]}'>
|
|
425
|
+
<template type="rich" slot="config" label="Rich Text">
|
|
426
|
+
<zn-translations name="content" label="Content" input-type="remarkd"></zn-translations>
|
|
427
|
+
</template>
|
|
428
|
+
</zn-page-builder>`);
|
|
429
|
+
|
|
430
|
+
it('should widen the inspector by default for a remarkd editor', async () => {
|
|
431
|
+
const el = await richBuilder(600);
|
|
432
|
+
await el.updateComplete;
|
|
433
|
+
|
|
434
|
+
el.shadowRoot?.querySelector('zn-page-section-card')?.dispatchEvent(new Event('click'));
|
|
435
|
+
await el.updateComplete;
|
|
436
|
+
|
|
437
|
+
const builder = el.shadowRoot!.querySelector<HTMLElement>('.builder')!;
|
|
438
|
+
expect(builder.classList.contains('builder--inspector-wide'), 'wide').to.be.true;
|
|
439
|
+
expect(getComputedStyle(builder).getPropertyValue('--inspector-col').trim()).to.equal('520px');
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
it('should leave the inspector at its usual width without a remarkd editor', async () => {
|
|
443
|
+
const el = await fixture<ZnPageBuilder>(html`
|
|
444
|
+
<zn-page-builder style="width: 1200px; height: 600px" config='{"sections":[{"id":"s1","type":"hero","data":{}}]}'>
|
|
445
|
+
<template type="hero" slot="config" label="Hero">
|
|
446
|
+
<zn-input name="title" label="Title"></zn-input>
|
|
447
|
+
</template>
|
|
448
|
+
</zn-page-builder>`);
|
|
449
|
+
await el.updateComplete;
|
|
450
|
+
|
|
451
|
+
el.shadowRoot?.querySelector('zn-page-section-card')?.dispatchEvent(new Event('click'));
|
|
452
|
+
await el.updateComplete;
|
|
453
|
+
|
|
454
|
+
const builder = el.shadowRoot!.querySelector<HTMLElement>('.builder')!;
|
|
455
|
+
expect(builder.classList.contains('builder--inspector-wide')).to.be.false;
|
|
456
|
+
expect(getComputedStyle(builder).getPropertyValue('--inspector-col').trim()).to.equal('343px');
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
it('should prefill translations stamped inside a translation group', async () => {
|
|
460
|
+
const state = JSON.stringify({
|
|
461
|
+
sections: [{
|
|
462
|
+
id: 's1', type: 'tile', data: {
|
|
463
|
+
title: JSON.stringify({en: 'Billing'}),
|
|
464
|
+
subtitle: JSON.stringify({en: 'How to pay'})
|
|
465
|
+
}
|
|
466
|
+
}]
|
|
467
|
+
});
|
|
468
|
+
const el = await fixture<ZnPageBuilder>(html`
|
|
469
|
+
<zn-page-builder .config="${state}">
|
|
470
|
+
<template type="tile" slot="config" label="Tile">
|
|
471
|
+
<zn-translation-group inline languages='{"en":"English","fr":"French"}'>
|
|
472
|
+
<zn-translations name="title" label="Title"></zn-translations>
|
|
473
|
+
<zn-translations name="subtitle" label="Subtitle"></zn-translations>
|
|
474
|
+
</zn-translation-group>
|
|
475
|
+
</template>
|
|
476
|
+
</zn-page-builder>`);
|
|
477
|
+
await el.updateComplete;
|
|
478
|
+
|
|
479
|
+
el.shadowRoot?.querySelector('zn-page-section-card')?.dispatchEvent(new Event('click'));
|
|
480
|
+
await el.updateComplete;
|
|
481
|
+
|
|
482
|
+
const fields = [...el.shadowRoot!.querySelectorAll<HTMLElement & {
|
|
483
|
+
values: Record<string, string>; languages: Record<string, string>; grouped: boolean;
|
|
484
|
+
updateComplete: Promise<unknown>;
|
|
485
|
+
}>('.inspector__form zn-translations')];
|
|
486
|
+
expect(fields.length, 'both fields stamped').to.equal(2);
|
|
487
|
+
await Promise.all(fields.map(f => f.updateComplete));
|
|
488
|
+
|
|
489
|
+
expect(fields[0].values.en).to.equal('Billing');
|
|
490
|
+
expect(fields[1].values.en).to.equal('How to pay');
|
|
491
|
+
// The group owns the select and the language list, so the children drop theirs.
|
|
492
|
+
fields.forEach(field => {
|
|
493
|
+
expect(field.grouped, 'grouped').to.be.true;
|
|
494
|
+
expect(field.languages).to.have.property('fr');
|
|
495
|
+
expect(field.shadowRoot!.querySelector('[part="language-select"]'), 'child select').to.not.exist;
|
|
496
|
+
});
|
|
497
|
+
expect(el.shadowRoot!.querySelectorAll('.inspector__form [part="language-select"]').length).to.equal(0);
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
it('should not strand the group language select above its fields', async () => {
|
|
501
|
+
const el = await fixture<ZnPageBuilder>(html`
|
|
502
|
+
<zn-page-builder style="width: 1200px; height: 600px" config='{"sections":[{"id":"s1","type":"tile","data":{}}]}'>
|
|
503
|
+
<template type="tile" slot="config" label="Tile">
|
|
504
|
+
<zn-translation-group inline languages='{"en":"English","fr":"French"}'>
|
|
505
|
+
<zn-translations name="title" label="Title" flush></zn-translations>
|
|
506
|
+
<zn-translations name="subtitle" label="Subtitle" flush></zn-translations>
|
|
507
|
+
</zn-translation-group>
|
|
508
|
+
</template>
|
|
509
|
+
</zn-page-builder>`);
|
|
510
|
+
await el.updateComplete;
|
|
511
|
+
|
|
512
|
+
el.shadowRoot?.querySelector('zn-page-section-card')?.dispatchEvent(new Event('click'));
|
|
513
|
+
await el.updateComplete;
|
|
514
|
+
|
|
515
|
+
const group = el.shadowRoot!.querySelector<HTMLElement & { updateComplete: Promise<unknown> }>(
|
|
516
|
+
'.inspector__form zn-translation-group')!;
|
|
517
|
+
await group.updateComplete;
|
|
518
|
+
const select = group.shadowRoot!.querySelector<HTMLElement>('[part="language-field"]')!;
|
|
519
|
+
const first = group.querySelector<HTMLElement>('zn-translations')!;
|
|
520
|
+
|
|
521
|
+
const gap = first.getBoundingClientRect().top - select.getBoundingClientRect().bottom;
|
|
522
|
+
expect(gap, 'gap under the language select').to.be.at.most(24);
|
|
523
|
+
});
|
|
524
|
+
|
|
315
525
|
it('should clear the selection from the inspector close button', async () => {
|
|
316
526
|
const el = await fixture<ZnPageBuilder>(html`
|
|
317
527
|
<zn-page-builder config='{"sections":[{"id":"s1","type":"hero","data":{}}]}'>
|
|
@@ -1778,12 +1778,14 @@ export default class ZnRemarkdEditor extends ZincElement implements ZincFormCont
|
|
|
1778
1778
|
|
|
1779
1779
|
/** A picker action with no endpoint configured is not offered at all. */
|
|
1780
1780
|
private actionAvailable(action: EditorAction): boolean {
|
|
1781
|
+
if (action.opens === 'image') return !!this.attachmentUrl;
|
|
1781
1782
|
if (action.opens === 'include') return !!this.includeUrl;
|
|
1782
1783
|
if (action.opens === 'link') return !!this.linkUrl;
|
|
1783
1784
|
return true;
|
|
1784
1785
|
}
|
|
1785
1786
|
|
|
1786
1787
|
private slashItemAvailable(item: SlashMenuItem): boolean {
|
|
1788
|
+
if (item.action === 'image') return !!this.attachmentUrl;
|
|
1787
1789
|
if (item.action === 'include') return !!this.includeUrl;
|
|
1788
1790
|
if (item.action === 'link') return !!this.linkUrl;
|
|
1789
1791
|
return true;
|
|
@@ -331,7 +331,7 @@ Second"></zn-remarkd-editor>`);
|
|
|
331
331
|
|
|
332
332
|
it('should group the toolbar buttons', async () => {
|
|
333
333
|
const el = await fixture<ZnRemarkdEditor>(html`
|
|
334
|
-
<zn-remarkd-editor include-url="/includes" link-url="/links"></zn-remarkd-editor>`);
|
|
334
|
+
<zn-remarkd-editor attachment-url="/attachments" include-url="/includes" link-url="/links"></zn-remarkd-editor>`);
|
|
335
335
|
const groups = el.shadowRoot!.querySelectorAll('.remarkd-editor__toolbar .toolbar__group');
|
|
336
336
|
|
|
337
337
|
expect(groups.length).to.be.greaterThan(5);
|
|
@@ -343,7 +343,7 @@ Second"></zn-remarkd-editor>`);
|
|
|
343
343
|
|
|
344
344
|
it('should move toolbar groups into an overflow menu when narrow', async () => {
|
|
345
345
|
const el = await fixture<ZnRemarkdEditor>(html`
|
|
346
|
-
<zn-remarkd-editor style="width: 240px"></zn-remarkd-editor>`);
|
|
346
|
+
<zn-remarkd-editor attachment-url="/attachments" style="width: 240px"></zn-remarkd-editor>`);
|
|
347
347
|
await el.updateComplete;
|
|
348
348
|
await waitUntil(() => el.shadowRoot!.querySelector('.remarkd-editor__toolbar-more'),
|
|
349
349
|
'the overflow trigger never appeared');
|
|
@@ -748,7 +748,7 @@ Second"></zn-remarkd-editor>`);
|
|
|
748
748
|
|
|
749
749
|
it('should list every action in the slash menu under its group', async () => {
|
|
750
750
|
const el = await fixture<ZnRemarkdEditor>(html`
|
|
751
|
-
<zn-remarkd-editor value="Hello" include-url="/includes" link-url="/links"></zn-remarkd-editor>`);
|
|
751
|
+
<zn-remarkd-editor value="Hello" attachment-url="/attachments" include-url="/includes" link-url="/links"></zn-remarkd-editor>`);
|
|
752
752
|
el.shadowRoot!.querySelector<HTMLElement>('.remarkd-editor__rendered')!.click();
|
|
753
753
|
await el.updateComplete;
|
|
754
754
|
typeInBlock(el, '/');
|
|
@@ -761,20 +761,32 @@ Second"></zn-remarkd-editor>`);
|
|
|
761
761
|
|
|
762
762
|
it('should omit the Include action from the slash menu without an include-url', async () => {
|
|
763
763
|
const el = await fixture<ZnRemarkdEditor>(html`
|
|
764
|
-
<zn-remarkd-editor value="Hello"></zn-remarkd-editor>`);
|
|
764
|
+
<zn-remarkd-editor value="Hello" attachment-url="/attachments" link-url="/links"></zn-remarkd-editor>`);
|
|
765
765
|
el.shadowRoot!.querySelector<HTMLElement>('.remarkd-editor__rendered')!.click();
|
|
766
766
|
await el.updateComplete;
|
|
767
767
|
typeInBlock(el, '/');
|
|
768
768
|
await waitUntil(() => el.shadowRoot!.querySelector('zn-slash-menu[open]'), 'the slash menu never opened');
|
|
769
769
|
|
|
770
770
|
const menu = el.shadowRoot!.querySelector<ZnSlashMenu>('zn-slash-menu')!;
|
|
771
|
-
expect(menu.items.length).to.equal(EDITOR_ACTIONS.length -
|
|
771
|
+
expect(menu.items.length).to.equal(EDITOR_ACTIONS.length - 1);
|
|
772
772
|
expect(menu.items.every(item => item.action !== 'include')).to.be.true;
|
|
773
773
|
});
|
|
774
774
|
|
|
775
|
+
it('should omit the Image action from the slash menu without an attachment-url', async () => {
|
|
776
|
+
const el = await fixture<ZnRemarkdEditor>(html`
|
|
777
|
+
<zn-remarkd-editor value="Hello" include-url="/includes" link-url="/links"></zn-remarkd-editor>`);
|
|
778
|
+
el.shadowRoot!.querySelector<HTMLElement>('.remarkd-editor__rendered')!.click();
|
|
779
|
+
await el.updateComplete;
|
|
780
|
+
typeInBlock(el, '/');
|
|
781
|
+
await waitUntil(() => el.shadowRoot!.querySelector('zn-slash-menu[open]'), 'the slash menu never opened');
|
|
782
|
+
|
|
783
|
+
const menu = el.shadowRoot!.querySelector<ZnSlashMenu>('zn-slash-menu')!;
|
|
784
|
+
expect(menu.items.every(item => item.action !== 'image')).to.be.true;
|
|
785
|
+
});
|
|
786
|
+
|
|
775
787
|
it('should omit the article link action without a link-url', async () => {
|
|
776
788
|
const el = await fixture<ZnRemarkdEditor>(html`
|
|
777
|
-
<zn-remarkd-editor value="Hello" include-url="/includes"></zn-remarkd-editor>`);
|
|
789
|
+
<zn-remarkd-editor value="Hello" attachment-url="/attachments" include-url="/includes"></zn-remarkd-editor>`);
|
|
778
790
|
el.shadowRoot!.querySelector<HTMLElement>('.remarkd-editor__rendered')!.click();
|
|
779
791
|
await el.updateComplete;
|
|
780
792
|
typeInBlock(el, '/');
|
|
@@ -787,7 +799,7 @@ Second"></zn-remarkd-editor>`);
|
|
|
787
799
|
|
|
788
800
|
it('should offer the article link action with a link-url', async () => {
|
|
789
801
|
const el = await fixture<ZnRemarkdEditor>(html`
|
|
790
|
-
<zn-remarkd-editor value="Hello" include-url="/includes" link-url="/links"></zn-remarkd-editor>`);
|
|
802
|
+
<zn-remarkd-editor value="Hello" attachment-url="/attachments" include-url="/includes" link-url="/links"></zn-remarkd-editor>`);
|
|
791
803
|
el.shadowRoot!.querySelector<HTMLElement>('.remarkd-editor__rendered')!.click();
|
|
792
804
|
await el.updateComplete;
|
|
793
805
|
typeInBlock(el, '/');
|
|
@@ -1179,6 +1191,22 @@ include::inc-1[Payment Terms]"></zn-remarkd-editor>`);
|
|
|
1179
1191
|
expect(options[0].textContent).to.contain('Refund Policy');
|
|
1180
1192
|
});
|
|
1181
1193
|
|
|
1194
|
+
it('should offer no image entry points without an attachment-url', async () => {
|
|
1195
|
+
const el = await fixture<ZnRemarkdEditor>(html`
|
|
1196
|
+
<zn-remarkd-editor value="# Title"></zn-remarkd-editor>`);
|
|
1197
|
+
const tooltips = Array.from(el.shadowRoot!.querySelectorAll('.remarkd-editor__toolbar zn-button'))
|
|
1198
|
+
.map(button => button.getAttribute('tooltip'));
|
|
1199
|
+
expect(tooltips).to.not.contain('Image');
|
|
1200
|
+
});
|
|
1201
|
+
|
|
1202
|
+
it('should offer the Image action with an attachment-url', async () => {
|
|
1203
|
+
const el = await fixture<ZnRemarkdEditor>(html`
|
|
1204
|
+
<zn-remarkd-editor value="# Title" attachment-url="/attachments"></zn-remarkd-editor>`);
|
|
1205
|
+
const tooltips = Array.from(el.shadowRoot!.querySelectorAll('.remarkd-editor__toolbar zn-button'))
|
|
1206
|
+
.map(button => button.getAttribute('tooltip'));
|
|
1207
|
+
expect(tooltips).to.contain('Image');
|
|
1208
|
+
});
|
|
1209
|
+
|
|
1182
1210
|
it('should offer no include entry points without an include-url', async () => {
|
|
1183
1211
|
const el = await fixture<ZnRemarkdEditor>(html`
|
|
1184
1212
|
<zn-remarkd-editor value="# Title"></zn-remarkd-editor>`);
|
|
@@ -15,6 +15,12 @@
|
|
|
15
15
|
height: 100%;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
// A form wrapping the whole panel is there to submit it, not to lay it out —
|
|
19
|
+
// leaving it in the box tree floors the panel at the form's content height.
|
|
20
|
+
:host([grow]) ::slotted(form:only-child) {
|
|
21
|
+
display: contents;
|
|
22
|
+
}
|
|
23
|
+
|
|
18
24
|
:host([max-full]) {
|
|
19
25
|
max-height: 100%;
|
|
20
26
|
overflow: hidden;
|
|
@@ -8,6 +8,37 @@ describe('<zn-sp>', () => {
|
|
|
8
8
|
expect(el).to.exist;
|
|
9
9
|
});
|
|
10
10
|
|
|
11
|
+
it('should lay out through a form that wraps the whole panel', async () => {
|
|
12
|
+
const parent = await fixture(html`
|
|
13
|
+
<div style="display: flex; flex-direction: column; height: 400px; width: 600px;">
|
|
14
|
+
<zn-sp grow flush>
|
|
15
|
+
<form>
|
|
16
|
+
<div id="panel" style="height: 100%;">Content</div>
|
|
17
|
+
</form>
|
|
18
|
+
</zn-sp>
|
|
19
|
+
</div>
|
|
20
|
+
`);
|
|
21
|
+
|
|
22
|
+
const form = parent.querySelector('form')!;
|
|
23
|
+
expect(getComputedStyle(form).display).to.equal('contents');
|
|
24
|
+
expect(parent.querySelector<HTMLElement>('#panel')!.getBoundingClientRect().height).to.equal(400);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('should leave a form that shares the panel with siblings alone', async () => {
|
|
28
|
+
const parent = await fixture(html`
|
|
29
|
+
<div style="display: flex; flex-direction: column; height: 400px;">
|
|
30
|
+
<zn-sp grow>
|
|
31
|
+
<h2>Heading</h2>
|
|
32
|
+
<form>
|
|
33
|
+
<div>Content</div>
|
|
34
|
+
</form>
|
|
35
|
+
</zn-sp>
|
|
36
|
+
</div>
|
|
37
|
+
`);
|
|
38
|
+
|
|
39
|
+
expect(getComputedStyle(parent.querySelector('form')!).display).to.equal('block');
|
|
40
|
+
});
|
|
41
|
+
|
|
11
42
|
it('should fill its parent container height', async () => {
|
|
12
43
|
const parent = await fixture(html`
|
|
13
44
|
<div style="height: 300px;">
|
|
@@ -13,6 +13,7 @@ import ZnChip from '../chip';
|
|
|
13
13
|
import ZnInlineEdit from '../inline-edit';
|
|
14
14
|
import ZnInput from '../input';
|
|
15
15
|
import ZnOption from '../option';
|
|
16
|
+
import ZnRemarkdEditor from '../remarkd-editor';
|
|
16
17
|
import ZnSelect from '../select';
|
|
17
18
|
import ZnTextarea from '../textarea';
|
|
18
19
|
import type {PropertyValues} from 'lit';
|
|
@@ -64,6 +65,7 @@ export default class ZnTranslations extends ZincElement implements ZincFormContr
|
|
|
64
65
|
'zn-inline-edit': ZnInlineEdit,
|
|
65
66
|
'zn-input': ZnInput,
|
|
66
67
|
'zn-option': ZnOption,
|
|
68
|
+
'zn-remarkd-editor': ZnRemarkdEditor,
|
|
67
69
|
'zn-select': ZnSelect,
|
|
68
70
|
'zn-textarea': ZnTextarea
|
|
69
71
|
};
|
|
@@ -96,11 +98,23 @@ export default class ZnTranslations extends ZincElement implements ZincFormContr
|
|
|
96
98
|
@property({type: Boolean, reflect: true}) flush = false;
|
|
97
99
|
|
|
98
100
|
/** The control each translation is edited through. */
|
|
99
|
-
@property({attribute: "input-type"}) inputType: 'text' | 'number' | 'textarea' = 'text';
|
|
101
|
+
@property({attribute: "input-type"}) inputType: 'text' | 'number' | 'textarea' | 'remarkd' = 'text';
|
|
100
102
|
|
|
101
103
|
/** Rows of the textarea, when `input-type` is `textarea`. */
|
|
102
104
|
@property({attribute: "textarea-rows", type: Number}) textareaRows: number | undefined;
|
|
103
105
|
|
|
106
|
+
/** Allows raw HTML blocks, when `input-type` is `remarkd`. */
|
|
107
|
+
@property({type: Boolean, attribute: 'allow-raw'}) allowRaw = false;
|
|
108
|
+
|
|
109
|
+
/** Where the remarkd editor uploads images, when `input-type` is `remarkd`. */
|
|
110
|
+
@property({attribute: 'attachment-url'}) attachmentUrl = '';
|
|
111
|
+
|
|
112
|
+
/** Where the remarkd editor lists embeddable includes, when `input-type` is `remarkd`. */
|
|
113
|
+
@property({attribute: 'include-url'}) includeUrl = '';
|
|
114
|
+
|
|
115
|
+
/** Where the remarkd editor searches link targets, when `input-type` is `remarkd`. */
|
|
116
|
+
@property({attribute: 'link-url'}) linkUrl = '';
|
|
117
|
+
|
|
104
118
|
/**
|
|
105
119
|
* Edits the translation through a `zn-inline-edit` — the value reads as text until it is clicked — rather than a
|
|
106
120
|
* plain input or textarea.
|
|
@@ -325,7 +339,7 @@ export default class ZnTranslations extends ZincElement implements ZincFormContr
|
|
|
325
339
|
};
|
|
326
340
|
|
|
327
341
|
private handleValueUpdate = (e: CustomEvent) => {
|
|
328
|
-
const target = e.target as (ZnInput | ZnInlineEdit | ZnTextarea);
|
|
342
|
+
const target = e.target as (ZnInput | ZnInlineEdit | ZnRemarkdEditor | ZnTextarea);
|
|
329
343
|
if (this._activeLanguage) {
|
|
330
344
|
const newValue: string = target.value as string;
|
|
331
345
|
if (newValue !== this.values[this._activeLanguage]) {
|
|
@@ -387,6 +401,23 @@ export default class ZnTranslations extends ZincElement implements ZincFormContr
|
|
|
387
401
|
></zn-inline-edit>`;
|
|
388
402
|
}
|
|
389
403
|
|
|
404
|
+
if (this.inputType === 'remarkd') {
|
|
405
|
+
return html`
|
|
406
|
+
<zn-remarkd-editor
|
|
407
|
+
.value=${live(value)}
|
|
408
|
+
name="${this.name}"
|
|
409
|
+
placeholder="${placeholder}"
|
|
410
|
+
dir="${dir}"
|
|
411
|
+
?disabled="${this.disabled}"
|
|
412
|
+
?allow-raw="${this.allowRaw}"
|
|
413
|
+
attachment-url="${this.attachmentUrl}"
|
|
414
|
+
include-url="${this.includeUrl}"
|
|
415
|
+
link-url="${this.linkUrl}"
|
|
416
|
+
@zn-change="${this.handleValueUpdate}"
|
|
417
|
+
@zn-input="${this.handleValueUpdate}"
|
|
418
|
+
></zn-remarkd-editor>`;
|
|
419
|
+
}
|
|
420
|
+
|
|
390
421
|
if (this.inputType === 'textarea') {
|
|
391
422
|
return html`
|
|
392
423
|
<zn-textarea
|
|
@@ -219,6 +219,47 @@ describe('<zn-translations>', () => {
|
|
|
219
219
|
expect(el.shadowRoot!.querySelector('zn-input')).to.be.null;
|
|
220
220
|
});
|
|
221
221
|
|
|
222
|
+
it('renders a remarkd editor for input-type="remarkd"', async () => {
|
|
223
|
+
const el = await fixture<ZnTranslations>(html`
|
|
224
|
+
<zn-translations input-type="remarkd" values='{"en":"= Hello"}'></zn-translations>`);
|
|
225
|
+
await el.updateComplete;
|
|
226
|
+
|
|
227
|
+
const editor = el.shadowRoot!.querySelector<HTMLElement & { value: string }>('zn-remarkd-editor');
|
|
228
|
+
expect(editor).to.exist;
|
|
229
|
+
expect(editor!.value).to.equal('= Hello');
|
|
230
|
+
expect(el.shadowRoot!.querySelector('zn-input')).to.be.null;
|
|
231
|
+
expect(el.shadowRoot!.querySelector('zn-textarea')).to.be.null;
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it('passes its editor endpoints through to the remarkd editor', async () => {
|
|
235
|
+
const el = await fixture<ZnTranslations>(html`
|
|
236
|
+
<zn-translations input-type="remarkd"
|
|
237
|
+
allow-raw
|
|
238
|
+
attachment-url="/a"
|
|
239
|
+
include-url="/i"
|
|
240
|
+
link-url="/l"></zn-translations>`);
|
|
241
|
+
await el.updateComplete;
|
|
242
|
+
|
|
243
|
+
const editor = el.shadowRoot!.querySelector('zn-remarkd-editor')!;
|
|
244
|
+
expect(editor.getAttribute('attachment-url')).to.equal('/a');
|
|
245
|
+
expect(editor.getAttribute('include-url')).to.equal('/i');
|
|
246
|
+
expect(editor.getAttribute('link-url')).to.equal('/l');
|
|
247
|
+
expect(editor.hasAttribute('allow-raw')).to.be.true;
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it('writes a remarkd edit back to the active language', async () => {
|
|
251
|
+
const el = await fixture<ZnTranslations>(html`
|
|
252
|
+
<zn-translations input-type="remarkd" values='{"en":"one"}'></zn-translations>`);
|
|
253
|
+
await el.updateComplete;
|
|
254
|
+
|
|
255
|
+
const editor = el.shadowRoot!.querySelector<HTMLElement & { value: string }>('zn-remarkd-editor')!;
|
|
256
|
+
editor.value = 'two';
|
|
257
|
+
editor.dispatchEvent(new CustomEvent('zn-change', {bubbles: true, composed: true}));
|
|
258
|
+
await el.updateComplete;
|
|
259
|
+
|
|
260
|
+
expect(el.values.en).to.equal('two');
|
|
261
|
+
});
|
|
262
|
+
|
|
222
263
|
it('renders an inline edit when asked for one', async () => {
|
|
223
264
|
const el = await fixture<ZnTranslations>(html`
|
|
224
265
|
<zn-translations inline-edit values='{"en":"Hello"}'></zn-translations>`);
|