@kubex/zinc 1.1.63 → 1.1.64

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.
@@ -0,0 +1,241 @@
1
+ @use "../../wc";
2
+ // Remarkd's official output styles, scoped under .remarkd-rendered.
3
+ @use "../../../node_modules/remarkd-js/docs/assets/remarkd-output";
4
+
5
+ :host {
6
+ display: block;
7
+ }
8
+
9
+ .remarkd-editor {
10
+ position: relative;
11
+ border: 1px solid rgb(var(--zn-border-color));
12
+ border-radius: var(--zn-border-radius);
13
+ background-color: rgba(var(--zn-panel), var(--zn-panel-opacity));
14
+ }
15
+
16
+ .remarkd-editor__toolbar {
17
+ display: flex;
18
+ gap: 2px;
19
+ flex-wrap: wrap;
20
+ padding: var(--zn-spacing-x-small);
21
+ border-bottom: 1px solid rgb(var(--zn-border-color));
22
+ }
23
+
24
+ .remarkd-editor__body {
25
+ padding: var(--zn-spacing-large) var(--zn-spacing-large) var(--zn-spacing-large) 88px;
26
+ min-height: 10rem;
27
+ }
28
+
29
+ .remarkd-editor--disabled {
30
+ opacity: 0.6;
31
+ pointer-events: none;
32
+ }
33
+
34
+ .remarkd-editor__block {
35
+ position: relative;
36
+ border-radius: var(--zn-border-radius);
37
+ }
38
+
39
+ .remarkd-editor__actions {
40
+ position: absolute;
41
+ left: -80px;
42
+ top: 0;
43
+ display: flex;
44
+ align-items: center;
45
+ gap: 2px;
46
+ opacity: 0;
47
+ transition: opacity 0.15s ease;
48
+ }
49
+
50
+ .remarkd-editor__block:hover .remarkd-editor__actions,
51
+ .remarkd-editor__actions:focus-within {
52
+ opacity: 1;
53
+ }
54
+
55
+ .remarkd-editor__drag-handle {
56
+ display: inline-flex;
57
+ align-items: center;
58
+ padding: 2px;
59
+ color: var(--zn-color-neutral-400, #9aa4b2);
60
+ cursor: grab;
61
+ touch-action: none;
62
+ user-select: none;
63
+
64
+ &:active {
65
+ cursor: grabbing;
66
+ }
67
+ }
68
+
69
+ .remarkd-editor__ghost {
70
+ position: fixed;
71
+ top: 0;
72
+ left: 0;
73
+ z-index: 20;
74
+ pointer-events: none;
75
+ opacity: 0.9;
76
+ background: var(--zn-color-neutral-0, white);
77
+ border-radius: var(--zn-border-radius);
78
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
79
+ max-width: 50%;
80
+ max-height: 160px;
81
+ overflow: hidden;
82
+ }
83
+
84
+ .remarkd-editor__block--dragging {
85
+ opacity: 0.4;
86
+ }
87
+
88
+ .remarkd-editor__block--drop-before::before,
89
+ .remarkd-editor__block--drop-after::after {
90
+ content: '';
91
+ position: absolute;
92
+ left: 0;
93
+ right: 0;
94
+ height: 6px;
95
+ border-radius: 3px;
96
+ background: rgba(var(--zn-primary), 0.6);
97
+ pointer-events: none;
98
+ }
99
+
100
+ .remarkd-editor__block--drop-before::before {
101
+ top: -4px;
102
+ }
103
+
104
+ .remarkd-editor__block--drop-after::after {
105
+ bottom: -4px;
106
+ }
107
+
108
+ .remarkd-editor__rendered {
109
+ padding: var(--zn-spacing-x-small);
110
+ border-radius: var(--zn-border-radius);
111
+ cursor: text;
112
+
113
+ &:hover {
114
+ background: var(--zn-color-neutral-100);
115
+ }
116
+ }
117
+
118
+ // The shell keeps the block's rendered chrome while its source is edited —
119
+ // remarkd hint classes (hint-note, hint-tip, …) apply their own styling here.
120
+ .remarkd-editor__edit-shell {
121
+ border: 1px solid rgb(var(--zn-border-color));
122
+ border-radius: var(--zn-border-radius);
123
+ background: var(--zn-color-neutral-0, white);
124
+ padding: var(--zn-spacing-x-small);
125
+
126
+ // Hint shells keep their own left accent — only tint the other edges.
127
+ &:focus-within {
128
+ border-top-color: rgb(var(--zn-primary));
129
+ border-right-color: rgb(var(--zn-primary));
130
+ border-bottom-color: rgb(var(--zn-primary));
131
+ }
132
+
133
+ &:not([class*="hint-"]):focus-within {
134
+ border-left-color: rgb(var(--zn-primary));
135
+ }
136
+ }
137
+
138
+ .remarkd-editor__input {
139
+ display: block;
140
+ width: 100%;
141
+ resize: none;
142
+ overflow: hidden;
143
+ border: 0;
144
+ background: transparent;
145
+ color: inherit;
146
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
147
+ font-size: 0.875rem;
148
+ line-height: 1.5;
149
+ padding: 0;
150
+ outline: none;
151
+ }
152
+
153
+ // Hints edit in the rendered font so they read as the same block.
154
+ .remarkd-editor__edit-shell[class*="hint-"] .remarkd-editor__input {
155
+ font: inherit;
156
+ }
157
+
158
+ .remarkd-editor__edit-shell--h1 .remarkd-editor__input,
159
+ .remarkd-editor__edit-shell--h2 .remarkd-editor__input,
160
+ .remarkd-editor__edit-shell--h3 .remarkd-editor__input {
161
+ font-family: inherit;
162
+ font-weight: 700;
163
+ line-height: 1.2;
164
+ }
165
+
166
+ .remarkd-editor__edit-shell--h1 .remarkd-editor__input {
167
+ font-size: 26px;
168
+ }
169
+
170
+ .remarkd-editor__edit-shell--h2 .remarkd-editor__input {
171
+ font-size: 21px;
172
+ }
173
+
174
+ .remarkd-editor__edit-shell--h3 .remarkd-editor__input {
175
+ font-size: 17px;
176
+ }
177
+
178
+ .remarkd-editor__edit-shell--code {
179
+ background: var(--rmd-code-bg, #0f1626);
180
+
181
+ .remarkd-editor__input {
182
+ color: var(--rmd-code-text, #f5f8ff);
183
+ }
184
+ }
185
+
186
+ .remarkd-editor__add {
187
+ padding: var(--zn-spacing-x-small);
188
+ min-height: 1.8em;
189
+ color: var(--zn-color-neutral-400, #9aa4b2);
190
+ cursor: text;
191
+ }
192
+
193
+ .remarkd-editor__edit-wrap {
194
+ position: relative;
195
+ }
196
+
197
+ .remarkd-editor__slash-menu {
198
+ position: absolute;
199
+ top: 100%;
200
+ left: 0;
201
+ z-index: 10;
202
+ min-width: 12rem;
203
+ margin-top: 4px;
204
+ padding: 4px;
205
+ border: 1px solid rgb(var(--zn-border-color));
206
+ border-radius: var(--zn-border-radius);
207
+ background: var(--zn-color-neutral-0, white);
208
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
209
+ display: flex;
210
+ flex-direction: column;
211
+ }
212
+
213
+ .remarkd-editor__slash-item {
214
+ display: flex;
215
+ align-items: center;
216
+ gap: var(--zn-spacing-x-small);
217
+ padding: 6px 8px;
218
+ border: 0;
219
+ border-radius: var(--zn-border-radius);
220
+ background: none;
221
+ color: var(--zn-color-text, #222);
222
+ font: inherit;
223
+ font-size: 0.875rem;
224
+ text-align: left;
225
+ cursor: pointer;
226
+
227
+ &:hover,
228
+ &--active {
229
+ background: var(--zn-color-neutral-100);
230
+ }
231
+ }
232
+
233
+ .remarkd-editor__validation {
234
+ position: absolute;
235
+ width: 1px;
236
+ height: 1px;
237
+ opacity: 0;
238
+ pointer-events: none;
239
+ border: 0;
240
+ padding: 0;
241
+ }
@@ -0,0 +1,147 @@
1
+ import '../../../dist/zn.min.js';
2
+ import {expect, fixture, html} from '@open-wc/testing';
3
+ import type ZnRemarkdEditor from './remarkd-editor.component';
4
+
5
+ describe('<zn-remarkd-editor>', () => {
6
+ it('should render a component', async () => {
7
+ const el = await fixture(html`
8
+ <zn-remarkd-editor></zn-remarkd-editor>`);
9
+ expect(el).to.exist;
10
+ });
11
+
12
+ it('should split the value into rendered remarkd blocks', async () => {
13
+ const el = await fixture<ZnRemarkdEditor>(html`
14
+ <zn-remarkd-editor value="# Title
15
+
16
+ NOTE: a note"></zn-remarkd-editor>`);
17
+ const blocks = el.shadowRoot!.querySelectorAll('.remarkd-editor__block');
18
+ expect(blocks.length).to.equal(2);
19
+ expect(blocks[1].innerHTML).to.contain('hint-note');
20
+ });
21
+
22
+ it('should keep fenced content as a single block', async () => {
23
+ const fenced = '```\n\ncode\n\n```';
24
+ const el = await fixture<ZnRemarkdEditor>(html`
25
+ <zn-remarkd-editor value=${fenced}></zn-remarkd-editor>`);
26
+ expect(el.shadowRoot!.querySelectorAll('.remarkd-editor__block').length).to.equal(1);
27
+ });
28
+
29
+ it('should swap a block to source editing on click and commit on blur', async () => {
30
+ const el = await fixture<ZnRemarkdEditor>(html`
31
+ <zn-remarkd-editor value="# Title"></zn-remarkd-editor>`);
32
+ el.shadowRoot!.querySelector<HTMLElement>('.remarkd-editor__rendered')!.click();
33
+ await el.updateComplete;
34
+
35
+ const input = el.shadowRoot!.querySelector<HTMLTextAreaElement>('.remarkd-editor__input')!;
36
+ expect(input).to.exist;
37
+ expect(input.value).to.equal('# Title');
38
+
39
+ input.value = '# Changed';
40
+ input.dispatchEvent(new Event('input', {bubbles: true}));
41
+ input.dispatchEvent(new Event('blur'));
42
+ await el.updateComplete;
43
+
44
+ expect(el.value).to.equal('# Changed');
45
+ expect(el.shadowRoot!.querySelector('.remarkd-editor__rendered')!.innerHTML).to.contain('Changed');
46
+ });
47
+
48
+ it('should remove a block committed as empty', async () => {
49
+ const el = await fixture<ZnRemarkdEditor>(html`
50
+ <zn-remarkd-editor value="# Title
51
+
52
+ Second"></zn-remarkd-editor>`);
53
+ el.shadowRoot!.querySelectorAll<HTMLElement>('.remarkd-editor__rendered')[1].click();
54
+ await el.updateComplete;
55
+
56
+ const input = el.shadowRoot!.querySelector<HTMLTextAreaElement>('.remarkd-editor__input')!;
57
+ input.value = '';
58
+ input.dispatchEvent(new Event('input', {bubbles: true}));
59
+ input.dispatchEvent(new Event('blur'));
60
+ await el.updateComplete;
61
+
62
+ expect(el.value).to.equal('# Title');
63
+ expect(el.shadowRoot!.querySelectorAll('.remarkd-editor__block').length).to.equal(1);
64
+ });
65
+
66
+ it('should render an always-visible toolbar', async () => {
67
+ const el = await fixture<ZnRemarkdEditor>(html`
68
+ <zn-remarkd-editor></zn-remarkd-editor>`);
69
+ expect(el.shadowRoot!.querySelector('.remarkd-editor__toolbar')).to.exist;
70
+ });
71
+
72
+ it('should open the slash menu when "/" starts an empty block', async () => {
73
+ const el = await fixture<ZnRemarkdEditor>(html`
74
+ <zn-remarkd-editor value="Hello"></zn-remarkd-editor>`);
75
+ el.shadowRoot!.querySelector<HTMLElement>('.remarkd-editor__rendered')!.click();
76
+ await el.updateComplete;
77
+
78
+ const input = el.shadowRoot!.querySelector<HTMLTextAreaElement>('.remarkd-editor__input')!;
79
+ input.value = '/';
80
+ input.dispatchEvent(new Event('input', {bubbles: true}));
81
+ await el.updateComplete;
82
+
83
+ expect(el.shadowRoot!.querySelector('.remarkd-editor__slash-menu')).to.exist;
84
+ });
85
+
86
+ it('should start a new block on shift+enter', async () => {
87
+ const el = await fixture<ZnRemarkdEditor>(html`
88
+ <zn-remarkd-editor value="First"></zn-remarkd-editor>`);
89
+ el.shadowRoot!.querySelector<HTMLElement>('.remarkd-editor__rendered')!.click();
90
+ await el.updateComplete;
91
+
92
+ const input = el.shadowRoot!.querySelector<HTMLTextAreaElement>('.remarkd-editor__input')!;
93
+ input.dispatchEvent(new KeyboardEvent('keydown', {key: 'Enter', shiftKey: true, bubbles: true}));
94
+ await el.updateComplete;
95
+
96
+ // First block committed and rendered; a fresh draft textarea sits below it.
97
+ expect(el.shadowRoot!.querySelectorAll('.remarkd-editor__block').length).to.equal(1);
98
+ const draft = el.shadowRoot!.querySelector<HTMLTextAreaElement>('.remarkd-editor__input')!;
99
+ expect(draft).to.exist;
100
+ expect(draft.value).to.equal('');
101
+ });
102
+
103
+ it('should open the image dialog from the toolbar', async () => {
104
+ const el = await fixture<ZnRemarkdEditor>(html`
105
+ <zn-remarkd-editor></zn-remarkd-editor>`);
106
+ const buttons = el.shadowRoot!.querySelectorAll('.remarkd-editor__toolbar zn-button');
107
+ const imageButton = buttons[buttons.length - 1];
108
+ imageButton.dispatchEvent(new MouseEvent('click', {bubbles: true, composed: true, cancelable: true}));
109
+ await el.updateComplete;
110
+
111
+ const dialog = el.shadowRoot!.querySelector('.remarkd-editor__image-dialog')!;
112
+ expect(dialog).to.exist;
113
+ // No attachment-url configured — the dialog falls back to a URL input.
114
+ expect(el.shadowRoot!.querySelector('.remarkd-editor__image-url')).to.exist;
115
+ });
116
+
117
+ it('should toggle a checkbox in the source instead of opening the editor', async () => {
118
+ const el = await fixture<ZnRemarkdEditor>(html`
119
+ <zn-remarkd-editor value="- [ ] First
120
+ - [x] Second"></zn-remarkd-editor>`);
121
+ const boxes = el.shadowRoot!.querySelectorAll<HTMLInputElement>('input[type="checkbox"]');
122
+ boxes[0].dispatchEvent(new MouseEvent('click', {bubbles: true, composed: true, cancelable: true}));
123
+ await el.updateComplete;
124
+
125
+ expect(el.value).to.equal('- [x] First\n- [x] Second');
126
+ expect(el.shadowRoot!.querySelector('.remarkd-editor__input')).to.not.exist;
127
+ });
128
+
129
+ it('should emit zn-change when a block edit changes the value', async () => {
130
+ const el = await fixture<ZnRemarkdEditor>(html`
131
+ <zn-remarkd-editor value="Hello"></zn-remarkd-editor>`);
132
+ let fired = false;
133
+ el.addEventListener('zn-change', () => {
134
+ fired = true;
135
+ });
136
+
137
+ el.shadowRoot!.querySelector<HTMLElement>('.remarkd-editor__rendered')!.click();
138
+ await el.updateComplete;
139
+ const input = el.shadowRoot!.querySelector<HTMLTextAreaElement>('.remarkd-editor__input')!;
140
+ input.value = 'Hello world';
141
+ input.dispatchEvent(new Event('input', {bubbles: true}));
142
+ input.dispatchEvent(new Event('blur'));
143
+ await el.updateComplete;
144
+
145
+ expect(fired).to.be.true;
146
+ });
147
+ });
@@ -10,6 +10,11 @@ import ZincElement from '../../internal/zinc-element';
10
10
 
11
11
  import styles from './tabs.scss';
12
12
 
13
+ // Every element that manages its own tabs (ZnTabs and its subclasses). Tab
14
+ // registration must stop at these so an outer container never claims a nested
15
+ // container's tabs.
16
+ const tabContainerSelector = 'zn-tabs, zn-page, zn-page-nav';
17
+
13
18
  /**
14
19
  * @summary Short summary of the component's intended use.
15
20
  * @documentation https://zinc.style/components/tabs
@@ -228,7 +233,7 @@ export default class ZnTabs extends ZincElement {
228
233
  }
229
234
  }
230
235
 
231
- for (const uriTab of deepQuerySelectorAll("[tab-uri]", this, '')) {
236
+ for (const uriTab of deepQuerySelectorAll("[tab-uri]", this, tabContainerSelector)) {
232
237
  const uri: string = uriTab.getAttribute("tab-uri")!;
233
238
  const eleTabId = this._uriToId(uri);
234
239
  if (eleTabId === tabId) {
@@ -459,11 +464,11 @@ export default class ZnTabs extends ZincElement {
459
464
  }
460
465
 
461
466
  _registerTabs = () => {
462
- deepQuerySelectorAll('[tab]', this, 'zn-tabs').forEach(ele => {
467
+ deepQuerySelectorAll('[tab]', this, tabContainerSelector).forEach(ele => {
463
468
  this._addTab(ele as HTMLElement);
464
469
  });
465
470
 
466
- deepQuerySelectorAll('[tab-uri]', this, 'zn-tabs').forEach(ele => {
471
+ deepQuerySelectorAll('[tab-uri]', this, tabContainerSelector).forEach(ele => {
467
472
  if (ele.getAttribute('tab-uri') === "") {
468
473
  ele.setAttribute('tab', "");
469
474
  ele.removeAttribute('tab-uri');
@@ -471,7 +476,7 @@ export default class ZnTabs extends ZincElement {
471
476
  this._addTab(ele as HTMLElement);
472
477
  });
473
478
 
474
- deepQuerySelectorAll('[ref-tab-uri]', this, 'zn-tabs').forEach(ele => {
479
+ deepQuerySelectorAll('[ref-tab-uri]', this, tabContainerSelector).forEach(ele => {
475
480
  if (!ele.hasAttribute('ref-tab')) {
476
481
  ele.setAttribute('ref-tab', this._uriToId(ele.getAttribute('ref-tab-uri')!));
477
482
  ele.removeAttribute('ref-tab-uri');
@@ -1,5 +1,6 @@
1
1
  import '../../../dist/zn.min.js';
2
- import { expect, fixture, html } from '@open-wc/testing';
2
+ import { aTimeout, expect, fixture, html } from '@open-wc/testing';
3
+ import type ZnTabs from './tabs.component';
3
4
 
4
5
  describe('<zn-tabs>', () => {
5
6
  it('should render a component', async () => {
@@ -7,4 +8,44 @@ describe('<zn-tabs>', () => {
7
8
 
8
9
  expect(el).to.exist;
9
10
  });
11
+
12
+ it('does not register tabs belonging to a nested zn-page', async () => {
13
+ const el = await fixture<ZnTabs>(html`
14
+ <zn-tabs>
15
+ <div class="outer-home">Outer Home</div>
16
+ <div id="outer-second">
17
+ <zn-page caption="Nested Page">
18
+ <zn-tab caption="Overview">Overview Content</zn-tab>
19
+ <zn-tab caption="Details">Details Content</zn-tab>
20
+ </zn-page>
21
+ </div>
22
+ </zn-tabs>
23
+ `);
24
+ await aTimeout(60);
25
+
26
+ el.setActiveTab('outer-second', false, false);
27
+ await aTimeout(20);
28
+
29
+ // A menu selection bubbling out of the page (e.g. an ellipses action menu)
30
+ // makes the outer tabs re-register; it must not claim the page's nav items.
31
+ const page = el.querySelector('zn-page')!;
32
+ page.dispatchEvent(new CustomEvent('zn-menu-select', { bubbles: true, composed: true }));
33
+ await aTimeout(250);
34
+
35
+ const navbar = page.shadowRoot!.querySelector('zn-navbar')!;
36
+ const navItems = Array.from(navbar.shadowRoot!.querySelectorAll<HTMLElement>('li:not(.more)'));
37
+
38
+ navItems[1].click();
39
+ await aTimeout(40);
40
+ navItems[0].click();
41
+ await aTimeout(40);
42
+
43
+ const outerHome = el.querySelector('.outer-home')!;
44
+ const outerSecond = el.querySelector('#outer-second')!;
45
+ expect(outerSecond.hasAttribute('selected')).to.equal(true);
46
+ expect(outerHome.hasAttribute('selected')).to.equal(false);
47
+
48
+ const selectedPagePanel = page.shadowRoot!.querySelector<HTMLElement>('#content > div[selected]')!;
49
+ expect(selectedPagePanel.id).to.equal('');
50
+ });
10
51
  });
package/src/zinc.ts CHANGED
@@ -101,6 +101,7 @@ export { default as TranslationGroup } from './components/translation-group';
101
101
  export { default as OptGroup } from './components/opt-group';
102
102
  export { default as PriorityList } from './components/priority-list';
103
103
  export { default as MarkdownEditor } from './components/markdown-editor';
104
+ export { default as RemarkdEditor } from './components/remarkd-editor';
104
105
  export { default as FlowBuilder } from './components/flow-builder';
105
106
  export { default as FlowSteps } from './components/flow-builder/modules/flow-steps';
106
107
  export { default as FlowStepGroup } from './components/flow-builder/modules/flow-step-group';