@kubex/zinc 1.1.78 → 1.1.80

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.
Files changed (34) hide show
  1. package/dist/custom-elements.json +1149 -130
  2. package/dist/vscode.html-custom-data.json +175 -1
  3. package/dist/web-types.json +396 -2
  4. package/dist/zn.d.ts +384 -0
  5. package/dist/zn.min.js +350 -295
  6. package/docs/pages/components/inline-edit.md +21 -0
  7. package/docs/pages/components/rating.md +2 -1
  8. package/docs/pages/components/slash-item.md +126 -0
  9. package/docs/pages/components/slash-menu.md +168 -0
  10. package/docs/pages/components/textarea.md +148 -0
  11. package/docs/pages/components/translations.md +34 -0
  12. package/package.json +1 -1
  13. package/src/components/inline-edit/inline-edit.component.ts +31 -0
  14. package/src/components/inline-edit/inline-edit.test.ts +83 -1
  15. package/src/components/rating/rating.component.ts +10 -1
  16. package/src/components/rating/rating.scss +6 -9
  17. package/src/components/slash-item/index.ts +12 -0
  18. package/src/components/slash-item/slash-item.component.ts +76 -0
  19. package/src/components/slash-item/slash-item.scss +5 -0
  20. package/src/components/slash-menu/index.ts +14 -0
  21. package/src/components/slash-menu/slash-menu-controller.ts +361 -0
  22. package/src/components/slash-menu/slash-menu-items.ts +122 -0
  23. package/src/components/slash-menu/slash-menu.component.ts +305 -0
  24. package/src/components/slash-menu/slash-menu.scss +120 -0
  25. package/src/components/slash-menu/slash-menu.test.ts +154 -0
  26. package/src/components/textarea/textarea.component.ts +143 -0
  27. package/src/components/textarea/textarea.test.ts +310 -2
  28. package/src/components/translations/translations.component.ts +31 -0
  29. package/src/components/translations/translations.test.ts +89 -1
  30. package/src/events/events.ts +2 -0
  31. package/src/events/zn-slash-insert.ts +9 -0
  32. package/src/events/zn-slash-select.ts +9 -0
  33. package/src/utilities/caret-position.ts +118 -0
  34. package/src/zinc.ts +3 -0
@@ -117,6 +117,27 @@ Use `input-type="textarea"` for multi-line text content.
117
117
  </div>
