@aquera/nile-elements 0.1.67-beta-1.4 → 0.1.67-beta-1.5

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/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "Webcomponent nile-elements following open-wc recommendations",
4
4
  "license": "MIT",
5
5
  "author": "nile-elements",
6
- "version": "0.1.67-beta-1.4",
6
+ "version": "0.1.67-beta-1.5",
7
7
  "main": "dist/src/index.js",
8
8
  "type": "module",
9
9
  "module": "dist/src/index.js",
@@ -2,160 +2,67 @@
2
2
  import { css } from 'lit';
3
3
 
4
4
  export const styles = css`
5
- /* ---- SAFER RESET INSIDE THE EDITOR -------------------------------------- */
6
- .editor * {
7
- all: revert; /* keep your reset… */
8
- box-sizing: border-box; /* …but preserve sane layout */
9
- overflow-wrap: anywhere; /* break long words/URLs */
10
- word-break: break-word;
5
+ .editor * {
6
+ all: revert;
11
7
  }
12
8
 
13
- /* Let component & editor shrink inside flex/grid parents */
14
- nile-rich-text-editor,
15
- .editor {
16
- display: block;
17
- min-width: 0;
18
- max-width: 100%;
19
- font-family: inherit;
20
- }
21
-
22
- /* ---- TOOLBAR ------------------------------------------------------------- */
23
- nile-rte-toolbar,
24
- .toolbar {
25
- display: flex;
26
- flex-wrap: wrap; /* allow wrapping to next row */
27
- align-items: center;
28
- gap: 8px;
29
- padding: 8px;
30
- border: 1px solid #e5e7eb;
31
- border-bottom: none;
32
- border-radius: 8px 8px 0 0;
33
- background: #fff;
34
- box-sizing: border-box;
35
- width: 100%;
36
- }
37
-
38
- /* allow children to shrink instead of forcing overflow */
39
- nile-rte-toolbar > *,
40
- .toolbar > * {
41
- flex: 0 1 auto;
42
- min-width: 0;
43
- }
9
+ nile-rich-text-editor { position: relative; display: block; font-family: inherit; }
44
10
 
45
- /* buttons */
11
+
46
12
  nile-rte-toolbar-item > nile-button::part(base) {
47
- width: 32px;
48
- height: 32px;
49
- padding: 0 6px;
13
+
14
+ width:32px; height:32px; padding:0px 6px;
50
15
  border: none;
51
16
  }
52
-
53
- nile-rte-toolbar-item > button,
54
- .toolbar button,
55
- nile-rte-toolbar button {
56
- border: 1px solid #e5e7eb;
57
- background: #fff;
58
- border-radius: 6px;
59
- cursor: pointer;
60
- }
61
-
62
- nile-rte-toolbar-item > button nile-icon { pointer-events: none; }
63
-
64
- nile-rte-toolbar-item > button.active {
65
- border-color: #2563eb;
66
- background: #eff6ff;
67
- }
68
-
69
- /* selects should be able to shrink on small screens */
70
- nile-rte-select select {
71
- height: 32px;
72
- border: 1px solid #e5e7eb;
73
- border-radius: 6px;
74
- background: #fff;
75
- min-width: 0;
76
- max-width: 100%;
77
- }
78
-
79
- /* color input */
80
- nile-rte-color input[type="color"] {
81
- height: 32px;
82
- width: 36px;
83
- border: 1px solid #e5e7eb;
84
- padding: 0;
85
- border-radius: 6px;
86
- background: #fff;
87
- }
88
-
89
- nile-rte-divider {
90
- width: 1px;
91
- height: 20px;
92
- background: #e5e7eb;
93
- display: inline-block;
94
- margin: 0 4px;
95
- }
96
-
97
- /* ---- EDITOR AREA --------------------------------------------------------- */
98
- .editor {
99
- min-height: 160px;
100
- padding: 12px;
101
- border: 1px solid #e5e7eb;
102
- border-radius: 0 0 8px 8px;
103
- background: #fff;
104
- outline: none;
105
- white-space: pre-wrap;
106
- tab-size: 4;
107
- -moz-tab-size: 4;
108
- overflow-wrap: anywhere;
109
- box-sizing: border-box;
110
- width: 100%;
111
- }
112
-
113
17
 
114
- .editor p { margin: 1em 0; }
115
- .editor h1, .editor h2, .editor h3, .editor h4, .editor h5, .editor h6 {
116
- margin-left: 0;
117
- margin-right: 0;
118
- font-weight: bold;
119
- }
120
- .editor h1 { font-size: 2em; margin: 0.67em 0; }
121
- .editor h2 { font-size: 1.5em; margin: 0.83em 0; }
122
- .editor h3 { font-size: 1.17em; margin: 1em 0; }
123
- .editor h4 { font-size: 1em; margin: 1.33em 0; }
124
- .editor h5 { font-size: 0.83em; margin: 1.67em 0; }
125
- .editor h6 { font-size: 0.67em; margin: 2.33em 0; }
126
-
127
- /* Keep typical wide content inside the box */
128
- .editor img,
129
- .editor svg,
130
- .editor table,
131
- .editor pre,
132
- .editor code,
133
- .editor blockquote {
134
- max-width: 100%;
135
- box-sizing: border-box;
136
- }
137
-
138
- /* Make <pre> wrap on small screens */
139
- .editor pre {
140
- white-space: pre-wrap;
141
- word-break: break-word;
142
- }
18
+
143
19
 
144
- /* Long links shouldn’t push layout */
145
- .editor a { word-break: break-all; }
146
-
147
- /* ---- PREVIEW ------------------------------------------------------------- */
148
- nile-rte-preview {
149
- display: block;
150
- margin-top: 10px;
151
- padding: 10px;
152
- border: 1px dashed #cbd5e1;
153
- border-radius: 8px;
154
- background: #fafafa;
155
- }
156
20
 
157
- /* ---- COLOR TRIGGER (unchanged) ------------------------------------------ */
158
- .rte-color-trigger {
21
+ .toolbar, nile-rte-toolbar {
22
+ display:flex; align-items:center; gap:8px; padding:8px;
23
+ border:1px solid #e5e7eb; border-bottom:none; border-radius:8px 8px 0 0; background:#fff;
24
+ }
25
+
26
+ nile-rte-toolbar-item > button, .toolbar button, nile-rte-toolbar button {
27
+ border:1px solid #e5e7eb; background:#fff; border-radius:6px;
28
+ cursor:pointer;
29
+ }
30
+
31
+
32
+ /* Ensure clicks hit the button (not nested icon internals) */
33
+ nile-rte-toolbar-item > button nile-icon { pointer-events:none; }
34
+
35
+ nile-rte-toolbar-item > button.active { border-color:#2563eb; background:#eff6ff; }
36
+ nile-rte-select select { height:32px; border:1px solid #e5e7eb; border-radius:6px; background:#fff; }
37
+ nile-rte-color input[type="color"] { height:32px; width:36px; border:1px solid #e5e7eb; padding:0; border-radius:6px; background:#fff; }
38
+ nile-rte-divider { width:1px; height:20px; background:#e5e7eb; display:inline-block; margin:0 4px; }
39
+
40
+ .editor p { margin:1em 0; }
41
+ .editor h1 { font-size:2em, all: revert; display: block;
42
+ font-size: 2em;
43
+ margin-top: 0.67em;
44
+ margin-bottom: 0.67em;
45
+ margin-left: 0;
46
+ margin-right: 0;
47
+ font-weight: bold; }
48
+ .editor h2 { all: revert; display: block;
49
+ font-size: 1.5em;
50
+ margin-top: 0.83em;
51
+ margin-bottom: 0.83em;
52
+ margin-left: 0;
53
+ margin-right: 0;
54
+ font-weight: bold;}
55
+ .editor h3 { font-size:1.17em }
56
+ .editor h4 { font-size:1em }
57
+ .editor h5 { font-size:0.83em }
58
+ .editor h6 { font-size:0.67em }
59
+
60
+ .editor { min-height:160px; padding:12px; border:1px solid #e5e7eb; border-radius:0 0 8px 8px; background:#fff; outline:none; white-space: pre-wrap;
61
+ tab-size: 4;
62
+ -moz-tab-size: 4; }
63
+ nile-rte-preview { display:block; margin-top:10px; padding:10px; border:1px dashed #cbd5e1; border-radius:8px; background:#fafafa; }
64
+
65
+ .rte-color-trigger {
159
66
  display: inline-flex;
160
67
  align-items: center;
161
68
  justify-content: center;
@@ -166,51 +73,37 @@ nile-rte-preview {
166
73
  background: #fff;
167
74
  cursor: pointer;
168
75
  }
76
+
169
77
  .rte-color-trigger .glyph-stack {
170
- display: grid;
171
- grid-auto-rows: max-content;
78
+ display: grid; /* stack vertically */
79
+ grid-auto-rows: max-content;
172
80
  align-items: center;
173
81
  justify-items: center;
174
82
  line-height: 1;
175
83
  }
84
+
176
85
  .rte-color-trigger .glyph {
177
86
  font-size: 14px;
178
87
  line-height: 1;
179
- margin-bottom: 2px;
88
+ margin-bottom: 2px; /* little breathing space above underline */
180
89
  }
90
+
181
91
  .rte-color-trigger .underline {
182
92
  width: 18px;
183
93
  height: 3px;
184
94
  border-radius: 2px;
185
- background: currentColor;
95
+ background: currentColor; /* we override via JS with backgroundColor */
186
96
  }
97
+
98
+ /* (unchanged) highlight square */
187
99
  .rte-color-trigger .swatch-box {
188
100
  width: 18px;
189
101
  height: 16px;
190
102
  border-radius: 4px;
191
103
  border: 1px solid rgba(0,0,0,0.35);
192
- background: currentColor;
193
- }
194
-
195
- /* ---- RESPONSIVE TWEAKS --------------------------------------------------- */
196
- @media (max-width: 900px) {
197
- nile-rte-toolbar { gap: 6px; padding: 6px; }
198
- nile-rte-select select { max-width: 160px; }
104
+ background: currentColor; /* overridden via JS */
199
105
  }
200
106
 
201
- @media (max-width: 600px) {
202
- nile-rte-toolbar { gap: 4px; }
203
- nile-rte-select select { max-width: 120px; }
204
- /* optional fallback if wrapping still feels tight */
205
- /* nile-rte-toolbar { overflow-x: auto; } */
206
- }
207
-
208
- @media (max-width: 420px) {
209
- nile-rte-select select { max-width: 100px; }
210
- nile-rte-divider { display: none; }
211
- }
212
-
213
-
214
107
  `;
