@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,103 @@
1
+ ---
2
+ meta:
3
+ title: Remarkd Editor
4
+ description: A Notion-style block editor for remarkd content — blocks render inline and are edited in place.
5
+ layout: component
6
+ ---
7
+
8
+ `zn-remarkd-editor` is a Notion-style editor for
9
+ [remarkd](https://github.com/packaged/remarkd) content. The document is a list
10
+ of blocks rendered inline with remarkd — there is no separate preview. Click a
11
+ block to edit its raw remarkd source in place; it re-renders when you click
12
+ away (or press Escape / Ctrl+Enter). The `value` is always plain remarkd
13
+ source, submitted with the surrounding form.
14
+
15
+ ```html:preview
16
+ <zn-remarkd-editor
17
+ name="content"
18
+ value="# Product Guide
19
+
20
+ This is a paragraph with **strong text**, __emphasis__, and a [link](https://example.com).
21
+
22
+ - [ ] Draft the guide
23
+ - [x] Review the output
24
+
25
+ NOTE: Click any block to edit its source."
26
+ ></zn-remarkd-editor>
27
+ ```
28
+
29
+ ## Examples
30
+
31
+ ### Adding and Moving Blocks
32
+
33
+ Hover a block to reveal its gutter actions: add a block below it, or grab the
34
+ handle to drag the block somewhere else in the document. Clicking the empty
35
+ area at the end of the document starts a new block. A block committed empty
36
+ is removed.
37
+
38
+ ```html:preview
39
+ <zn-remarkd-editor
40
+ name="content"
41
+ value="Hover me to see the block actions."
42
+ ></zn-remarkd-editor>
43
+ ```
44
+
45
+ ### Images
46
+
47
+ Adding an image opens a dialog. With `attachment-url` set it contains a
48
+ `zn-file` drop area; the file's metadata is POSTed to the endpoint, which must
49
+ respond with `{uploadPath, uploadUrl}`; the file is then PUT to `uploadUrl`
50
+ and `uploadPath` is inserted as an image block. Without `attachment-url` the
51
+ dialog asks for an image URL instead. Dropping an image file straight onto the
52
+ editor uploads it directly.
53
+
54
+ ```html
55
+ <zn-remarkd-editor name="content" attachment-url="/upload"></zn-remarkd-editor>
56
+ ```
57
+
58
+ ### Remarkd Blocks
59
+
60
+ Remarkd's block syntax — hints, containers, code fences — renders with the
61
+ official remarkd styles. Fenced content stays a single block, blank lines and
62
+ all.
63
+
64
+ ````html:preview
65
+ <zn-remarkd-editor name="content" value="TIP: Hint blocks are remarkd-specific.
66
+
67
+ ====
68
+ An example **container** block.
69
+ ====
70
+
71
+ ```
72
+ a code fence
73
+
74
+ with a blank line inside
75
+ ```"
76
+ ></zn-remarkd-editor>
77
+ ````
78
+
79
+ ### Form Integration
80
+
81
+ `zn-remarkd-editor` is a [form control](/getting-started/form-controls); its
82
+ remarkd source is submitted under `name`, and `required` is supported.
83
+
84
+ ```html:preview
85
+ <form class="remarkd-editor-form">
86
+ <zn-remarkd-editor name="content" required value="Edit me, then submit."></zn-remarkd-editor>
87
+ <br />
88
+ <zn-button type="submit" color="success">Submit</zn-button>
89
+ </form>
90
+
91
+ <script type="module">
92
+ const form = document.querySelector('.remarkd-editor-form');
93
+
94
+ await customElements.whenDefined('zn-button');
95
+ await customElements.whenDefined('zn-remarkd-editor');
96
+
97
+ form.addEventListener('submit', (e) => {
98
+ e.preventDefault();
99
+ const data = Object.fromEntries(new FormData(form));
100
+ alert('Submitted!\n\n' + JSON.stringify(data, null, 2));
101
+ });
102
+ </script>
103
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kubex/zinc",
3
- "version": "1.1.63",
3
+ "version": "1.1.64",
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",
@@ -55,7 +55,8 @@
55
55
  "lit": "^3.3.3",
56
56
  "lucide": "^1.17.0",
57
57
  "marked": "^18.0.2",
58
- "quill": "^2.0.3"
58
+ "quill": "^2.0.3",
59
+ "remarkd-js": "github:packaged/remarkd#fc482a4260740e0d61ab1cd50b056545fe195d94"
59
60
  },
60
61
  "devDependencies": {
61
62
  "@custom-elements-manifest/analyzer": "^0.10.3",
@@ -76,9 +76,17 @@ export default class ZnFile extends ZincElement implements ZincFormControl {
76
76
 
77
77
  private readonly formControlController = new FormControlController(this, {
78
78
  assumeInteractionOn: ['zn-change'],
79
- value: (el: ZnFile) => el.files
79
+ // An explicitly cleared control submits an empty value so the server can
80
+ // remove the stored upload; an untouched empty control submits nothing.
81
+ value: (el: ZnFile) => {
82
+ if (el.files?.length) return el.files;
83
+ return el.clearedByUser ? '' : undefined;
84
+ }
80
85
  })
81
86
 
87
+ /** Set when the user clears the control; reset when a file is chosen. */
88
+ private clearedByUser = false;
89
+
82
90
  private readonly hasSlotController = new HasSlotController(this, 'help-text', 'label');
83
91
 
84
92
  private readonly localize = new LocalizeController(this);
@@ -110,6 +118,9 @@ export default class ZnFile extends ZincElement implements ZincFormControl {
110
118
  set files(v: FileList | null) {
111
119
  if (this.input) {
112
120
  this.input.files = v;
121
+ if (v?.length) {
122
+ this.clearedByUser = false;
123
+ }
113
124
  this.updatePreview();
114
125
  }
115
126
  }
@@ -481,6 +492,9 @@ export default class ZnFile extends ZincElement implements ZincFormControl {
481
492
  const dataTransfer = new DataTransfer();
482
493
  files.forEach(f => dataTransfer.items.add(f));
483
494
  this.files = dataTransfer.files;
495
+ if (!dataTransfer.files.length) {
496
+ this.clearedByUser = true;
497
+ }
484
498
  this.input.dispatchEvent(new Event('change'));
485
499
  }
486
500
 
@@ -501,6 +515,7 @@ export default class ZnFile extends ZincElement implements ZincFormControl {
501
515
  if (!hadLocalFile) {
502
516
  this.src = '';
503
517
  }
518
+ this.clearedByUser = true;
504
519
  this.clearConfirmDialog?.hide();
505
520
  this.input.dispatchEvent(new Event('change'));
506
521
  this.emit('zn-clear');
@@ -167,6 +167,10 @@ export default class ZnQueryBuilder extends ZincElement implements ZincFormContr
167
167
  }) showValues: string[] = [];
168
168
 
169
169
 
170
+ private get _usedFilterIds(): Set<string> {
171
+ return new Set([...this._selectedRules.values()].map(rule => rule.id));
172
+ }
173
+
170
174
  get validationMessage(): string {
171
175
  return '';
172
176
  }
@@ -196,7 +200,7 @@ export default class ZnQueryBuilder extends ZincElement implements ZincFormContr
196
200
  size="medium"
197
201
  placeholder="Select Filter"
198
202
  @zn-change="${this._addRule}">
199
- ${this.filters && this.filters.map(item => html`
203
+ ${this.filters && this.filters.filter(item => !this._usedFilterIds.has(item.id)).map(item => html`
200
204
  <zn-option value="${item.id}">
201
205
  ${item.name.charAt(0).toUpperCase() + item.name.slice(1)}
202
206
  </zn-option>`)}
@@ -227,6 +231,7 @@ export default class ZnQueryBuilder extends ZincElement implements ZincFormContr
227
231
 
228
232
  const filter: QueryBuilderItem | undefined = this.filters.find(item => item.id === id);
229
233
  if (filter === undefined) return;
234
+ if (this._usedFilterIds.has(filter.id)) return;
230
235
 
231
236
  const uniqueId = Math.random().toString(36).substring(7);
232
237
 
@@ -237,11 +242,12 @@ export default class ZnQueryBuilder extends ZincElement implements ZincFormContr
237
242
  value: ''
238
243
  });