118
118
  ```
119
119
 
120
+ #### Slash Menu Quick Insertions
121
+
122
+ {% raw %}
123
+
124
+ `slash-items`, `slash-preset`, `slash-trigger` and `slash-heading` are forwarded to the inner
125
+ [`zn-textarea`](/components/textarea#slash-menu-quick-insertions), so typing `/` offers replacement strings at the
126
+ caret. The menu claims `Enter` and `Escape` while it is open, so choosing an item neither submits nor cancels the edit.
127
+
128
+ ```html:preview
129
+ <zn-inline-edit
130
+ name="terms"
131
+ input-type="textarea"
132
+ textarea-rows="4"
133
+ value="This agreement is between you and "
134
+ slash-heading="Replacement strings"
135
+ slash-items="Brand name={{BRAND_NAME}}, Legal entity={{LEGAL_ENTITY}}, Jurisdiction={{JURISDICTION}}">
136
+ </zn-inline-edit>
137
+ ```
138
+
139
+ {% endraw %}
140
+
120
141
  ### Number Input Type
121
142
 
122
143
  Use `input-type="number"` for numeric values.
@@ -328,7 +328,8 @@ Available CSS custom properties:
328
328
  - `--symbol-color` - Color of unfilled symbols
329
329
  - `--symbol-color-active` - Color of filled/active symbols
330
330
  - `--symbol-size` - Size of the symbols
331
- - `--symbol-spacing` - Spacing between symbols
331
+ - `--symbol-spacing` - Gap between symbols. The rating adds no padding of its own, so the first symbol sits flush with
332
+ the component's edge and lines up with other form controls.
332
333
  - `--preview-color` - Color of the preview value (see [Preview](#preview))
333
334
  - `--preview-size` - Font size of the preview value
334
335
 
@@ -0,0 +1,126 @@
1
+ ---
2
+ meta:
3
+ title: Slash Item
4
+ description: Slash items declare the quick insertions offered by a slash menu.
5
+ layout: component
6
+ ---
7
+
8
+ {% raw %}
9
+
10
+ `<zn-slash-item>` renders nothing on its own. It describes one entry for the component it sits in — today that is
11
+ [`zn-textarea`](/components/textarea#slash-menu-quick-insertions), which picks up items placed anywhere inside it,
12
+ including inside a slotted [`zn-slash-menu`](/components/slash-menu).
13
+
14
+ ```html:preview
15
+ <zn-textarea label="Terms and conditions" rows="5" help-text="Type / to insert">
16
+ <zn-slash-item
17
+ icon="tag@lu"
18
+ label="Brand name"
19
+ description="The merchant's trading name"
20
+ value="{{BRAND_NAME}}"></zn-slash-item>
21
+ <zn-slash-item
22
+ icon="mail@lu"
23
+ label="Support email"
24
+ value="{{SUPPORT_EMAIL}}"></zn-slash-item>
25
+ </zn-textarea>
26
+ ```
27
+
28
+ ## Examples
29
+
30
+ ### Inserting Long Or Multi-Line Text
31
+
32
+ Leave `value` off and the element's text content is inserted instead, which keeps whole clauses readable in markup.
33
+
34
+ ```html:preview
35
+ <zn-textarea label="Policy" rows="6" help-text="Type / to insert">
36
+ <zn-slash-item icon="scale@lu" label="Governing law">This agreement is governed by the laws of {{JURISDICTION}}, and the parties submit to the exclusive jurisdiction of its courts.</zn-slash-item>
37
+ <zn-slash-item icon="undo-2@lu" label="Refund window">Refunds are available within {{REFUND_DAYS}} days of purchase.</zn-slash-item>
38
+ </zn-textarea>
39
+ ```
40
+
41
+ ### Searching, Grouping And Ordering
42
+
43
+ `keywords` adds terms an item can be found by, `group` collects items under a heading, and `order` overrides the
44
+ position an item takes in the list.
45
+
46
+ ```html:preview
47
+ <zn-textarea label="Grouped insertions" rows="5" help-text="Type / then try 'company'">
48
+ <zn-slash-item
49
+ group="Merchant"
50
+ icon="tag@lu"
51
+ label="Brand name"
52
+ keywords="company, trading"
53
+ value="{{BRAND_NAME}}"></zn-slash-item>
54
+ <zn-slash-item
55
+ group="Merchant"
56
+ icon="building@lu"
57
+ label="Legal entity"
58
+ keywords="company, registered"
59
+ value="{{LEGAL_ENTITY}}"></zn-slash-item>
60
+ <zn-slash-item
61
+ group="Customer"
62
+ icon="user@lu"
63
+ label="Customer name"
64
+ value="{{CUSTOMER_NAME}}"></zn-slash-item>
65
+ </zn-textarea>
66
+ ```
67
+
68
+ ### Placing The Caret Inside An Insertion
69
+
70
+ `caret-offset` sets where the caret lands after insertion, as an offset into the inserted text. Here it lands between
71
+ the tags, ready for the conditional's body.
72
+
73
+ ```html:preview
74
+ <zn-textarea label="Conditional block" rows="5" help-text="Type / to insert">
75
+ <zn-slash-item
76
+ icon="git-branch@lu"
77
+ label="If trial customer"
78
+ caret-offset="12"
79
+ value="{{IF_TRIAL}}{{END_IF}}"></zn-slash-item>
80
+ </zn-textarea>
81
+ ```
82
+
83
+ ### Disabled Items
84
+
85
+ A disabled item is listed but cannot be chosen — useful for showing a replacement string that is not available in the
86
+ current context.
87
+
88
+ ```html:preview
89
+ <zn-textarea label="Available insertions" rows="5" help-text="Type / to insert">
90
+ <zn-slash-item label="Brand name" value="{{BRAND_NAME}}"></zn-slash-item>
91
+ <zn-slash-item
92
+ label="Invoice number"
93
+ description="Only available on invoice templates"
94
+ value="{{INVOICE_NUMBER}}"
95
+ disabled></zn-slash-item>
96
+ </zn-textarea>
97
+ ```
98
+
99
+ ### Handling An Item Yourself
100
+
101
+ An item with an `action` and no `value` inserts nothing. Listen for `zn-slash-select`, call `preventDefault()`, and do
102
+ whatever the action means in your application. The typed trigger and query are removed either way — they are a command,
103
+ not content.
104
+
105
+ ```html:preview
106
+ <zn-textarea id="action-textarea" label="Notes" rows="5" help-text="Type / to insert">
107
+ <zn-slash-item label="Brand name" value="{{BRAND_NAME}}"></zn-slash-item>
108
+ <zn-slash-item icon="clock@lu" label="Timestamp" action="timestamp"></zn-slash-item>
109
+ </zn-textarea>
110
+
111
+ <script type="module">
112
+ const textarea = document.getElementById('action-textarea');
113
+
114
+ await customElements.whenDefined('zn-textarea');
115
+
116
+ textarea.addEventListener('zn-slash-select', (event) => {
117
+ if (event.detail.item.action !== 'timestamp') return;
118
+
119
+ event.preventDefault();
120
+ textarea.setRangeText(new Date().toISOString());
121
+ textarea.focus();
122
+ });
123
+ </script>
124
+ ```
125
+
126
+ {% endraw %}
@@ -0,0 +1,168 @@
1
+ ---
2
+ meta:
3
+ title: Slash Menu
4
+ description: A keyboard-driven list of quick insertions, anchored to the caret of the field that opened it.
5
+ layout: component
6
+ ---
7
+
8
+ {% raw %}
9
+
10
+ `<zn-slash-menu>` is the panel behind a slash menu. Most of the time you don't use it directly — a component drives it
11
+ for you, as [`zn-textarea`](/components/textarea#slash-menu-quick-insertions) does with its
12
+ [`zn-slash-item`](/components/slash-item) entries:
13
+
14
+ ```html:preview
15
+ <zn-textarea label="Terms and conditions" rows="5" help-text="Type / to insert a replacement string"
16
+ slash-items="Brand name={{BRAND_NAME}}, Legal entity={{LEGAL_ENTITY}}"></zn-textarea>
17
+ ```
18
+
19
+ Slot one into a textarea when you want the panel's own settings — `heading`, `max-items`, `placement`, `empty-text`, or
20
+ its width — declared in markup. The textarea then drives your menu instead of building its own:
21
+
22
+ ```html:preview
23
+ <zn-textarea label="Terms and conditions" rows="6" help-text="Type / to insert">
24
+ <zn-slash-menu slot="slash-menu" heading="Replacement strings" max-items="6" style="--slash-menu-width: 360px">
25
+ <zn-slash-item icon="tag@lu" label="Brand name" value="{{BRAND_NAME}}"></zn-slash-item>
26
+ <zn-slash-item icon="building@lu" label="Legal entity" value="{{LEGAL_ENTITY}}"></zn-slash-item>
27
+ <zn-slash-item icon="scale@lu" label="Jurisdiction" value="{{JURISDICTION}}"></zn-slash-item>
28
+ </zn-slash-menu>
29
+ </zn-textarea>
30
+ ```
31
+
32
+ Reach for the component on its own when you are adding a slash menu to a control the library doesn't cover. It renders
33
+ and positions the list; the `SlashMenuController` watches a text field, tracks the query and inserts the result.
34
+
35
+ ## Examples
36
+
37
+ ### Driving It Directly
38
+
39
+ Set `items`, position the panel with `anchor` (an element or a
40
+ [virtual element](https://floating-ui.com/docs/virtual-elements)), and call `show()`. The menu emits
41
+ `zn-slash-item-select` when an item is chosen. That event does not cross shadow boundaries, so a component that hosts
42
+ the menu in its shadow root re-emits it as its own public event.
43
+
44
+ ```html:preview
45
+ <zn-button id="menu-anchor">Open the menu</zn-button>
46
+ <zn-slash-menu id="standalone-menu"></zn-slash-menu>
47
+ <div id="standalone-log" style="margin-top: 1rem; font-family: monospace; font-size: 0.875rem;"></div>
48
+
49
+ <script type="module">
50
+ const anchor = document.getElementById('menu-anchor');
51
+ const menu = document.getElementById('standalone-menu');
52
+ const log = document.getElementById('standalone-log');
53
+
54
+ await customElements.whenDefined('zn-slash-menu');
55
+
56
+ menu.items = [
57
+ {label: 'Brand name', value: '{{BRAND_NAME}}', icon: 'tag@lu', group: 'Merchant'},
58
+ {label: 'Legal entity', value: '{{LEGAL_ENTITY}}', icon: 'building@lu', group: 'Merchant'},
59
+ {label: 'Jurisdiction', value: '{{JURISDICTION}}', icon: 'scale@lu', group: 'Policy'}
60
+ ];
61
+ menu.anchor = anchor;
62
+
63
+ anchor.addEventListener('click', () => menu.open ? menu.hide() : menu.show());
64
+
65
+ menu.addEventListener('zn-slash-item-select', (event) => {
66
+ log.textContent = `selected ${event.detail.item.label} → ${event.detail.item.value}`;
67
+ menu.hide();
68
+ });
69
+ </script>
70
+ ```
71
+
72
+ ### Keyboard Navigation
73
+
74
+ The menu doesn't listen for keys itself — whatever owns the field decides which keys belong to the menu, then calls
75
+ `moveActive()`, `selectActive()` and `hide()`. Disabled items are skipped, and moving past either end wraps around.
76
+
77
+ ```html:preview
78
+ <zn-button id="nav-toggle">Toggle</zn-button>
79
+ <zn-button id="nav-up">↑</zn-button>
80
+ <zn-button id="nav-down">↓</zn-button>
81
+ <zn-button id="nav-select">Enter</zn-button>
82
+ <zn-slash-menu id="nav-menu"></zn-slash-menu>
83
+ <div id="nav-log" style="margin-top: 1rem; font-family: monospace; font-size: 0.875rem;"></div>
84
+
85
+ <script type="module">
86
+ const menu = document.getElementById('nav-menu');
87
+ const toggle = document.getElementById('nav-toggle');
88
+ const log = document.getElementById('nav-log');
89
+
90
+ await customElements.whenDefined('zn-slash-menu');
91
+
92
+ menu.items = [
93
+ {label: 'Brand name', value: '{{BRAND_NAME}}'},
94
+ {label: 'Not available here', value: '{{INVOICE_NUMBER}}', disabled: true},
95
+ {label: 'Jurisdiction', value: '{{JURISDICTION}}'}
96
+ ];
97
+ menu.anchor = toggle;
98
+
99
+ toggle.addEventListener('click', () => menu.open ? menu.hide() : menu.show());
100
+ document.getElementById('nav-up').addEventListener('click', () => menu.moveActive(-1));
101
+ document.getElementById('nav-down').addEventListener('click', () => menu.moveActive(1));
102
+ document.getElementById('nav-select').addEventListener('click', () => menu.selectActive());
103
+
104
+ menu.addEventListener('zn-slash-item-select', (event) => {
105
+ log.textContent = `selected ${event.detail.item.label}`;
106
+ });
107
+ </script>
108
+ ```
109
+
110
+ ### Truncating Long Lists
111
+
112
+ `max-items` caps how many items are rendered; the rest are reported in a footer rather than silently dropped. The panel
113
+ scrolls when its content exceeds `--slash-menu-max-height`.
114
+
115
+ ```html:preview
116
+ <zn-button id="capped-toggle">Show 3 of 9</zn-button>
117
+ <zn-slash-menu id="capped-menu" max-items="3"></zn-slash-menu>
118
+
119
+ <script type="module">
120
+ const menu = document.getElementById('capped-menu');
121
+ const toggle = document.getElementById('capped-toggle');
122
+
123
+ await customElements.whenDefined('zn-slash-menu');
124
+
125
+ menu.items = Array.from({length: 9}, (_, i) => ({label: `Replacement ${i + 1}`, value: `{{TOKEN_${i + 1}}}`}));
126
+ menu.anchor = toggle;
127
+
128
+ toggle.addEventListener('click', () => menu.open ? menu.hide() : menu.show());
129
+ </script>
130
+ ```
131
+
132
+ ### Attaching It To Your Own Field
133
+
134
+ `SlashMenuController` is the reusable half. Give it the field, the menu, and the items; it handles trigger detection,
135
+ filtering, keyboard handling and insertion.
136
+
137
+ ```js
138
+ import {SlashMenuController} from '@kubex/zinc';
139
+
140
+ class MyEditor extends ZincElement {
141
+ private slash = new SlashMenuController(this, {
142
+ menu: () => this.shadowRoot.querySelector('zn-slash-menu'),
143
+ items: () => [{label: 'Brand name', value: '{{BRAND_NAME}}', icon: 'tag@lu'}],
144
+ trigger: () => '/',
145
+ onSelect: (item, query) => !this.emit('my-select', {detail: {item, query}}).defaultPrevented,
146
+ onInsert: (item, value) => this.emit('my-insert', {detail: {item, value}})
147
+ });
148
+
149
+ firstUpdated() {
150
+ this.slash.attach(this.shadowRoot.querySelector('textarea'));
151
+ }
152
+ }
153
+ ```
154
+
155
+ Items can be shared between fields by registering them once as a preset:
156
+
157
+ ```js
158
+ import {registerSlashMenuPreset} from '@kubex/zinc';
159
+
160
+ registerSlashMenuPreset('legal', [
161
+ {label: 'Brand name', value: '{{BRAND_NAME}}', icon: 'tag@lu'},
162
+ {label: 'Jurisdiction', value: '{{JURISDICTION}}', icon: 'scale@lu'}
163
+ ]);
164
+ ```
165
+
166
+ Any component that reads presets — `zn-textarea` via `slash-preset="legal"` — then offers the same list.
167
+
168
+ {% endraw %}
@@ -408,6 +408,154 @@ Use methods to programmatically control the textarea.
408
408
  </script>
409
409
  ```