215
108
 
216
109
  export default [styles];
@@ -177,60 +177,6 @@ private bgSwatchEl: HTMLElement | null = null;
177
177
  this.ensureAtLeastOneParagraph();
178
178
  }
179
179
 
180
- private onPaste = (e: ClipboardEvent) => {
181
- const html = e.clipboardData?.getData('text/html');
182
- if (!html) return; // let plain text/default paste happen
183
-
184
- e.preventDefault();
185
- this.focusAndRestore();
186
-
187
- const div = document.createElement('div');
188
- div.innerHTML = html;
189
-
190
- // strip layout-y attrs & styles
191
- const walker = document.createTreeWalker(div, NodeFilter.SHOW_ELEMENT);
192
- const layoutProps = new Set([
193
- 'width','height','max-width','min-width','margin','padding','position','left','right','top','bottom',
194
- 'float','display','border','line-height','white-space'
195
- ]);
196
-
197
- while (walker.nextNode()) {
198
- const el = walker.currentNode as HTMLElement;
199
- // remove HTML width/height attributes that cause fixed sizing
200
- el.removeAttribute('width');
201
- el.removeAttribute('height');
202
-
203
- // prune style properties if present
204
- if (el.hasAttribute('style')) {
205
- const style = el.getAttribute('style') || '';
206
- const kept = style.split(';').map(s => s.trim()).filter(Boolean).filter(rule => {
207
- const prop = rule.split(':')[0]?.trim().toLowerCase();
208
- return prop && !layoutProps.has(prop);
209
- });
210
- if (kept.length) el.setAttribute('style', kept.join('; ')); else el.removeAttribute('style');
211
- }
212
- }
213
-
214
- // insert sanitized fragment at the selection
215
- const sel = window.getSelection();
216
- if (sel && sel.rangeCount) {
217
- const range = sel.getRangeAt(0);
218
- range.deleteContents();
219
-
220
- const frag = document.createDocumentFragment();
221
- while (div.firstChild) frag.appendChild(div.firstChild);
222
- range.insertNode(frag);
223
-
224
- // move caret after paste
225
- sel.removeAllRanges();
226
- sel.addRange(range);
227
- }
228
-
229
- this.ensureAtLeastOneParagraph();
230
- this.updateContent();
231
- this.updateToolbarState();
232
- };
233
-
234
180
  private wireEditor() {
235
181
  this.editorEl.addEventListener('input', () => {
236
182
  this.ensureAtLeastOneParagraph();
@@ -238,9 +184,7 @@ private bgSwatchEl: HTMLElement | null = null;
238
184
  });
239
185
  this.editorEl.addEventListener('mouseup', () => this.saveSelection());
240
186
  this.editorEl.addEventListener('keyup', () => this.saveSelection());
241
- this.editorEl.addEventListener('keydown', this.onEditorKeydown);
242
- this.editorEl.addEventListener('paste', this.onPaste);
243
-
187
+ this.editorEl.addEventListener('keydown', this.onEditorKeydown);
244
188
  }