239
244
 
245
+ const availableFilters = this.filters.filter(item => item.id === filter.id || !this._usedFilterIds.has(item.id));
240
246
  const select = html`
241
247
  <zn-select class="query-builder__key"
242
248
  @zn-change="${(e: ZnChangeEvent) => this._changeRule(uniqueId, e)}"
243
249
  value="${filter?.id}">
244
- ${this.filters.map((item: QueryBuilderItem) => {
250
+ ${availableFilters.map((item: QueryBuilderItem) => {
245
251
  return html`
246
252
  <zn-option value="${item.id}">
247
253
  ${item.name.charAt(0).toUpperCase() + item.name.slice(1)}
@@ -338,7 +344,7 @@ export default class ZnQueryBuilder extends ZincElement implements ZincFormContr
338
344
  break;
339
345
  }
340
346
  case 'dateTime': {
341
- input = this._createDateInput(uniqueId, true , filter.dateSubmitFormat);
347
+ input = this._createDateInput(uniqueId, true, filter.dateSubmitFormat);
342
348
  break;
343
349
  }
344
350
  default: {
@@ -401,9 +407,9 @@ export default class ZnQueryBuilder extends ZincElement implements ZincFormContr
401
407
  ?time-picker="${hasTime}"
402
408
  class="query-builder__value"
403
409
  @zn-change="${(e: ZnChangeEvent) => this._updateDateValue(uniqueId, e, submitFormat)}"
404
- >
410
+ >
405
411
  </zn-datepicker>
406
- `;
412
+ `;
407
413
  return litToHTML<ZnDatepicker>(input);
408
414
  }
409
415
 
@@ -451,7 +457,9 @@ export default class ZnQueryBuilder extends ZincElement implements ZincFormContr
451
457
  this._handleChange();
452
458
  }
453
459
 
454
- private _updateDateValue(id: string, event: Event | { target: ZnDatepicker | HTMLDivElement }, submitFormat: QueryBuilderDateSubmitFormat = 'legacy') {
460
+ private _updateDateValue(id: string, event: Event | {
461
+ target: ZnDatepicker | HTMLDivElement
462
+ }, submitFormat: QueryBuilderDateSubmitFormat = 'legacy') {
455
463
  const filter = this._selectedRules.get(id);
456
464
  if (!filter) return;
457
465
 
@@ -0,0 +1,12 @@
1
+ import ZnRemarkdEditor from './remarkd-editor.component';
2
+
3
+ export * from './remarkd-editor.component';
4
+ export default ZnRemarkdEditor;
5
+
6
+ ZnRemarkdEditor.define('zn-remarkd-editor');
7
+
8
+ declare global {
9
+ interface HTMLElementTagNameMap {
10
+ 'zn-remarkd-editor': ZnRemarkdEditor;
11
+ }
12
+ }