410
410
 
411
+ ### Slash Menu Quick Insertions
412
+
413
+ {% raw %}
414
+
415
+ Give a textarea a list of quick insertions and typing `/` opens a menu at the caret, in the same way the
416
+ [editor](/components/editor)'s context menu works. It is built for replacement strings — merge fields such as
417
+ `{{BRAND_NAME}}` in legal copy — but any text can be inserted.
418
+
419
+ The list can be declared as an attribute, which accepts the shorthand `Label={{TOKEN}}` for each entry:
420
+
421
+ ```html:preview
422
+ <zn-textarea
423
+ label="Terms and conditions"
424
+ rows="6"
425
+ help-text="Type / to insert a replacement string"
426
+ slash-items="Brand name={{BRAND_NAME}}, Legal entity={{LEGAL_ENTITY}}, Jurisdiction={{JURISDICTION}}, Support email={{SUPPORT_EMAIL}}">
427
+ This agreement is between you and {{LEGAL_ENTITY}}, trading as
428
+ </zn-textarea>
429
+ ```
430
+
431
+ Type `/` at the start of a word, keep typing to filter, then choose an item with `↑`/`↓` and `Enter` (or `Tab`), or by
432
+ clicking it. `Escape` closes the menu without inserting and leaves the field focused. The trigger and the query that
433
+ follows it are replaced by the item's value.
434
+
435
+ #### Declaring Items With `zn-slash-item`
436
+
437
+ For richer entries — icons, descriptions, groups, or multi-line values — put [`zn-slash-item`](/components/slash-item)
438
+ elements inside the textarea. No `slot` attribute is needed: items are picked up wherever they sit. An item's text
439
+ content is inserted when it has no `value` attribute, which keeps long clauses readable in markup.
440
+
441
+ ```html:preview
442
+ <zn-textarea label="Privacy policy" rows="8" help-text="Type / to insert">
443
+ <zn-slash-item
444
+ group="Merchant"
445
+ icon="tag@lu"
446
+ label="Brand name"
447
+ description="The merchant's trading name"
448
+ keywords="company, trading"
449
+ value="{{BRAND_NAME}}"></zn-slash-item>
450
+ <zn-slash-item
451
+ group="Merchant"
452
+ icon="mail@lu"
453
+ label="Support email"
454
+ value="{{SUPPORT_EMAIL}}"></zn-slash-item>
455
+ <zn-slash-item
456
+ group="Clauses"
457
+ icon="scale@lu"
458
+ label="Governing law"
459
+ description="Full clause, inserted inline">This agreement is governed by the laws of {{JURISDICTION}}, and the parties submit to the exclusive jurisdiction of its courts.</zn-slash-item>
460
+ </zn-textarea>
461
+ ```
462
+
463
+ #### Slotting The Menu Itself
464
+
465
+ Wrap the items in a [`zn-slash-menu`](/components/slash-menu) on the `slash-menu` slot to keep the whole configuration
466
+ in one place and set the panel's own attributes — `heading`, `max-items`, `placement`, `empty-text` — in markup. The
467
+ textarea uses that menu instead of building its own, so anything you style on it applies.
468
+
469
+ ```html:preview
470
+ <zn-textarea label="Terms and conditions" rows="7" help-text="Type / to insert">
471
+ <zn-slash-menu slot="slash-menu" heading="Replacement strings" max-items="6" style="--slash-menu-width: 360px">
472
+ <zn-slash-item icon="tag@lu" label="Brand name" value="{{BRAND_NAME}}"></zn-slash-item>
473
+ <zn-slash-item icon="building@lu" label="Legal entity" value="{{LEGAL_ENTITY}}"></zn-slash-item>
474
+ <zn-slash-item icon="scale@lu" label="Jurisdiction" value="{{JURISDICTION}}"></zn-slash-item>
475
+ <zn-slash-item icon="mail@lu" label="Support email" value="{{SUPPORT_EMAIL}}"></zn-slash-item>
476
+ </zn-slash-menu>
477
+ </zn-textarea>
478
+ ```
479
+
480
+ #### Reusable Presets
481
+
482
+ Register a list once and reference it by name from any textarea with `slash-preset`. This keeps a single definition of
483
+ the merge fields an application allows.
484
+
485
+ ```html:preview
486
+ <zn-textarea
487
+ label="Refund policy"
488
+ rows="5"
489
+ slash-preset="legal"
490
+ help-text="Type / to insert a replacement string"></zn-textarea>
491
+
492
+ <script type="module">
493
+ import {registerSlashMenuPreset} from '/dist/zn.min.js';
494
+
495
+ registerSlashMenuPreset('legal', [
496
+ {label: 'Brand name', value: '{{BRAND_NAME}}', icon: 'tag@lu', group: 'Merchant'},
497
+ {label: 'Legal entity', value: '{{LEGAL_ENTITY}}', icon: 'building@lu', group: 'Merchant'},
498
+ {label: 'Refund window', value: '{{REFUND_DAYS}} days', icon: 'calendar@lu', group: 'Policy'},
499
+ {label: 'Jurisdiction', value: '{{JURISDICTION}}', icon: 'scale@lu', group: 'Policy'}
500
+ ]);
501
+ </script>
502
+ ```
503
+
504
+ #### Changing the Trigger
505
+
506
+ Set `slash-trigger` to any characters. Using `{{` lets someone who already knows the token they want type it directly
507
+ and get the list as they go.
508
+
509
+ ```html:preview
510
+ <zn-textarea
511
+ label="Email footer"
512
+ rows="4"
513
+ slash-trigger="{{"
514
+ help-text="Type {{ to insert a replacement string"
515
+ slash-items="Brand name={{BRAND_NAME}}, Unsubscribe link={{UNSUBSCRIBE_URL}}"></zn-textarea>
516
+ ```
517
+
518
+ #### Custom And Remote Items
519
+
520
+ `slashItemsProvider` resolves extra items each time the menu opens, so a list can come from an API. `zn-slash-select` is
521
+ cancelable: call `preventDefault()` to handle an item yourself instead of inserting its value, which is how items that
522
+ open a dialog or run a command are built. The trigger and query the user typed are removed either way.
523
+
524
+ ```html:preview
525
+ <zn-textarea id="provider-textarea" label="Notes" rows="5" help-text="Type / to insert"></zn-textarea>
526
+ <div id="provider-log" style="margin-top: 1rem; font-family: monospace; font-size: 0.875rem;"></div>
527
+
528
+ <script type="module">
529
+ const textarea = document.getElementById('provider-textarea');
530
+ const log = document.getElementById('provider-log');
531
+
532
+ await customElements.whenDefined('zn-textarea');
533
+
534
+ textarea.slashItemsProvider = async (query) => {
535
+ // In a real application this would be a fetch
536
+ return [
537
+ {label: 'Today', value: new Date().toLocaleDateString(), icon: 'calendar@lu'},
538
+ {label: 'Clear the field', icon: 'trash-2@lu', action: 'clear', description: 'Handled by the page'}
539
+ ];
540
+ };
541
+
542
+ textarea.addEventListener('zn-slash-select', (event) => {
543
+ log.textContent = `zn-slash-select: ${event.detail.item.label}`;
544
+
545
+ if (event.detail.item.action === 'clear') {
546
+ event.preventDefault();
547
+ textarea.value = '';
548
+ }
549
+ });
550
+
551
+ textarea.addEventListener('zn-slash-insert', (event) => {
552
+ log.textContent = `zn-slash-insert: ${event.detail.value}`;
553
+ });
554
+ </script>
555
+ ```
556
+
557
+ {% endraw %}
558
+
411
559
  ### Selection and Range Methods