245
189
 
246
190
 
@@ -724,93 +668,43 @@ private ensureAtLeastOneParagraph() {
724
668
  this.updateContent();
725
669
  }
726
670
 
727
- // replace your existing updateContent() with this
728
- private updateContent() {
729
- if (!this.editorEl) return;
730
- this.ensureAtLeastOneParagraph();
731
-
732
- const clone = this.editorEl.cloneNode(true) as HTMLElement;
733
-
734
- const origWalker = document.createTreeWalker(this.editorEl, NodeFilter.SHOW_ELEMENT);
735
- const cloneWalker = document.createTreeWalker(clone, NodeFilter.SHOW_ELEMENT);
736
-
737
- while (origWalker.nextNode() && cloneWalker.nextNode()) {
738
- const origEl = origWalker.currentNode as HTMLElement;
739
- const cloneEl = cloneWalker.currentNode as HTMLElement;
740
-
741
- // start clean
742
- cloneEl.removeAttribute('style');
743
-
744
- // rely on semantics for these; no inline styles
745
- if (['B','STRONG','I','EM','U','H1','H2','H3','H4','H5','H6'].includes(cloneEl.tagName)) {
746
- continue;
671
+ private updateContent() {
672
+ if (!this.editorEl) return;
673
+ this.ensureAtLeastOneParagraph();
674
+
675
+ // Clone content for safe manipulation
676
+ const clone = this.editorEl.cloneNode(true) as HTMLElement;
677
+
678
+ // Walk the original DOM and the clone in parallel
679
+ const origWalker = document.createTreeWalker(this.editorEl, NodeFilter.SHOW_ELEMENT);
680
+ const cloneWalker = document.createTreeWalker(clone, NodeFilter.SHOW_ELEMENT);
681
+
682
+ while (origWalker.nextNode() && cloneWalker.nextNode()) {
683
+ const origEl = origWalker.currentNode as HTMLElement;
684
+ const cloneEl = cloneWalker.currentNode as HTMLElement;
685
+ const computed = window.getComputedStyle(origEl);
686
+
687
+ // Dump *all* computed styles into a single inline style attribute
688
+ const cssText = Array.from(computed)
689
+ .map(prop => `${prop}:${computed.getPropertyValue(prop)}`)
690
+ .join(';');
691
+
692
+ cloneEl.setAttribute('style', cssText);
747
693
  }
748
-
749
- const cssText = this.collectMinimalInline(origEl);
750
- if (cssText) cloneEl.setAttribute('style', cssText);
751
- }
752
-
753
- this.content = clone.innerHTML;
754
- if (this.previewEl) this.previewEl.innerHTML = this.content;
755
-
756
- this.dispatchEvent(new CustomEvent('content-changed', {
757
- detail: { content: this.content },
758
- bubbles: true,
759
- composed: true
760
- }));
761
- }
762
- private collectMinimalInline(el: HTMLElement): string {
763
- const cs = getComputedStyle(el);
764
- const ps = el.parentElement ? getComputedStyle(el.parentElement) : null;
765
-
766
- const out: string[] = [];
767
- const push = (prop: string, val?: string) => { if (val) out.push(`${prop}:${val}`); };
768
-
769
- // 1) text-align on block-ish containers (only if changed)
770
- if (['P','DIV','LI','TD','TH','BLOCKQUOTE','H1','H2','H3','H4','H5','H6'].includes(el.tagName)) {
771
- const ta = cs.textAlign;
772
- const pta = ps?.textAlign || 'start';
773
- if (ta !== pta && ta !== 'start') push('text-align', ta);
774
- }
775
-
776
- // 2) color (only if different from parent)
777
- if (!ps || cs.color !== ps.color) push('color', cs.color);
778
-
779
- // 3) background-color (only if not transparent)
780
- const bg = cs.backgroundColor;
781
- const isTransparent = /transparent|rgba\(\s*0\s*,\s*0\s*,\s*0\s*,\s*0\s*\)/i.test(bg);
782
- if (!isTransparent) push('background-color', bg);
783
-
784
- // 4) font-family (only if different from parent)
785
- const ff = this.normalizeFontFamily(cs.fontFamily);
786
- const pff = this.normalizeFontFamily(ps?.fontFamily || '');
787
- if (ff && ff !== pff) push('font-family', cs.fontFamily);
788
-
789
- // 5) font-size (only if explicitly set / typical inline span use)
790
- if (el.tagName === 'SPAN' || (el as HTMLElement).style.fontSize) {
791
- const fs = cs.fontSize;
792
- const pfs = ps?.fontSize;
793
- if (!pfs || fs !== pfs) push('font-size', fs);
794
- }
795
-
796
- // 6) list-style-type on UL/OL (only if non-default)
797
- if (el.tagName === 'UL' || el.tagName === 'OL') {
798
- const lst = cs.listStyleType;
799
- const def = el.tagName === 'UL' ? 'disc' : 'decimal';
800
- if (lst && lst !== def) push('list-style-type', lst);
694
+
695
+ // Store the fully inlined HTML
696
+ this.content = clone.innerHTML;
697
+
698
+ // Live preview updates
699
+ if (this.previewEl) this.previewEl.innerHTML = this.content;
700
+
701
+ // Emit event with full inline styles
702
+ this.dispatchEvent(new CustomEvent('content-changed', {
703
+ detail: { content: this.content },
704
+ bubbles: true,
705
+ composed: true
706
+ }));
801
707
  }
802
-
803
- // 7) text-decoration-line (keep only if not the default and not coming from <u>)
804
- const tdl = cs.textDecorationLine;
805
- if (tdl && tdl !== 'none' && el.tagName !== 'U') push('text-decoration-line', tdl);
806
-
807
- return out.join(';');
808
- }
809
-
810
- private normalizeFontFamily(ff: string) {
811
- return (ff || '').replace(/["']/g, '').split(',')[0].trim().toLowerCase();
812
- }
813
-
814
708
 
815
709
 
816
710
 
@@ -2953,7 +2953,7 @@
2953
2953
  },
2954
2954
  {
2955
2955
  "name": "nile-rich-text-editor",
2956
- "description": "Events:\n\n * `content-changed` {`CustomEvent<{ content: string; }>`} - \n\nAttributes:\n\n * `value` {`string`} - Initial HTML content\n\n * `mentions` - Optional mentions config (can also be on <nile-rte-mentions mentions=\"...\">)\n\nProperties:\n\n * `value` {`string`} - Initial HTML content\n\n * `mentions` - Optional mentions config (can also be on <nile-rte-mentions mentions=\"...\">)\n\n * `content` {`string`} - \n\n * `editorEl` {`HTMLElement`} - \n\n * `previewEl` {`HTMLElement | null`} - \n\n * `toolbarEl` {`HTMLElement | null`} - \n\n * `lastRange` {`Range | null`} - \n\n * `buttonMap` {`Map<string, HTMLElement[]>`} - \n\n * `headingSelect` {`HTMLSelectElement | null`} - \n\n * `fontSelect` {`HTMLSelectElement | null`} - \n\n * `colorInput` {`HTMLInputElement | null`} - \n\n * `bgColorInput` {`HTMLInputElement | null`} - \n\n * `colorSwatchEl` {`HTMLElement | null`} - \n\n * `bgSwatchEl` {`HTMLElement | null`} - \n\n * `mentionsEl` {`HTMLElement | null`} - \n\n * `onPaste` - \n\n * `onEditorKeydown` - \n\n * `onSelectionChange` - ",
2956
+ "description": "Events:\n\n * `content-changed` {`CustomEvent<{ content: string; }>`} - \n\nAttributes:\n\n * `value` {`string`} - Initial HTML content\n\n * `mentions` - Optional mentions config (can also be on <nile-rte-mentions mentions=\"...\">)\n\nProperties:\n\n * `value` {`string`} - Initial HTML content\n\n * `mentions` - Optional mentions config (can also be on <nile-rte-mentions mentions=\"...\">)\n\n * `content` {`string`} - \n\n * `editorEl` {`HTMLElement`} - \n\n * `previewEl` {`HTMLElement | null`} - \n\n * `toolbarEl` {`HTMLElement | null`} - \n\n * `lastRange` {`Range | null`} - \n\n * `buttonMap` {`Map<string, HTMLElement[]>`} - \n\n * `headingSelect` {`HTMLSelectElement | null`} - \n\n * `fontSelect` {`HTMLSelectElement | null`} - \n\n * `colorInput` {`HTMLInputElement | null`} - \n\n * `bgColorInput` {`HTMLInputElement | null`} - \n\n * `colorSwatchEl` {`HTMLElement | null`} - \n\n * `bgSwatchEl` {`HTMLElement | null`} - \n\n * `mentionsEl` {`HTMLElement | null`} - \n\n * `onEditorKeydown` - \n\n * `onSelectionChange` - ",
2957
2957
  "attributes": [
2958
2958
  {
2959
2959
  "name": "value",