@kubex/zinc 1.1.94 → 1.1.96
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 +653 -89
- package/dist/vscode.html-custom-data.json +73 -18
- package/dist/web-types.json +145 -35
- package/dist/zn.d.ts +204 -39
- package/dist/zn.min.css +1 -1
- package/dist/zn.min.js +374 -308
- package/docs/pages/components/flow-builder.md +17 -1
- package/docs/pages/components/page-builder.md +31 -0
- package/docs/pages/components/slash-menu.md +132 -6
- package/docs/pages/components/textarea.md +16 -0
- package/package.json +1 -1
- package/scss/_root.scss +7 -1
- package/src/components/button/button.scss +5 -2
- package/src/components/flow-builder/flow-builder.component.ts +16 -1
- package/src/components/flow-builder/flow-builder.test.ts +206 -1
- package/src/components/flow-builder/flow-geometry.test.ts +102 -0
- package/src/components/flow-builder/flow.types.ts +82 -15
- package/src/components/flow-builder/modules/flow-canvas/flow-canvas.component.ts +163 -108
- package/src/components/flow-builder/modules/flow-step/flow-step.component.ts +2 -0
- package/src/components/inline-edit/inline-edit.component.ts +6 -1
- package/src/components/input/input.component.ts +12 -2
- package/src/components/page/page.scss +7 -2
- package/src/components/page-builder/modules/page-section-card/page-section-card.component.ts +16 -9
- package/src/components/page-builder/modules/page-section-card/page-section-card.scss +13 -0
- package/src/components/page-builder/modules/page-section-card/page-section-card.test.ts +9 -0
- package/src/components/page-builder/page-builder.component.ts +62 -4
- package/src/components/page-builder/page-builder.test.ts +94 -0
- package/src/components/remarkd-editor/remarkd-editor.component.ts +227 -12
- package/src/components/remarkd-editor/remarkd-editor.scss +81 -0
- package/src/components/remarkd-editor/remarkd-editor.test.ts +258 -0
- package/src/components/settings-container/settings-container.scss +2 -1
- package/src/components/slash-item/slash-item.component.ts +1 -1
- package/src/components/slash-menu/slash-menu-items.ts +48 -0
- package/src/components/slash-menu/slash-menu.component.ts +134 -27
- package/src/components/slash-menu/slash-menu.scss +90 -12
- package/src/components/slash-menu/slash-menu.test.ts +107 -0
- package/src/components/textarea/textarea.component.ts +12 -2
- package/src/components/textarea/textarea.test.ts +2 -2
- package/src/components/translations/translations.component.ts +5 -1
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
:host {
|
|
4
4
|
--slash-menu-width: 320px;
|
|
5
5
|
--slash-menu-max-height: 260px;
|
|
6
|
+
--slash-menu-border-radius: 12px;
|
|
7
|
+
--slash-menu-item-border-radius: 8px;
|
|
6
8
|
|
|
7
9
|
display: contents;
|
|
8
10
|
}
|
|
@@ -20,24 +22,32 @@
|
|
|
20
22
|
width: var(--slash-menu-width);
|
|
21
23
|
max-width: var(--auto-size-available-width, none);
|
|
22
24
|
max-height: min(var(--slash-menu-max-height), var(--auto-size-available-height, 100vh));
|
|
23
|
-
overflow-y: auto;
|
|
24
|
-
overscroll-behavior: none;
|
|
25
25
|
z-index: var(--zn-z-index-dropdown);
|
|
26
|
-
|
|
26
|
+
overflow: hidden;
|
|
27
27
|
background: var(--zn-panel-background-color);
|
|
28
28
|
border: solid var(--zn-panel-border-width, 1px) var(--zn-panel-border-color);
|
|
29
|
-
border-radius: var(--
|
|
29
|
+
border-radius: var(--slash-menu-border-radius);
|
|
30
30
|
box-shadow: var(--zn-shadow-medium);
|
|
31
31
|
font-family: var(--zn-font-sans);
|
|
32
32
|
font-size: var(--zn-font-size-medium);
|
|
33
33
|
color: rgb(var(--zn-text));
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
.slash-
|
|
36
|
+
.slash-menu__list {
|
|
37
|
+
display: flex;
|
|
38
|
+
flex: 1 1 auto;
|
|
39
|
+
flex-direction: column;
|
|
40
|
+
gap: var(--zn-spacing-3x-small, 2px);
|
|
41
|
+
min-height: 0;
|
|
42
|
+
overflow-y: auto;
|
|
43
|
+
overscroll-behavior: none;
|
|
44
|
+
padding: var(--zn-spacing-2x-small, 4px) var(--zn-spacing-x-small, 8px);
|
|
45
|
+
}
|
|
46
|
+
|
|
37
47
|
.slash-menu__group-heading,
|
|
38
48
|
.slash-menu__footer,
|
|
39
49
|
.slash-menu__empty {
|
|
40
|
-
padding: var(--zn-spacing-2x-small, 4px) var(--zn-spacing-small,
|
|
50
|
+
padding: var(--zn-spacing-2x-small, 4px) var(--zn-spacing-x-small, 8px);
|
|
41
51
|
font-size: var(--zn-font-size-x-small);
|
|
42
52
|
color: var(--zn-color-neutral-500);
|
|
43
53
|
user-select: none;
|
|
@@ -45,9 +55,11 @@
|
|
|
45
55
|
}
|
|
46
56
|
|
|
47
57
|
.slash-menu__group-heading {
|
|
58
|
+
padding-top: var(--zn-spacing-x-small, 8px);
|
|
48
59
|
font-weight: var(--zn-font-weight-semibold);
|
|
49
60
|
text-transform: uppercase;
|
|
50
|
-
letter-spacing: 0.
|
|
61
|
+
letter-spacing: 0.06em;
|
|
62
|
+
color: var(--zn-color-neutral-400);
|
|
51
63
|
}
|
|
52
64
|
|
|
53
65
|
.slash-menu__footer {
|
|
@@ -55,24 +67,72 @@
|
|
|
55
67
|
margin-top: var(--zn-spacing-2x-small, 4px);
|
|
56
68
|
}
|
|
57
69
|
|
|
70
|
+
.slash-menu__divider {
|
|
71
|
+
height: var(--zn-panel-border-width, 1px);
|
|
72
|
+
margin: var(--zn-spacing-2x-small, 4px) 0;
|
|
73
|
+
background: var(--zn-panel-border-color);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.slash-menu__hints {
|
|
77
|
+
display: flex;
|
|
78
|
+
flex: 0 0 auto;
|
|
79
|
+
align-items: center;
|
|
80
|
+
gap: var(--zn-spacing-small, 12px);
|
|
81
|
+
padding: var(--zn-spacing-x-small, 8px) var(--zn-spacing-small, 12px);
|
|
82
|
+
border-top: solid var(--zn-panel-border-width, 1px) var(--zn-panel-border-color);
|
|
83
|
+
font-size: var(--zn-font-size-x-small);
|
|
84
|
+
color: var(--zn-color-neutral-500);
|
|
85
|
+
user-select: none;
|
|
86
|
+
-webkit-user-select: none;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.slash-menu__hint {
|
|
90
|
+
display: flex;
|
|
91
|
+
align-items: center;
|
|
92
|
+
gap: var(--zn-spacing-2x-small, 4px);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.slash-menu__key {
|
|
96
|
+
display: inline-flex;
|
|
97
|
+
align-items: center;
|
|
98
|
+
justify-content: center;
|
|
99
|
+
min-width: 22px;
|
|
100
|
+
height: 22px;
|
|
101
|
+
padding: 0 var(--zn-spacing-2x-small, 4px);
|
|
102
|
+
border: solid var(--zn-panel-border-width, 1px) var(--zn-panel-border-color);
|
|
103
|
+
border-radius: var(--zn-border-radius, 6px);
|
|
104
|
+
background: var(--zn-panel-background-color);
|
|
105
|
+
font-family: var(--zn-font-sans);
|
|
106
|
+
font-size: var(--zn-font-size-2x-small);
|
|
107
|
+
line-height: 1;
|
|
108
|
+
color: var(--zn-color-neutral-600);
|
|
109
|
+
}
|
|
110
|
+
|
|
58
111
|
.slash-menu__item {
|
|
59
112
|
display: flex;
|
|
60
113
|
align-items: center;
|
|
61
|
-
gap: var(--zn-spacing-
|
|
114
|
+
gap: var(--zn-spacing-small, 12px);
|
|
62
115
|
width: 100%;
|
|
63
|
-
padding: var(--zn-spacing-
|
|
116
|
+
padding: var(--zn-spacing-2x-small, 4px) var(--zn-spacing-x-small, 8px);
|
|
64
117
|
border: none;
|
|
118
|
+
border-radius: var(--slash-menu-item-border-radius);
|
|
65
119
|
background: transparent;
|
|
66
120
|
color: inherit;
|
|
67
121
|
font: inherit;
|
|
68
122
|
text-align: left;
|
|
69
123
|
cursor: pointer;
|
|
70
124
|
|
|
71
|
-
&--active,
|
|
72
125
|
&:hover:not(&--disabled) {
|
|
73
126
|
background: var(--zn-input-background-color-hover);
|
|
74
127
|
}
|
|
75
128
|
|
|
129
|
+
// The active item is where Enter lands, so it is tinted rather than merely shaded like a hover.
|
|
130
|
+
// The tints throughout are translucent, so one set of them suits a light and a dark panel alike.
|
|
131
|
+
&--active,
|
|
132
|
+
&--active:hover:not(&--disabled) {
|
|
133
|
+
background: rgba(var(--zn-color-primary), 0.08);
|
|
134
|
+
}
|
|
135
|
+
|
|
76
136
|
&--disabled {
|
|
77
137
|
opacity: 0.5;
|
|
78
138
|
cursor: not-allowed;
|
|
@@ -83,8 +143,25 @@
|
|
|
83
143
|
display: flex;
|
|
84
144
|
align-items: center;
|
|
85
145
|
justify-content: center;
|
|
86
|
-
flex: 0 0
|
|
87
|
-
|
|
146
|
+
flex: 0 0 auto;
|
|
147
|
+
width: 32px;
|
|
148
|
+
height: 32px;
|
|
149
|
+
border: solid var(--zn-panel-border-width, 1px) rgba(var(--zn-color-primary), 0.18);
|
|
150
|
+
border-radius: var(--slash-menu-item-border-radius);
|
|
151
|
+
background: rgba(var(--zn-color-primary), 0.06);
|
|
152
|
+
color: var(--zn-color-neutral-700);
|
|
153
|
+
|
|
154
|
+
// Icons are all-or-nothing across a list, so an item without one still holds the column
|
|
155
|
+
&--empty {
|
|
156
|
+
border-color: transparent;
|
|
157
|
+
background: none;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.slash-menu__item--active & {
|
|
161
|
+
border-color: rgba(var(--zn-color-primary), 0.3);
|
|
162
|
+
background: rgba(var(--zn-color-primary), 0.14);
|
|
163
|
+
color: rgb(var(--zn-color-primary));
|
|
164
|
+
}
|
|
88
165
|
}
|
|
89
166
|
|
|
90
167
|
.slash-menu__text {
|
|
@@ -95,6 +172,7 @@
|
|
|
95
172
|
}
|
|
96
173
|
|
|
97
174
|
.slash-menu__label {
|
|
175
|
+
font-weight: var(--zn-font-weight-semibold);
|
|
98
176
|
overflow: hidden;
|
|
99
177
|
text-overflow: ellipsis;
|
|
100
178
|
white-space: nowrap;
|
|
@@ -81,6 +81,113 @@ describe('<zn-slash-menu>', () => {
|
|
|
81
81
|
expect(reachedDocument, 'mousedown does not bubble to document').to.be.false;
|
|
82
82
|
});
|
|
83
83
|
|
|
84
|
+
it('shows the keyboard hints unless they are hidden', async () => {
|
|
85
|
+
const el = await fixture<ZnSlashMenu>(html`
|
|
86
|
+
<zn-slash-menu></zn-slash-menu>`);
|
|
87
|
+
el.items = [{label: 'One'}];
|
|
88
|
+
el.show();
|
|
89
|
+
await el.updateComplete;
|
|
90
|
+
|
|
91
|
+
const hints = el.shadowRoot!.querySelector('[part="hints"]')!;
|
|
92
|
+
expect(hints).to.exist;
|
|
93
|
+
expect(hints.textContent).to.contain('navigate');
|
|
94
|
+
expect(hints.textContent).to.contain('select');
|
|
95
|
+
expect(hints.textContent).to.contain('dismiss');
|
|
96
|
+
expect(hints.querySelectorAll('kbd').length).to.equal(4);
|
|
97
|
+
|
|
98
|
+
el.hideHints = true;
|
|
99
|
+
await el.updateComplete;
|
|
100
|
+
expect(el.shadowRoot!.querySelector('[part="hints"]')).to.not.exist;
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
describe('recently used', () => {
|
|
104
|
+
const key = 'slash-menu-test';
|
|
105
|
+
const labelsOf = (el: ZnSlashMenu) =>
|
|
106
|
+
[...el.shadowRoot!.querySelectorAll('[data-slash-item] .slash-menu__label')].map(node => node.textContent);
|
|
107
|
+
|
|
108
|
+
const choose = async (el: ZnSlashMenu, label: string) => {
|
|
109
|
+
const buttons = [...el.shadowRoot!.querySelectorAll<HTMLElement>('[data-slash-item]')];
|
|
110
|
+
const button = buttons.find(item => item.querySelector('.slash-menu__label')?.textContent === label);
|
|
111
|
+
button!.dispatchEvent(new MouseEvent('mousedown', {bubbles: true, cancelable: true}));
|
|
112
|
+
await el.updateComplete;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const items = [
|
|
116
|
+
{label: 'One', value: '1'},
|
|
117
|
+
{label: 'Two', value: '2'},
|
|
118
|
+
{label: 'Three', value: '3'}
|
|
119
|
+
];
|
|
120
|
+
|
|
121
|
+
beforeEach(() => localStorage.removeItem('zn-slash-recent:' + key));
|
|
122
|
+
|
|
123
|
+
it('lists the items chosen here first, newest first, and remembers them', async () => {
|
|
124
|
+
const el = await fixture<ZnSlashMenu>(html`
|
|
125
|
+
<zn-slash-menu recent-key=${key} max-recent="2"></zn-slash-menu>`);
|
|
126
|
+
el.items = items;
|
|
127
|
+
el.show();
|
|
128
|
+
await el.updateComplete;
|
|
129
|
+
|
|
130
|
+
expect(labelsOf(el), 'nothing has been chosen yet').to.eql(['One', 'Two', 'Three']);
|
|
131
|
+
|
|
132
|
+
await choose(el, 'Three');
|
|
133
|
+
await choose(el, 'Two');
|
|
134
|
+
|
|
135
|
+
expect(labelsOf(el)).to.eql(['Two', 'Three', 'One', 'Two', 'Three']);
|
|
136
|
+
expect(el.shadowRoot!.querySelector('[part="group-heading"]')!.textContent).to.contain('Recently used');
|
|
137
|
+
expect(el.activeItem?.label, 'starts on the most recent item').to.equal('Two');
|
|
138
|
+
|
|
139
|
+
const other = await fixture<ZnSlashMenu>(html`
|
|
140
|
+
<zn-slash-menu recent-key=${key} max-recent="2" recent-heading="Your favourites"></zn-slash-menu>`);
|
|
141
|
+
other.items = items;
|
|
142
|
+
other.show();
|
|
143
|
+
await other.updateComplete;
|
|
144
|
+
|
|
145
|
+
expect(labelsOf(other), 'the list outlives the element').to.eql(['Two', 'Three', 'One', 'Two', 'Three']);
|
|
146
|
+
expect(other.shadowRoot!.querySelector('[part="group-heading"]')!.textContent).to.contain('Your favourites');
|
|
147
|
+
|
|
148
|
+
other.clearRecent();
|
|
149
|
+
await other.updateComplete;
|
|
150
|
+
expect(labelsOf(other)).to.eql(['One', 'Two', 'Three']);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('closes the section with a rule only when the items below it have no heading', async () => {
|
|
154
|
+
const el = await fixture<ZnSlashMenu>(html`
|
|
155
|
+
<zn-slash-menu recent-key=${key}></zn-slash-menu>`);
|
|
156
|
+
el.items = items;
|
|
157
|
+
el.show();
|
|
158
|
+
await el.updateComplete;
|
|
159
|
+
|
|
160
|
+
await choose(el, 'Two');
|
|
161
|
+
expect(el.shadowRoot!.querySelector('[part="divider"]'), 'ungrouped items get a rule').to.exist;
|
|
162
|
+
|
|
163
|
+
el.items = items.map(item => ({...item, group: 'Basic blocks'}));
|
|
164
|
+
await el.updateComplete;
|
|
165
|
+
|
|
166
|
+
expect(el.shadowRoot!.querySelector('[part="divider"]'), 'a group heading separates them already').to.not.exist;
|
|
167
|
+
expect(el.shadowRoot!.querySelectorAll('[part="group-heading"]').length).to.equal(2);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('stands aside while the user is searching, and stays off without a key', async () => {
|
|
171
|
+
const el = await fixture<ZnSlashMenu>(html`
|
|
172
|
+
<zn-slash-menu recent-key=${key}></zn-slash-menu>`);
|
|
173
|
+
el.items = items;
|
|
174
|
+
el.show();
|
|
175
|
+
await el.updateComplete;
|
|
176
|
+
|
|
177
|
+
await choose(el, 'Three');
|
|
178
|
+
el.query = 'o';
|
|
179
|
+
await el.updateComplete;
|
|
180
|
+
|
|
181
|
+
expect(labelsOf(el), 'the ranked matches are the answer to a query').to.eql(['One', 'Two', 'Three']);
|
|
182
|
+
|
|
183
|
+
el.recentKey = '';
|
|
184
|
+
el.query = '';
|
|
185
|
+
await el.updateComplete;
|
|
186
|
+
|
|
187
|
+
expect(labelsOf(el)).to.eql(['One', 'Two', 'Three']);
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
|
|
84
191
|
it('reports how many matches were not rendered', async () => {
|
|
85
192
|
const el = await fixture<ZnSlashMenu>(html`
|
|
86
193
|
<zn-slash-menu max-items="2"></zn-slash-menu>`);
|
|
@@ -200,12 +200,18 @@ export default class ZnTextarea extends ZincElement implements ZincFormControl {
|
|
|
200
200
|
/** The characters that open the slash menu. */
|
|
201
201
|
@property({attribute: 'slash-trigger'}) slashTrigger = '/';
|
|
202
202
|
|
|
203
|
-
/** The
|
|
203
|
+
/** The name the slash menu's list is announced by. */
|
|
204
204
|
@property({attribute: 'slash-heading'}) slashHeading = 'Insert';
|
|
205
205
|
|
|
206
206
|
/** Hides the insertion keys normally shown against the slash menu's items. */
|
|
207
207
|
@property({attribute: 'slash-hide-keys', type: Boolean}) slashHideKeys = false;
|
|
208
208
|
|
|
209
|
+
/**
|
|
210
|
+
* Lists the items most recently chosen here above the rest, remembered in `localStorage` under this
|
|
211
|
+
* key. Share a key between the fields that should share a history; leave unset to offer no such list.
|
|
212
|
+
*/
|
|
213
|
+
@property({attribute: 'slash-recent-key'}) slashRecentKey = '';
|
|
214
|
+
|
|
209
215
|
/**
|
|
210
216
|
* Resolves additional items each time the menu opens, for lists that come from elsewhere (e.g. an
|
|
211
217
|
* API). Receives the current query and may return a promise. JavaScript only.
|
|
@@ -587,7 +593,11 @@ export default class ZnTextarea extends ZincElement implements ZincFormControl {
|
|
|
587
593
|
</div>
|
|
588
594
|
${this.hasSlashMenu
|
|
589
595
|
? html`
|
|
590
|
-
<zn-slash-menu
|
|
596
|
+
<zn-slash-menu
|
|
597
|
+
part="slash-menu"
|
|
598
|
+
heading=${this.slashHeading}
|
|
599
|
+
recent-key=${this.slashRecentKey}
|
|
600
|
+
?hide-keys=${this.slashHideKeys}></zn-slash-menu>`
|
|
591
601
|
: ''}
|
|
592
602
|
<slot name="slash-menu"></slot>
|
|
593
603
|
<slot name="slash-items" hidden></slot>
|
|
@@ -151,8 +151,8 @@ describe('<zn-textarea>', () => {
|
|
|
151
151
|
|
|
152
152
|
expect(el.shadowRoot!.querySelector('zn-slash-menu'), 'no second menu is created').to.be.null;
|
|
153
153
|
expect(slotted.items.map(item => item.label)).to.deep.equal(['Brand name', 'Support email']);
|
|
154
|
-
expect(slotted.shadowRoot!.querySelector('[part="
|
|
155
|
-
.to.
|
|
154
|
+
expect(slotted.shadowRoot!.querySelector('[part="list"]')?.getAttribute('aria-label'))
|
|
155
|
+
.to.equal('Replacements');
|
|
156
156
|
expect(menuItemLabels(slotted), 'the author\'s max-items is respected').to.deep.equal(['Brand name']);
|
|
157
157
|
|
|
158
158
|
press(el, 'Enter');
|
|
@@ -69,12 +69,15 @@ export default class ZnTranslations extends ZincElement implements ZincFormContr
|
|
|
69
69
|
/** The characters that open the slash menu. */
|
|
70
70
|
@property({attribute: 'slash-trigger'}) slashTrigger = '/';
|
|
71
71
|
|
|
72
|
-
/** The
|
|
72
|
+
/** The name the slash menu's list is announced by. */
|
|
73
73
|
@property({attribute: 'slash-heading'}) slashHeading = 'Insert';
|
|
74
74
|
|
|
75
75
|
/** Hides the insertion keys normally shown against the slash menu's items. */
|
|
76
76
|
@property({attribute: 'slash-hide-keys', type: Boolean}) slashHideKeys = false;
|
|
77
77
|
|
|
78
|
+
/** Lists the slash menu items most recently chosen here above the rest, remembered under this key. */
|
|
79
|
+
@property({attribute: 'slash-recent-key'}) slashRecentKey = '';
|
|
80
|
+
|
|
78
81
|
/** Resolves additional slash menu items each time the menu opens. JavaScript only. */
|
|
79
82
|
@property({attribute: false}) slashItemsProvider?: (query: string) => SlashMenuItem[] | Promise<SlashMenuItem[]>;
|
|
80
83
|
|
|
@@ -447,6 +450,7 @@ export default class ZnTranslations extends ZincElement implements ZincFormContr
|
|
|
447
450
|
slash-trigger="${this.slashTrigger}"
|
|
448
451
|
slash-heading="${this.slashHeading}"
|
|
449
452
|
slash-preset="${this.slashPreset}"
|
|
453
|
+
slash-recent-key="${this.slashRecentKey}"
|
|
450
454
|
?slash-hide-keys="${this.slashHideKeys}"
|
|
451
455
|
.slashItems="${this.slashItems}"
|
|
452
456
|
.slashItemsProvider="${this.slashItemsProvider}"
|