412
560
 
413
561
  Use `setSelectionRange()` and `setRangeText()` methods to work with text selections.
@@ -323,6 +323,36 @@ A complete example showing product content management with translations.
323
323
  </div>
324
324
  ```
325
325
 
326
+ ### Slash Menu Quick Insertions
327
+
328
+ {% raw %}
329
+
330
+ Copy that carries replacement strings — `{{BRAND_NAME}}` and the like — needs the same tokens in every language. Set
331
+ `slash-items` alongside `input-type="textarea"` and typing `/` offers them at the caret, in whichever language is
332
+ being edited. The attribute takes the same shorthand and JSON that
333
+ [`zn-textarea`](/components/textarea#slash-menu-quick-insertions) accepts, and `slash-preset`, `slash-trigger` and
334
+ `slash-heading` are forwarded too.
335
+
336
+ ```html:preview
337
+ <zn-translations
338
+ label="Confirmation message"
339
+ input-type="textarea"
340
+ textarea-rows="4"
341
+ languages='{"en": "EN", "fr": "FR", "de": "DE"}'
342
+ values='{"en": "Look for /"}'
343
+ slash-heading="Replacement strings"
344
+ slash-items='[
345
+ {"label": "Brand name", "value": "{{BRAND_NAME}}", "description": "The product / company name", "icon": "sell"},
346
+ {"label": "Customer email", "value": "{{CUSTOMER_EMAIL}}", "description": "The customer&#39;s email address", "icon": "mail"},
347
+ {"label": "Renewal price", "value": "{{RENEWAL_PRICE}}", "description": "The renewal price amount", "icon": "payments"}
348
+ ]'></zn-translations>
349
+ ```
350
+
351
+ The menu claims `↑`, `↓`, `Enter`, `Tab` and `Escape` while it is open, so choosing an item never submits the form the
352
+ way `Enter` otherwise would.
353
+
354
+ {% endraw %}
355
+
326
356
  ## Properties
327
357
 
328
358
  | Property | Type | Default | Description |
@@ -335,6 +365,10 @@ A complete example showing product content management with translations.
335
365
  | `flush` | `boolean` | `false` | Removes padding for compact layout |
336
366
  | `languages` | `Record<string, string>` | `{en: "EN"}` | Object mapping language codes to display names |
337
367
  | `values` | `Record<string, string>` | `{}` | Object mapping language codes to translation text |
368
+ | `slash-items` | `SlashMenuItem[]` | `[]` | Quick insertions offered by the slash menu on a `textarea` input |
369
+ | `slash-preset` | `string` | `''` | Registered item sets to offer, comma separated |
370
+ | `slash-trigger` | `string` | `'/'` | The characters that open the slash menu |
371
+ | `slash-heading` | `string` | `'Insert'` | Heading shown above the slash menu's items |
338
372
 
339
373
  ## Events
340
374
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kubex/zinc",
3
- "version": "1.1.78",
3
+ "version": "1.1.80",
4
4
  "description": "A collection of web components for building web applications based off of @shoelace-style/Shoelace",
5
5
  "keywords": [
6
6
  "web components",
@@ -5,6 +5,7 @@ import { defaultValue } from "../../internal/default-value";
5
5
  import { FormControlController } from "../../internal/form";
6
6
  import { HasSlotController } from "../../internal/slot";
7
7
  import { ifDefined } from "lit/directives/if-defined.js";
8
+ import { parseSlashItems, type SlashMenuItem } from "../slash-menu/slash-menu-items";
8
9
  import { property, query, state } from 'lit/decorators.js';
9
10
  import { watch } from "../../internal/watch";
10
11
  import ZincElement from '../../internal/zinc-element';
@@ -97,6 +98,31 @@ export default class ZnInlineEdit extends ZincElement implements ZincFormControl
97
98
  @property({ attribute: "input-type" }) inputType: 'select' | 'text' | 'data-select' | 'number' | 'textarea' = 'text';
98
99
  @property({ attribute: "textarea-rows", type: Number }) textareaRows: 1;
99
100
 
101
+ /**
102
+ * Quick insertions offered by the slash menu of a `textarea` input. Accepts a JSON array of items, or the
103
+ * shorthand `Brand name={{BRAND_NAME}}, Support email={{SUPPORT_EMAIL}}`. Forwarded to the inner `zn-textarea`.
104
+ */
105
+ @property({
106
+ attribute: 'slash-items',
107
+ converter: {
108
+ fromAttribute: (value: string) => parseSlashItems(value),
109
+ toAttribute: (value: SlashMenuItem[]) => JSON.stringify(value)
110
+ }
111
+ })
112
+ slashItems: SlashMenuItem[] = [];
113
+
114
+ /** Names of item sets registered with `registerSlashMenuPreset`, comma separated. */
115
+ @property({ attribute: 'slash-preset' }) slashPreset = '';
116
+
117
+ /** The characters that open the slash menu. */
118
+ @property({ attribute: 'slash-trigger' }) slashTrigger = '/';
119
+
120
+ /** The heading shown above the slash menu's items. */
121
+ @property({ attribute: 'slash-heading' }) slashHeading = 'Insert';
122
+
123
+ /** Resolves additional slash menu items each time the menu opens. JavaScript only. */
124
+ @property({ attribute: false }) slashItemsProvider?: (query: string) => SlashMenuItem[] | Promise<SlashMenuItem[]>;
125
+
100
126
  @property({ type: Object }) options: { [key: string]: string } = {};
101
127
 
102
128
  @property({ attribute: 'provider' }) selectProvider: string;
@@ -434,6 +460,11 @@ export default class ZnInlineEdit extends ZincElement implements ZincFormControl
434
460
  pattern=${ifDefined(this.pattern)}
435
461
  autocomplete=${ifDefined(this.autocomplete)}
436
462
  dir="${this.dir}"
463
+ slash-trigger="${this.slashTrigger}"
464
+ slash-heading="${this.slashHeading}"
465
+ slash-preset="${this.slashPreset}"
466
+ .slashItems="${this.slashItems}"
467
+ .slashItemsProvider="${this.slashItemsProvider}"
437
468
  @zn-input="${this.handleInput}"
438
469
  @zn-blur="${this.handleBlur}">
439
470
  </zn-textarea>`;