@lukso/web-components 1.156.0 → 1.157.1
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/_commonjsHelpers-B85MJLTf.js +5 -0
- package/dist/_commonjsHelpers-CFO10eej.cjs +7 -0
- package/dist/axe-BK9JSROP.js +35093 -0
- package/dist/axe-C-H1UVi1.cjs +35097 -0
- package/dist/components/index.cjs +5 -5
- package/dist/components/index.js +5 -5
- package/dist/components/lukso-button/index.cjs +6 -6
- package/dist/components/lukso-button/index.js +4 -4
- package/dist/components/lukso-card/index.cjs +20 -20
- package/dist/components/lukso-card/index.js +8 -8
- package/dist/components/lukso-checkbox/index.cjs +3 -3
- package/dist/components/lukso-checkbox/index.js +3 -3
- package/dist/components/lukso-collapse/index.cjs +5 -5
- package/dist/components/lukso-collapse/index.js +4 -4
- package/dist/components/lukso-color-picker/index.cjs +6 -6
- package/dist/components/lukso-color-picker/index.js +5 -5
- package/dist/components/lukso-dropdown/index.cjs +4 -4
- package/dist/components/lukso-dropdown/index.js +4 -4
- package/dist/components/lukso-dropdown-option/index.cjs +2 -2
- package/dist/components/lukso-dropdown-option/index.js +2 -2
- package/dist/components/lukso-footer/index.cjs +2 -2
- package/dist/components/lukso-footer/index.js +2 -2
- package/dist/components/lukso-icon/index.cjs +41 -19
- package/dist/components/lukso-icon/index.d.ts +4 -4
- package/dist/components/lukso-icon/index.d.ts.map +1 -1
- package/dist/components/lukso-icon/index.js +41 -19
- package/dist/components/lukso-icon/lukso-icon.stories.d.ts +15 -1
- package/dist/components/lukso-icon/lukso-icon.stories.d.ts.map +1 -1
- package/dist/components/lukso-icon/vuesax/bold/warning-2.svg +3 -0
- package/dist/components/lukso-icon/vuesax/broken/warning-2.svg +5 -0
- package/dist/components/lukso-icon/vuesax/bulk/warning-2.svg +5 -0
- package/dist/components/lukso-icon/vuesax/linear/warning-2.svg +5 -0
- package/dist/components/lukso-icon/vuesax/outline/warning-2.svg +5 -0
- package/dist/components/lukso-icon/vuesax/twotone/warning-2.svg +5 -0
- package/dist/components/lukso-image/index.cjs +4 -4
- package/dist/components/lukso-image/index.js +4 -4
- package/dist/components/lukso-input/index.cjs +5 -5
- package/dist/components/lukso-input/index.js +4 -4
- package/dist/components/lukso-markdown/index.cjs +3 -2
- package/dist/components/lukso-markdown/index.d.ts.map +1 -1
- package/dist/components/lukso-markdown/index.js +3 -2
- package/dist/components/lukso-markdown-editor/index.cjs +858 -157
- package/dist/components/lukso-markdown-editor/index.d.ts +63 -1
- package/dist/components/lukso-markdown-editor/index.d.ts.map +1 -1
- package/dist/components/lukso-markdown-editor/index.js +857 -156
- package/dist/components/lukso-markdown-editor/lukso-markdown-editor.stories.d.ts +65 -1
- package/dist/components/lukso-markdown-editor/lukso-markdown-editor.stories.d.ts.map +1 -1
- package/dist/components/lukso-modal/index.cjs +2 -2
- package/dist/components/lukso-modal/index.js +2 -2
- package/dist/components/lukso-navbar/index.cjs +3 -3
- package/dist/components/lukso-navbar/index.js +3 -3
- package/dist/components/lukso-pagination/index.cjs +3 -3
- package/dist/components/lukso-pagination/index.js +3 -3
- package/dist/components/lukso-profile/index.cjs +3 -3
- package/dist/components/lukso-profile/index.js +3 -3
- package/dist/components/lukso-progress/index.cjs +7 -7
- package/dist/components/lukso-progress/index.js +4 -4
- package/dist/components/lukso-radio/index.cjs +6 -6
- package/dist/components/lukso-radio/index.js +4 -4
- package/dist/components/lukso-radio-group/index.cjs +3 -3
- package/dist/components/lukso-radio-group/index.js +3 -3
- package/dist/components/lukso-sanitize/index.cjs +30 -19
- package/dist/components/lukso-sanitize/index.js +30 -19
- package/dist/components/lukso-search/index.cjs +7 -7
- package/dist/components/lukso-search/index.js +7 -7
- package/dist/components/lukso-select/index.cjs +5 -5
- package/dist/components/lukso-select/index.js +5 -5
- package/dist/components/lukso-share/index.cjs +2 -2
- package/dist/components/lukso-share/index.js +2 -2
- package/dist/components/lukso-switch/index.cjs +3 -3
- package/dist/components/lukso-switch/index.js +3 -3
- package/dist/components/lukso-tag/index.cjs +3 -3
- package/dist/components/lukso-tag/index.js +3 -3
- package/dist/components/lukso-terms/index.cjs +3 -3
- package/dist/components/lukso-terms/index.js +3 -3
- package/dist/components/lukso-textarea/index.cjs +5 -5
- package/dist/components/lukso-textarea/index.js +4 -4
- package/dist/components/lukso-tooltip/index.cjs +3 -3
- package/dist/components/lukso-tooltip/index.js +3 -3
- package/dist/components/lukso-username/index.cjs +6 -6
- package/dist/components/lukso-username/index.js +6 -6
- package/dist/components/lukso-wizard/index.cjs +2 -2
- package/dist/components/lukso-wizard/index.js +2 -2
- package/dist/docs/Typography.stories.d.ts.map +1 -1
- package/dist/{index-QfURPzjM.cjs → index-1XmcSGot.cjs} +6 -6
- package/dist/{index-BvQ5tHy1.cjs → index-BhmMhjR9.cjs} +1 -1
- package/dist/{index-BR_I57SD.cjs → index-CJ6SmSwD.cjs} +4 -7
- package/dist/{index-g9LnjUjQ.js → index-CP2bxOlA.js} +3 -6
- package/dist/{index-DGEC4kLx.cjs → index-CgUaX5S2.cjs} +6 -6
- package/dist/index-DVrgVgp0.cjs +50 -0
- package/dist/{index-DI0bldib.js → index-Fy0gnibp.js} +1 -1
- package/dist/{index-CzJGpXul.js → index-Vj-gQHwJ.js} +5 -5
- package/dist/index-YQoO_Soq.js +41 -0
- package/dist/{index-bOsUG-kI.js → index-ldATebdp.js} +6 -6
- package/dist/index.cjs +6 -6
- package/dist/index.js +5 -5
- package/dist/{isAddress-7c5mkwjj.cjs → isAddress-DYM3mZP7.cjs} +1 -1
- package/dist/{isAddress-DyEmEF7O.js → isAddress-ZNhN82OL.js} +1 -1
- package/dist/{property-C7opy5D6.cjs → property-Df-2NJoK.cjs} +1 -1
- package/dist/{property-BohUfInC.js → property-Dsz7qIbu.js} +1 -1
- package/dist/shared/tailwind-element/index.cjs +1 -1
- package/dist/shared/tailwind-element/index.js +1 -1
- package/dist/{state-De1ciDiG.cjs → state-B2ymS5qU.cjs} +1 -1
- package/dist/{state-DJBtqv5G.js → state-CcocMKSJ.js} +1 -1
- package/dist/{style-map-BXvyZNeB.js → style-map-A9HLuIu5.js} +1 -1
- package/dist/{style-map-BjMzqR4L.cjs → style-map-CZiyZP71.cjs} +1 -1
- package/dist/styles/main.css +39 -0
- package/dist/styles/main.css.map +1 -1
- package/dist/{unsafe-html-Bs5tNJGm.js → unsafe-html-BTdQd0Aj.js} +1 -1
- package/dist/{unsafe-html-D9Z4CuNa.cjs → unsafe-html-CWvuBYgI.cjs} +1 -1
- package/dist/vitest.config.d.ts.map +1 -1
- package/dist/warning-2-97Si6IrM.cjs +7 -0
- package/dist/warning-2-BSM6W1Gm.js +3 -0
- package/dist/warning-2-BixGOAk9.js +3 -0
- package/dist/warning-2-BvOu0gS4.js +3 -0
- package/dist/warning-2-CZN1r-ol.cjs +7 -0
- package/dist/warning-2-CtlOo41U.js +3 -0
- package/dist/warning-2-Cv13JPGf.cjs +7 -0
- package/dist/warning-2-D2EzJ9aB.js +3 -0
- package/dist/warning-2-DMkszxQr.cjs +7 -0
- package/dist/warning-2-DdO_RDHZ.cjs +7 -0
- package/dist/warning-2-Dq9nReo1.cjs +7 -0
- package/dist/warning-2-DqcrFtKk.js +3 -0
- package/package.json +1 -1
- package/tools/__tests__/accessibility-checker.spec.d.ts +2 -0
- package/tools/__tests__/accessibility-checker.spec.d.ts.map +1 -0
- package/tools/accessibility-checker.d.ts +36 -0
- package/tools/accessibility-checker.d.ts.map +1 -0
- package/tools/index.cjs +32697 -1
- package/tools/index.d.ts +1 -0
- package/tools/index.d.ts.map +1 -1
- package/tools/index.js +32695 -2
- package/tools/sass/typography.scss +55 -1
- package/tools/styles/main.css +39 -0
- package/LICENSE +0 -21
- package/README.md +0 -146
- package/dist/cn-CNdKneQ1.cjs +0 -2578
- package/dist/cn-Cu9aP49j.js +0 -2575
- package/dist/index-Da-0kQ05.cjs +0 -50
- package/dist/index-wzuGnJOC.js +0 -41
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { T as TailwindStyledElement, E, x } from '../../index-
|
|
2
|
-
import { n, t } from '../../property-
|
|
3
|
-
import { r } from '../../state-
|
|
1
|
+
import { T as TailwindStyledElement, E, x } from '../../index-YQoO_Soq.js';
|
|
2
|
+
import { n, t } from '../../property-Dsz7qIbu.js';
|
|
3
|
+
import { r } from '../../state-CcocMKSJ.js';
|
|
4
4
|
import { e } from '../../query-CHb9Ft_d.js';
|
|
5
5
|
import { c as ce } from '../../index-B9iart53.js';
|
|
6
6
|
import '../../tailwind-config.js';
|
|
7
|
-
import { c as cn } from '../../
|
|
7
|
+
import { a as axe, c as cn } from '../../axe-BK9JSROP.js';
|
|
8
8
|
import '../lukso-textarea/index.js';
|
|
9
9
|
import '../lukso-markdown/index.js';
|
|
10
10
|
import '../lukso-switch/index.js';
|
|
@@ -15,6 +15,178 @@ import '../lukso-dropdown/index.js';
|
|
|
15
15
|
import '../lukso-dropdown-option/index.js';
|
|
16
16
|
import '../lukso-tooltip/index.js';
|
|
17
17
|
|
|
18
|
+
function mapAxeViolationToAccessibilityViolation(axeViolation) {
|
|
19
|
+
return {
|
|
20
|
+
id: axeViolation.id,
|
|
21
|
+
impact: axeViolation.impact,
|
|
22
|
+
description: axeViolation.description,
|
|
23
|
+
help: axeViolation.help,
|
|
24
|
+
helpUrl: axeViolation.helpUrl,
|
|
25
|
+
nodes: axeViolation.nodes?.map((node) => ({
|
|
26
|
+
html: node.html,
|
|
27
|
+
target: node.target,
|
|
28
|
+
failureSummary: node.failureSummary
|
|
29
|
+
})) || []
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
async function checkAccessibility(element) {
|
|
33
|
+
if (!element) {
|
|
34
|
+
return {
|
|
35
|
+
violations: [],
|
|
36
|
+
hasViolations: false,
|
|
37
|
+
violationCount: 0
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
try {
|
|
41
|
+
const results = await axe.run(element, {
|
|
42
|
+
resultTypes: ["violations"],
|
|
43
|
+
tags: ["wcag2a", "wcag2aa", "wcag21aa", "best-practice"],
|
|
44
|
+
rules: {
|
|
45
|
+
"color-contrast": { enabled: true },
|
|
46
|
+
"color-contrast-enhanced": { enabled: true },
|
|
47
|
+
"image-alt": { enabled: true },
|
|
48
|
+
"button-name": { enabled: true },
|
|
49
|
+
"link-name": { enabled: true },
|
|
50
|
+
label: { enabled: true },
|
|
51
|
+
"form-field-multiple-labels": { enabled: true }
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
const violations = results.violations.map(
|
|
55
|
+
mapAxeViolationToAccessibilityViolation
|
|
56
|
+
);
|
|
57
|
+
return {
|
|
58
|
+
violations,
|
|
59
|
+
hasViolations: violations.length > 0,
|
|
60
|
+
violationCount: violations.length
|
|
61
|
+
};
|
|
62
|
+
} catch (error) {
|
|
63
|
+
console.warn("Accessibility checking failed:", error);
|
|
64
|
+
return {
|
|
65
|
+
violations: [],
|
|
66
|
+
hasViolations: false,
|
|
67
|
+
violationCount: 0
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const getViolationSummary = (violations) => {
|
|
72
|
+
const summary = {
|
|
73
|
+
critical: 0,
|
|
74
|
+
serious: 0,
|
|
75
|
+
moderate: 0,
|
|
76
|
+
minor: 0,
|
|
77
|
+
total: violations.length
|
|
78
|
+
};
|
|
79
|
+
violations.forEach((violation) => {
|
|
80
|
+
if (violation.impact) {
|
|
81
|
+
summary[violation.impact]++;
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
return summary;
|
|
85
|
+
};
|
|
86
|
+
function getImpactStyling(impact) {
|
|
87
|
+
switch (impact) {
|
|
88
|
+
case "critical":
|
|
89
|
+
return {
|
|
90
|
+
colorClass: "text-red-600",
|
|
91
|
+
borderClass: "border-red-600",
|
|
92
|
+
label: "Critical"
|
|
93
|
+
};
|
|
94
|
+
case "serious":
|
|
95
|
+
return {
|
|
96
|
+
colorClass: "text-orange-600",
|
|
97
|
+
borderClass: "border-orange-600",
|
|
98
|
+
label: "Serious"
|
|
99
|
+
};
|
|
100
|
+
case "moderate":
|
|
101
|
+
return {
|
|
102
|
+
colorClass: "text-yellow-600",
|
|
103
|
+
borderClass: "border-yellow-600",
|
|
104
|
+
label: "Moderate"
|
|
105
|
+
};
|
|
106
|
+
case "minor":
|
|
107
|
+
return {
|
|
108
|
+
colorClass: "text-green-600",
|
|
109
|
+
borderClass: "border-green-600",
|
|
110
|
+
label: "Minor"
|
|
111
|
+
};
|
|
112
|
+
default:
|
|
113
|
+
return {
|
|
114
|
+
colorClass: "text-gray-500",
|
|
115
|
+
borderClass: "border-gray-500",
|
|
116
|
+
label: "Info"
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
const formatViolationsForTooltip = (violations) => {
|
|
121
|
+
if (violations.length === 0) {
|
|
122
|
+
return `
|
|
123
|
+
<div class="text-center text-green-600 font-semibold">
|
|
124
|
+
No accessibility violations found!
|
|
125
|
+
</div>
|
|
126
|
+
`;
|
|
127
|
+
}
|
|
128
|
+
const summary = getViolationSummary(violations);
|
|
129
|
+
let tooltip = `
|
|
130
|
+
<div class="max-w-sm leading-relaxed p-1">
|
|
131
|
+
<div class="font-bold text-sm mb-1">
|
|
132
|
+
Accessibility Issues Found
|
|
133
|
+
</div>
|
|
134
|
+
`;
|
|
135
|
+
tooltip += `
|
|
136
|
+
<div class="mb-2">
|
|
137
|
+
<div class="flex gap-3 flex-wrap">
|
|
138
|
+
`;
|
|
139
|
+
if (summary.critical > 0) {
|
|
140
|
+
tooltip += `<span class="text-red-600">🚨 ${summary.critical} Critical</span>`;
|
|
141
|
+
}
|
|
142
|
+
if (summary.serious > 0) {
|
|
143
|
+
tooltip += `<span class="text-orange-600">⚠️ ${summary.serious} Serious</span>`;
|
|
144
|
+
}
|
|
145
|
+
if (summary.moderate > 0) {
|
|
146
|
+
tooltip += `<span class="text-yellow-600">⚡ ${summary.moderate} Moderate</span>`;
|
|
147
|
+
}
|
|
148
|
+
if (summary.minor > 0) {
|
|
149
|
+
tooltip += `<span class="text-green-600">💡 ${summary.minor} Minor</span>`;
|
|
150
|
+
}
|
|
151
|
+
tooltip += `
|
|
152
|
+
</div>
|
|
153
|
+
</div>
|
|
154
|
+
`;
|
|
155
|
+
const sortedViolations = violations.sort((a, b) => {
|
|
156
|
+
const impactOrder = { critical: 0, serious: 1, moderate: 2, minor: 3 };
|
|
157
|
+
const aOrder = impactOrder[a.impact] ?? 4;
|
|
158
|
+
const bOrder = impactOrder[b.impact] ?? 4;
|
|
159
|
+
return aOrder - bOrder;
|
|
160
|
+
});
|
|
161
|
+
tooltip += `<div class="space-y-3">`;
|
|
162
|
+
sortedViolations.forEach((violation) => {
|
|
163
|
+
const impactStyling = getImpactStyling(violation.impact);
|
|
164
|
+
tooltip += `
|
|
165
|
+
<div class="p-2 bg-white border-l-4 ${impactStyling.borderClass} rounded">
|
|
166
|
+
<div class="flex items-center gap-1.5 mb-1.5">
|
|
167
|
+
<strong class="text-gray-800 text-xs leading-tight">${violation.help}</strong>
|
|
168
|
+
</div>
|
|
169
|
+
|
|
170
|
+
<div class="flex items-center gap-1.5 mb-1">
|
|
171
|
+
<span class="text-xs ${impactStyling.colorClass} font-semibold">
|
|
172
|
+
${impactStyling.label}
|
|
173
|
+
</span>
|
|
174
|
+
<span class="text-xs text-gray-400">•</span>
|
|
175
|
+
<span class="text-xs text-gray-500">
|
|
176
|
+
${violation.nodes?.length || 0} element${(violation.nodes?.length || 0) !== 1 ? "s" : ""} affected
|
|
177
|
+
</span>
|
|
178
|
+
</div>
|
|
179
|
+
</div>
|
|
180
|
+
`;
|
|
181
|
+
});
|
|
182
|
+
tooltip += `
|
|
183
|
+
</div>
|
|
184
|
+
<div class="text-10 text-gray-500 mt-2">Check out the <a target="_blank" class="underline" href="https://www.w3.org/TR/WCAG21/">WCAG</a> guidelines why this is important.</div>
|
|
185
|
+
</div>
|
|
186
|
+
`;
|
|
187
|
+
return tooltip.trim();
|
|
188
|
+
};
|
|
189
|
+
|
|
18
190
|
const style = ":host {\n display: flex\n}";
|
|
19
191
|
|
|
20
192
|
var __defProp = Object.defineProperty;
|
|
@@ -27,6 +199,7 @@ var __decorateClass = (decorators, target, key, kind) => {
|
|
|
27
199
|
if (kind && result) __defProp(target, key, result);
|
|
28
200
|
return result;
|
|
29
201
|
};
|
|
202
|
+
const DEFAULT_PREVIEW_BACKGROUND_COLOR = "#f9f9f9";
|
|
30
203
|
let LuksoMarkdownEditor = class extends TailwindStyledElement(style) {
|
|
31
204
|
constructor() {
|
|
32
205
|
super(...arguments);
|
|
@@ -44,13 +217,16 @@ let LuksoMarkdownEditor = class extends TailwindStyledElement(style) {
|
|
|
44
217
|
this.isPreview = false;
|
|
45
218
|
this.rows = 6;
|
|
46
219
|
this.placeholder = "";
|
|
220
|
+
this.previewBackgroundColor = DEFAULT_PREVIEW_BACKGROUND_COLOR;
|
|
47
221
|
this.savedSelectionForPreview = null;
|
|
48
222
|
this.isHeadingDropdownOpen = false;
|
|
49
223
|
this.isColorDropdownOpen = false;
|
|
50
224
|
this.isListDropdownOpen = false;
|
|
225
|
+
this.isAlignmentDropdownOpen = false;
|
|
51
226
|
this.headingTriggerId = "heading-dropdown-trigger";
|
|
52
227
|
this.colorTriggerId = "color-dropdown-trigger";
|
|
53
228
|
this.listTriggerId = "list-dropdown-trigger";
|
|
229
|
+
this.alignmentTriggerId = "alignment-dropdown-trigger";
|
|
54
230
|
this.currentSelection = { start: 0, end: 0 };
|
|
55
231
|
this.savedSelection = null;
|
|
56
232
|
this.defaultColor = "#374151";
|
|
@@ -61,10 +237,12 @@ let LuksoMarkdownEditor = class extends TailwindStyledElement(style) {
|
|
|
61
237
|
h1: false,
|
|
62
238
|
h2: false,
|
|
63
239
|
h3: false,
|
|
240
|
+
h4: false,
|
|
64
241
|
color: false,
|
|
65
242
|
activeColor: this.defaultColor,
|
|
66
243
|
unorderedList: false,
|
|
67
|
-
orderedList: false
|
|
244
|
+
orderedList: false,
|
|
245
|
+
alignment: "left"
|
|
68
246
|
};
|
|
69
247
|
// Undo/Redo state
|
|
70
248
|
this.undoStack = [];
|
|
@@ -74,6 +252,10 @@ let LuksoMarkdownEditor = class extends TailwindStyledElement(style) {
|
|
|
74
252
|
this.undoTimeout = null;
|
|
75
253
|
this.MAX_UNDO_STACK_SIZE = 100;
|
|
76
254
|
this.UNDO_SAVE_DELAY = 500;
|
|
255
|
+
this.accessibilityViolations = [];
|
|
256
|
+
this.hasAccessibilityViolations = false;
|
|
257
|
+
this.accessibilityCheckTimeout = null;
|
|
258
|
+
this.ACCESSIBILITY_CHECK_DELAY = 1e3;
|
|
77
259
|
this.handleOutsideClick = (event) => {
|
|
78
260
|
const target = event.target;
|
|
79
261
|
const isInsideThisComponent = this.contains(target) || this.shadowRoot?.contains(target);
|
|
@@ -87,14 +269,19 @@ let LuksoMarkdownEditor = class extends TailwindStyledElement(style) {
|
|
|
87
269
|
if (this.isListDropdownOpen) {
|
|
88
270
|
this.isListDropdownOpen = false;
|
|
89
271
|
}
|
|
272
|
+
if (this.isAlignmentDropdownOpen) {
|
|
273
|
+
this.isAlignmentDropdownOpen = false;
|
|
274
|
+
}
|
|
90
275
|
return;
|
|
91
276
|
}
|
|
92
277
|
const isInsideHeadingDropdown = this.shadowRoot?.getElementById("headingDropdown")?.contains(target);
|
|
93
278
|
const isInsideColorDropdown = this.shadowRoot?.getElementById("colorDropdown")?.contains(target);
|
|
94
279
|
const isInsideListDropdown = this.shadowRoot?.getElementById("listDropdown")?.contains(target);
|
|
280
|
+
const isInsideAlignmentDropdown = this.shadowRoot?.getElementById("alignmentDropdown")?.contains(target);
|
|
95
281
|
const isHeadingTrigger = this.shadowRoot?.getElementById(this.headingTriggerId)?.contains(target);
|
|
96
282
|
const isColorTrigger = this.shadowRoot?.getElementById(this.colorTriggerId)?.contains(target);
|
|
97
283
|
const isListTrigger = this.shadowRoot?.getElementById(this.listTriggerId)?.contains(target);
|
|
284
|
+
const isAlignmentTrigger = this.shadowRoot?.getElementById(this.alignmentTriggerId)?.contains(target);
|
|
98
285
|
if (!isInsideHeadingDropdown && !isHeadingTrigger && this.isHeadingDropdownOpen) {
|
|
99
286
|
this.isHeadingDropdownOpen = false;
|
|
100
287
|
}
|
|
@@ -104,18 +291,22 @@ let LuksoMarkdownEditor = class extends TailwindStyledElement(style) {
|
|
|
104
291
|
if (!isInsideListDropdown && !isListTrigger && this.isListDropdownOpen) {
|
|
105
292
|
this.isListDropdownOpen = false;
|
|
106
293
|
}
|
|
294
|
+
if (!isInsideAlignmentDropdown && !isAlignmentTrigger && this.isAlignmentDropdownOpen) {
|
|
295
|
+
this.isAlignmentDropdownOpen = false;
|
|
296
|
+
}
|
|
107
297
|
};
|
|
108
298
|
this.styles = ce({
|
|
109
299
|
slots: {
|
|
110
300
|
wrapper: "w-[inherit] grid gap-3",
|
|
111
301
|
header: "flex items-center justify-between gap-2 border border-neutral-90 rounded-12 px-3 py-2 bg-neutral-100",
|
|
112
302
|
toolbar: "flex flex-wrap items-center gap-1",
|
|
113
|
-
area: "",
|
|
303
|
+
area: "relative",
|
|
114
304
|
editor: "",
|
|
115
|
-
preview: "p-3",
|
|
305
|
+
preview: "p-3 border border-neutral-90 rounded-12 min-h-[158px]",
|
|
116
306
|
colorMenu: "relative",
|
|
117
307
|
headingMenu: "relative",
|
|
118
308
|
listMenu: "relative",
|
|
309
|
+
alignmentMenu: "relative",
|
|
119
310
|
label: "heading-inter-14-bold text-neutral-20",
|
|
120
311
|
description: "paragraph-inter-12-regular text-neutral-20",
|
|
121
312
|
divider: "w-[1px] h-4 bg-neutral-90"
|
|
@@ -229,7 +420,7 @@ let LuksoMarkdownEditor = class extends TailwindStyledElement(style) {
|
|
|
229
420
|
this.scheduleUndoStateSave();
|
|
230
421
|
}
|
|
231
422
|
this.value = newValue;
|
|
232
|
-
this.
|
|
423
|
+
this.emitChangeAndRefresh(event);
|
|
233
424
|
};
|
|
234
425
|
/**
|
|
235
426
|
* Textarea keyup handler.
|
|
@@ -280,7 +471,7 @@ let LuksoMarkdownEditor = class extends TailwindStyledElement(style) {
|
|
|
280
471
|
}
|
|
281
472
|
dispatchChange(event) {
|
|
282
473
|
this.updateComplete.then(() => {
|
|
283
|
-
const changeEvent = new CustomEvent("on-change", {
|
|
474
|
+
const changeEvent = new CustomEvent("on-markdown-change", {
|
|
284
475
|
detail: { value: this.value, event },
|
|
285
476
|
bubbles: false,
|
|
286
477
|
composed: true
|
|
@@ -288,6 +479,68 @@ let LuksoMarkdownEditor = class extends TailwindStyledElement(style) {
|
|
|
288
479
|
this.dispatchEvent(changeEvent);
|
|
289
480
|
});
|
|
290
481
|
}
|
|
482
|
+
/**
|
|
483
|
+
* Check accessibility violations on the live preview content
|
|
484
|
+
*/
|
|
485
|
+
async performAccessibilityCheck() {
|
|
486
|
+
if (!this.value.trim()) {
|
|
487
|
+
this.accessibilityViolations = [];
|
|
488
|
+
this.hasAccessibilityViolations = false;
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
try {
|
|
492
|
+
if (this.isPreview) {
|
|
493
|
+
const markdownEl = this.shadowRoot?.querySelector("lukso-markdown");
|
|
494
|
+
if (markdownEl) {
|
|
495
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
496
|
+
const sanitizeEl = markdownEl.shadowRoot?.querySelector("lukso-sanitize");
|
|
497
|
+
const proseDiv = sanitizeEl?.shadowRoot?.querySelector(
|
|
498
|
+
"div.prose"
|
|
499
|
+
);
|
|
500
|
+
if (proseDiv) {
|
|
501
|
+
const result = await checkAccessibility(proseDiv);
|
|
502
|
+
this.accessibilityViolations = result.violations;
|
|
503
|
+
this.hasAccessibilityViolations = result.hasViolations;
|
|
504
|
+
} else {
|
|
505
|
+
this.accessibilityViolations = [];
|
|
506
|
+
this.hasAccessibilityViolations = false;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
} else {
|
|
510
|
+
this.accessibilityViolations = [];
|
|
511
|
+
this.hasAccessibilityViolations = false;
|
|
512
|
+
}
|
|
513
|
+
} catch (error) {
|
|
514
|
+
console.warn("Accessibility checking failed:", error);
|
|
515
|
+
this.accessibilityViolations = [];
|
|
516
|
+
this.hasAccessibilityViolations = false;
|
|
517
|
+
}
|
|
518
|
+
this.requestUpdate();
|
|
519
|
+
}
|
|
520
|
+
/**
|
|
521
|
+
* Schedule accessibility check with debouncing
|
|
522
|
+
*/
|
|
523
|
+
scheduleAccessibilityCheck() {
|
|
524
|
+
if (this.accessibilityCheckTimeout) {
|
|
525
|
+
clearTimeout(this.accessibilityCheckTimeout);
|
|
526
|
+
}
|
|
527
|
+
this.accessibilityCheckTimeout = window.setTimeout(() => {
|
|
528
|
+
this.performAccessibilityCheck();
|
|
529
|
+
this.accessibilityCheckTimeout = null;
|
|
530
|
+
}, this.ACCESSIBILITY_CHECK_DELAY);
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* Unified helper that ensures both active format state and change events are properly
|
|
534
|
+
* emitted after any value mutation. This replaces the scattered updateActiveFormats()
|
|
535
|
+
* and dispatchChange() calls throughout the codebase.
|
|
536
|
+
*
|
|
537
|
+
* @param event - Optional event that triggered the change
|
|
538
|
+
*/
|
|
539
|
+
emitChangeAndRefresh(event) {
|
|
540
|
+
this.updateActiveFormats();
|
|
541
|
+
this.dispatchChange(event);
|
|
542
|
+
this.scheduleAccessibilityCheck();
|
|
543
|
+
}
|
|
291
544
|
/**
|
|
292
545
|
* Utility to perform an operation with the current textarea selection.
|
|
293
546
|
*
|
|
@@ -328,7 +581,7 @@ let LuksoMarkdownEditor = class extends TailwindStyledElement(style) {
|
|
|
328
581
|
/**
|
|
329
582
|
* Apply or toggle heading formatting for the current line(s).
|
|
330
583
|
*
|
|
331
|
-
* @param level - 0 to remove heading, 1-
|
|
584
|
+
* @param level - 0 to remove heading, 1-4 for heading levels
|
|
332
585
|
*/
|
|
333
586
|
applyHeading(level) {
|
|
334
587
|
if (this.isReadonly || this.isDisabled) return;
|
|
@@ -374,9 +627,8 @@ let LuksoMarkdownEditor = class extends TailwindStyledElement(style) {
|
|
|
374
627
|
}
|
|
375
628
|
requestAnimationFrame(() => {
|
|
376
629
|
textarea.setSelectionRange(cursorPosition, cursorPosition);
|
|
377
|
-
this.
|
|
630
|
+
this.emitChangeAndRefresh();
|
|
378
631
|
});
|
|
379
|
-
this.dispatchChange();
|
|
380
632
|
});
|
|
381
633
|
}
|
|
382
634
|
/**
|
|
@@ -506,9 +758,8 @@ let LuksoMarkdownEditor = class extends TailwindStyledElement(style) {
|
|
|
506
758
|
const cursorPosition = before.length + (firstLineEnd === -1 ? transformed.length : firstLineEnd);
|
|
507
759
|
requestAnimationFrame(() => {
|
|
508
760
|
textarea.setSelectionRange(cursorPosition, cursorPosition);
|
|
509
|
-
this.
|
|
761
|
+
this.emitChangeAndRefresh();
|
|
510
762
|
});
|
|
511
|
-
this.dispatchChange();
|
|
512
763
|
});
|
|
513
764
|
}
|
|
514
765
|
/**
|
|
@@ -519,6 +770,155 @@ let LuksoMarkdownEditor = class extends TailwindStyledElement(style) {
|
|
|
519
770
|
if (this.activeFormats.orderedList) return "ordered";
|
|
520
771
|
return "none";
|
|
521
772
|
}
|
|
773
|
+
/**
|
|
774
|
+
* Get the current alignment icon name based on activeFormats.
|
|
775
|
+
*/
|
|
776
|
+
getAlignmentIcon() {
|
|
777
|
+
switch (this.activeFormats.alignment) {
|
|
778
|
+
case "center":
|
|
779
|
+
return "textalign-center";
|
|
780
|
+
case "right":
|
|
781
|
+
return "textalign-right";
|
|
782
|
+
default:
|
|
783
|
+
return "textalign-left";
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
/**
|
|
787
|
+
* Apply or toggle text alignment for the current line(s).
|
|
788
|
+
*
|
|
789
|
+
* @param alignment - 'left', 'center', or 'right'
|
|
790
|
+
*/
|
|
791
|
+
applyAlignment(alignment) {
|
|
792
|
+
if (this.isReadonly || this.isDisabled) return;
|
|
793
|
+
this.saveUndoStateBeforeChange();
|
|
794
|
+
this.withSelection((textarea, start, end, value) => {
|
|
795
|
+
const lineStart = value.lastIndexOf("\n", start - 1) + 1;
|
|
796
|
+
let lineEnd = value.indexOf("\n", end);
|
|
797
|
+
if (lineEnd === -1) lineEnd = value.length;
|
|
798
|
+
const before = value.slice(0, lineStart);
|
|
799
|
+
const selected = value.slice(lineStart, lineEnd);
|
|
800
|
+
const after = value.slice(lineEnd);
|
|
801
|
+
let transformed;
|
|
802
|
+
const currentAlignmentRegex = /<div style="text-align: (left|center|right);">(.*?)<\/div>/s;
|
|
803
|
+
const existingMatch = selected.match(currentAlignmentRegex);
|
|
804
|
+
if (existingMatch || this.hasNestedAlignment(selected)) {
|
|
805
|
+
if (existingMatch) {
|
|
806
|
+
const existingAlignment = existingMatch[1];
|
|
807
|
+
const innerContent = existingMatch[2];
|
|
808
|
+
if (existingAlignment === alignment) {
|
|
809
|
+
transformed = innerContent;
|
|
810
|
+
} else {
|
|
811
|
+
transformed = `<div style="text-align: ${alignment};">${innerContent}</div>`;
|
|
812
|
+
}
|
|
813
|
+
} else {
|
|
814
|
+
if (this.getNestedAlignment(selected) === alignment) {
|
|
815
|
+
transformed = this.removeNestedAlignment(selected);
|
|
816
|
+
} else {
|
|
817
|
+
transformed = this.replaceNestedAlignment(selected, alignment);
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
} else {
|
|
821
|
+
if (alignment === "left") {
|
|
822
|
+
transformed = selected;
|
|
823
|
+
} else {
|
|
824
|
+
transformed = this.wrapContentWithAlignment(selected, alignment);
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
this.value = before + transformed + after;
|
|
828
|
+
textarea.value = before + transformed + after;
|
|
829
|
+
const cursorPosition = before.length + transformed.length;
|
|
830
|
+
requestAnimationFrame(() => {
|
|
831
|
+
textarea.setSelectionRange(cursorPosition, cursorPosition);
|
|
832
|
+
this.emitChangeAndRefresh();
|
|
833
|
+
});
|
|
834
|
+
});
|
|
835
|
+
}
|
|
836
|
+
/**
|
|
837
|
+
* Check if content has nested alignment divs inside formatting markers.
|
|
838
|
+
*/
|
|
839
|
+
hasNestedAlignment(content) {
|
|
840
|
+
const alignmentRegex = /<div style="text-align: (left|center|right);">/;
|
|
841
|
+
return alignmentRegex.test(content);
|
|
842
|
+
}
|
|
843
|
+
/**
|
|
844
|
+
* Get the alignment from nested alignment divs.
|
|
845
|
+
*/
|
|
846
|
+
getNestedAlignment(content) {
|
|
847
|
+
const alignmentMatch = content.match(
|
|
848
|
+
/<div style="text-align: (left|center|right);">/
|
|
849
|
+
);
|
|
850
|
+
return alignmentMatch ? alignmentMatch[1] : null;
|
|
851
|
+
}
|
|
852
|
+
/**
|
|
853
|
+
* Remove nested alignment divs from content.
|
|
854
|
+
*/
|
|
855
|
+
removeNestedAlignment(content) {
|
|
856
|
+
return content.replace(
|
|
857
|
+
/<div style="text-align: (left|center|right);">([^<]*?)<\/div>/g,
|
|
858
|
+
"$2"
|
|
859
|
+
);
|
|
860
|
+
}
|
|
861
|
+
/**
|
|
862
|
+
* Replace nested alignment with a new alignment.
|
|
863
|
+
*/
|
|
864
|
+
replaceNestedAlignment(content, newAlignment) {
|
|
865
|
+
return content.replace(
|
|
866
|
+
/<div style="text-align: (left|center|right);">([^<]*?)<\/div>/g,
|
|
867
|
+
`<div style="text-align: ${newAlignment};">$2</div>`
|
|
868
|
+
);
|
|
869
|
+
}
|
|
870
|
+
/**
|
|
871
|
+
* Wrap content with alignment div, ensuring proper nesting inside formatting markers.
|
|
872
|
+
* Examples:
|
|
873
|
+
* - **text** becomes **<div style="text-align: center;">text</div>**
|
|
874
|
+
* - *text* becomes *<div style="text-align: center;">text</div>*
|
|
875
|
+
* - # text becomes # <div style="text-align: center;">text</div>
|
|
876
|
+
*
|
|
877
|
+
* @param content - the content to wrap
|
|
878
|
+
* @param alignment - 'left', 'center' or 'right'
|
|
879
|
+
*/
|
|
880
|
+
wrapContentWithAlignment(content, alignment) {
|
|
881
|
+
const alignmentDiv = (innerContent) => `<div style="text-align: ${alignment};">${innerContent}</div>`;
|
|
882
|
+
const headingMatch = content.match(/^(#{1,6}\s+)(.*)$/);
|
|
883
|
+
if (headingMatch) {
|
|
884
|
+
const headingPrefix = headingMatch[1];
|
|
885
|
+
const headingText = headingMatch[2];
|
|
886
|
+
return headingPrefix + alignmentDiv(headingText);
|
|
887
|
+
}
|
|
888
|
+
const boldMatch = content.match(/^(\*\*)(.+?)(\*\*)$/s);
|
|
889
|
+
if (boldMatch) {
|
|
890
|
+
const innerText = boldMatch[2];
|
|
891
|
+
return `**${alignmentDiv(innerText)}**`;
|
|
892
|
+
}
|
|
893
|
+
const italicMatch = content.match(/^(\*)(.+?)(\*)$/s);
|
|
894
|
+
if (italicMatch) {
|
|
895
|
+
const innerText = italicMatch[2];
|
|
896
|
+
return `*${alignmentDiv(innerText)}*`;
|
|
897
|
+
}
|
|
898
|
+
const linkMatch = content.match(/^(\[)(.+?)(\]\([^)]+\))$/s);
|
|
899
|
+
if (linkMatch) {
|
|
900
|
+
const linkStart = linkMatch[1];
|
|
901
|
+
const linkText = linkMatch[2];
|
|
902
|
+
const linkEnd = linkMatch[3];
|
|
903
|
+
return linkStart + alignmentDiv(linkText) + linkEnd;
|
|
904
|
+
}
|
|
905
|
+
const colorMatch = content.match(
|
|
906
|
+
/^(<span style="color: [^"]+;">)(.+?)(<\/span>)$/s
|
|
907
|
+
);
|
|
908
|
+
if (colorMatch) {
|
|
909
|
+
const colorStart = colorMatch[1];
|
|
910
|
+
const colorText = colorMatch[2];
|
|
911
|
+
const colorEnd = colorMatch[3];
|
|
912
|
+
return colorStart + alignmentDiv(colorText) + colorEnd;
|
|
913
|
+
}
|
|
914
|
+
const listMatch = content.match(/^(\s*(?:[-*+]|\d+\.)\s+)(.*)$/);
|
|
915
|
+
if (listMatch) {
|
|
916
|
+
const listPrefix = listMatch[1];
|
|
917
|
+
const listText = listMatch[2];
|
|
918
|
+
return listPrefix + alignmentDiv(listText);
|
|
919
|
+
}
|
|
920
|
+
return alignmentDiv(content);
|
|
921
|
+
}
|
|
522
922
|
/**
|
|
523
923
|
* Toggle inline formatting by wrapping/unwrapping selection or current word.
|
|
524
924
|
*
|
|
@@ -545,9 +945,8 @@ let LuksoMarkdownEditor = class extends TailwindStyledElement(style) {
|
|
|
545
945
|
const selEnd2 = selStart2 + selected.length;
|
|
546
946
|
requestAnimationFrame(() => {
|
|
547
947
|
textarea.setSelectionRange(selStart2, selEnd2);
|
|
548
|
-
this.
|
|
948
|
+
this.emitChangeAndRefresh();
|
|
549
949
|
});
|
|
550
|
-
this.dispatchChange();
|
|
551
950
|
return;
|
|
552
951
|
}
|
|
553
952
|
const innerWrapped = selected.startsWith(wrapper) && selected.endsWith(wrapper);
|
|
@@ -562,9 +961,8 @@ let LuksoMarkdownEditor = class extends TailwindStyledElement(style) {
|
|
|
562
961
|
const selEnd2 = selStart2 + selected.length;
|
|
563
962
|
requestAnimationFrame(() => {
|
|
564
963
|
textarea.setSelectionRange(selStart2, selEnd2);
|
|
565
|
-
this.
|
|
964
|
+
this.emitChangeAndRefresh();
|
|
566
965
|
});
|
|
567
|
-
this.dispatchChange();
|
|
568
966
|
return;
|
|
569
967
|
}
|
|
570
968
|
const wrapped = `${wrapper}${selected || ""}${wrapper}`;
|
|
@@ -574,9 +972,8 @@ let LuksoMarkdownEditor = class extends TailwindStyledElement(style) {
|
|
|
574
972
|
const selEnd = selStart + (selected ? selected.length : 0);
|
|
575
973
|
requestAnimationFrame(() => {
|
|
576
974
|
textarea.setSelectionRange(selStart, selEnd);
|
|
577
|
-
this.
|
|
975
|
+
this.emitChangeAndRefresh();
|
|
578
976
|
});
|
|
579
|
-
this.dispatchChange();
|
|
580
977
|
});
|
|
581
978
|
}
|
|
582
979
|
/**
|
|
@@ -589,6 +986,11 @@ let LuksoMarkdownEditor = class extends TailwindStyledElement(style) {
|
|
|
589
986
|
this.enterPreview();
|
|
590
987
|
}
|
|
591
988
|
this.isPreview = !this.isPreview;
|
|
989
|
+
if (this.isPreview) {
|
|
990
|
+
requestAnimationFrame(() => {
|
|
991
|
+
this.scheduleAccessibilityCheck();
|
|
992
|
+
});
|
|
993
|
+
}
|
|
592
994
|
}
|
|
593
995
|
/**
|
|
594
996
|
* Enter preview mode - save current state and remove keyboard listeners from textarea
|
|
@@ -647,9 +1049,8 @@ let LuksoMarkdownEditor = class extends TailwindStyledElement(style) {
|
|
|
647
1049
|
const newCursor = leftBracket2 + textOnly.length;
|
|
648
1050
|
requestAnimationFrame(() => {
|
|
649
1051
|
textarea.setSelectionRange(newCursor, newCursor);
|
|
650
|
-
this.
|
|
1052
|
+
this.emitChangeAndRefresh();
|
|
651
1053
|
});
|
|
652
|
-
this.dispatchChange();
|
|
653
1054
|
return;
|
|
654
1055
|
}
|
|
655
1056
|
}
|
|
@@ -669,9 +1070,8 @@ let LuksoMarkdownEditor = class extends TailwindStyledElement(style) {
|
|
|
669
1070
|
const newEnd = newStart + textOnly.length;
|
|
670
1071
|
requestAnimationFrame(() => {
|
|
671
1072
|
textarea.setSelectionRange(newStart, newEnd);
|
|
672
|
-
this.
|
|
1073
|
+
this.emitChangeAndRefresh();
|
|
673
1074
|
});
|
|
674
|
-
this.dispatchChange();
|
|
675
1075
|
return;
|
|
676
1076
|
}
|
|
677
1077
|
const leftBracket = value.lastIndexOf("[", s);
|
|
@@ -688,9 +1088,8 @@ let LuksoMarkdownEditor = class extends TailwindStyledElement(style) {
|
|
|
688
1088
|
const newCursor = leftBracket + textOnly.length;
|
|
689
1089
|
requestAnimationFrame(() => {
|
|
690
1090
|
textarea.setSelectionRange(newCursor, newCursor);
|
|
691
|
-
this.
|
|
1091
|
+
this.emitChangeAndRefresh();
|
|
692
1092
|
});
|
|
693
|
-
this.dispatchChange();
|
|
694
1093
|
return;
|
|
695
1094
|
}
|
|
696
1095
|
}
|
|
@@ -702,9 +1101,8 @@ let LuksoMarkdownEditor = class extends TailwindStyledElement(style) {
|
|
|
702
1101
|
requestAnimationFrame(() => {
|
|
703
1102
|
textarea.focus();
|
|
704
1103
|
textarea.setSelectionRange(cursorPosition, cursorPosition);
|
|
705
|
-
this.
|
|
1104
|
+
this.emitChangeAndRefresh();
|
|
706
1105
|
});
|
|
707
|
-
this.dispatchChange();
|
|
708
1106
|
});
|
|
709
1107
|
}
|
|
710
1108
|
/**
|
|
@@ -752,6 +1150,23 @@ let LuksoMarkdownEditor = class extends TailwindStyledElement(style) {
|
|
|
752
1150
|
);
|
|
753
1151
|
activeColor = beforeColorMatch?.[1] || selectedColorMatch?.[1] || "";
|
|
754
1152
|
}
|
|
1153
|
+
const alignmentRegex = /<div style="text-align: (left|center|right);">/;
|
|
1154
|
+
let alignment = "left";
|
|
1155
|
+
const alignmentMatch = currentLine.match(alignmentRegex);
|
|
1156
|
+
if (alignmentMatch) {
|
|
1157
|
+
alignment = alignmentMatch[1];
|
|
1158
|
+
} else {
|
|
1159
|
+
const beforeCurrentLine = this.value.slice(0, lineStart);
|
|
1160
|
+
const alignmentStartMatch = beforeCurrentLine.match(
|
|
1161
|
+
/.*<div style="text-align: (left|center|right);">[^<]*$/s
|
|
1162
|
+
);
|
|
1163
|
+
if (alignmentStartMatch) {
|
|
1164
|
+
const afterCurrentLine = this.value.slice(lineEnd);
|
|
1165
|
+
if (afterCurrentLine.includes("</div>")) {
|
|
1166
|
+
alignment = alignmentStartMatch[1];
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
755
1170
|
this.activeFormats = {
|
|
756
1171
|
bold: hasBoldWrap,
|
|
757
1172
|
italic: hasItalicWrap,
|
|
@@ -759,10 +1174,12 @@ let LuksoMarkdownEditor = class extends TailwindStyledElement(style) {
|
|
|
759
1174
|
h1: headingLevel === 1,
|
|
760
1175
|
h2: headingLevel === 2,
|
|
761
1176
|
h3: headingLevel === 3,
|
|
1177
|
+
h4: headingLevel === 4,
|
|
762
1178
|
color: hasColorWrap,
|
|
763
1179
|
activeColor,
|
|
764
1180
|
unorderedList: hasUnorderedList,
|
|
765
|
-
orderedList: hasOrderedList
|
|
1181
|
+
orderedList: hasOrderedList,
|
|
1182
|
+
alignment
|
|
766
1183
|
};
|
|
767
1184
|
}
|
|
768
1185
|
/**
|
|
@@ -880,9 +1297,8 @@ let LuksoMarkdownEditor = class extends TailwindStyledElement(style) {
|
|
|
880
1297
|
}
|
|
881
1298
|
requestAnimationFrame(() => {
|
|
882
1299
|
textarea.setSelectionRange(selStart2, selEnd2);
|
|
883
|
-
this.
|
|
1300
|
+
this.emitChangeAndRefresh();
|
|
884
1301
|
});
|
|
885
|
-
this.dispatchChange();
|
|
886
1302
|
return;
|
|
887
1303
|
}
|
|
888
1304
|
const colorRegex = /^<span style="color: ([^"]+)">(.*)<\/span>$/s;
|
|
@@ -901,9 +1317,8 @@ let LuksoMarkdownEditor = class extends TailwindStyledElement(style) {
|
|
|
901
1317
|
const selEnd2 = selStart2 + (existingColor === color ? innerText.length : innerText.length);
|
|
902
1318
|
requestAnimationFrame(() => {
|
|
903
1319
|
textarea.setSelectionRange(selStart2, selEnd2);
|
|
904
|
-
this.
|
|
1320
|
+
this.emitChangeAndRefresh();
|
|
905
1321
|
});
|
|
906
|
-
this.dispatchChange();
|
|
907
1322
|
return;
|
|
908
1323
|
}
|
|
909
1324
|
const newColorTagOpen = `<span style="color: ${color}">`;
|
|
@@ -914,9 +1329,8 @@ let LuksoMarkdownEditor = class extends TailwindStyledElement(style) {
|
|
|
914
1329
|
const selEnd = selStart + (selected ? selected.length : 4);
|
|
915
1330
|
requestAnimationFrame(() => {
|
|
916
1331
|
textarea.setSelectionRange(selStart, selEnd);
|
|
917
|
-
this.
|
|
1332
|
+
this.emitChangeAndRefresh();
|
|
918
1333
|
});
|
|
919
|
-
this.dispatchChange();
|
|
920
1334
|
});
|
|
921
1335
|
}
|
|
922
1336
|
/**
|
|
@@ -964,54 +1378,58 @@ let LuksoMarkdownEditor = class extends TailwindStyledElement(style) {
|
|
|
964
1378
|
return;
|
|
965
1379
|
}
|
|
966
1380
|
const { start: s, end: e } = this.expandSelectionToWord(start, end, value);
|
|
967
|
-
const
|
|
968
|
-
let selected = value.slice(s, e);
|
|
969
|
-
const after = value.slice(e);
|
|
970
|
-
const colorRegex = /<span style="color: ([^"]+)">(.*?)<\/span>/gs;
|
|
971
|
-
selected = selected.replace(colorRegex, "$2");
|
|
972
|
-
const fullColorRegex = /<span style="color: ([^"]+)">(.*?)<\/span>/g;
|
|
1381
|
+
const colorRegex = /<span style="color: ([^"]+)">(.*?)<\/span>/g;
|
|
973
1382
|
let match;
|
|
974
|
-
let
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
);
|
|
979
|
-
const searchOffset = Math.max(0, s - 100);
|
|
980
|
-
match = fullColorRegex.exec(searchText);
|
|
1383
|
+
let newValue = value;
|
|
1384
|
+
let foundSpan = false;
|
|
1385
|
+
colorRegex.lastIndex = 0;
|
|
1386
|
+
match = colorRegex.exec(value);
|
|
981
1387
|
while (match !== null) {
|
|
982
|
-
const
|
|
983
|
-
const
|
|
1388
|
+
const fullMatchStart = match.index;
|
|
1389
|
+
const fullMatchEnd = match.index + match[0].length;
|
|
984
1390
|
const spanOpenTag = `<span style="color: ${match[1]}">`;
|
|
985
|
-
const contentStart =
|
|
986
|
-
const contentEnd =
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
1391
|
+
const contentStart = fullMatchStart + spanOpenTag.length;
|
|
1392
|
+
const contentEnd = fullMatchEnd - 7;
|
|
1393
|
+
const innerContent = match[2];
|
|
1394
|
+
match = colorRegex.exec(value);
|
|
1395
|
+
const selectionOverlaps = s >= contentStart && s < contentEnd || e > contentStart && e <= contentEnd || s <= contentStart && e >= contentEnd;
|
|
1396
|
+
if (selectionOverlaps) {
|
|
1397
|
+
newValue = value.slice(0, fullMatchStart) + innerContent + value.slice(fullMatchEnd);
|
|
1398
|
+
foundSpan = true;
|
|
1399
|
+
this.value = newValue;
|
|
1400
|
+
textarea.value = newValue;
|
|
1401
|
+
let newStart = s;
|
|
1402
|
+
let newEnd = e;
|
|
1403
|
+
if (s >= fullMatchStart) {
|
|
1404
|
+
const spanOpenTagLength = spanOpenTag.length;
|
|
1405
|
+
if (s >= contentStart) {
|
|
1406
|
+
newStart = s - spanOpenTagLength;
|
|
1407
|
+
} else if (s >= fullMatchStart) {
|
|
1408
|
+
newStart = fullMatchStart;
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
if (e >= fullMatchStart) {
|
|
1412
|
+
const spanOpenTagLength = spanOpenTag.length;
|
|
1413
|
+
if (e <= contentEnd) {
|
|
1414
|
+
newEnd = e - spanOpenTagLength;
|
|
1415
|
+
} else {
|
|
1416
|
+
newEnd = e - spanOpenTagLength - 7;
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
995
1419
|
requestAnimationFrame(() => {
|
|
996
|
-
textarea.setSelectionRange(
|
|
997
|
-
|
|
1420
|
+
textarea.setSelectionRange(
|
|
1421
|
+
Math.max(0, newStart),
|
|
1422
|
+
Math.max(0, newEnd)
|
|
1423
|
+
);
|
|
1424
|
+
this.emitChangeAndRefresh();
|
|
998
1425
|
});
|
|
999
|
-
|
|
1000
|
-
foundMatch = true;
|
|
1001
|
-
break;
|
|
1426
|
+
return;
|
|
1002
1427
|
}
|
|
1003
|
-
match = fullColorRegex.exec(searchText);
|
|
1004
1428
|
}
|
|
1005
|
-
if (!
|
|
1006
|
-
this.value = before + selected + after;
|
|
1007
|
-
textarea.value = before + selected + after;
|
|
1008
|
-
const selStart = before.length;
|
|
1009
|
-
const selEnd = selStart + selected.length;
|
|
1429
|
+
if (!foundSpan) {
|
|
1010
1430
|
requestAnimationFrame(() => {
|
|
1011
|
-
textarea.setSelectionRange(selStart, selEnd);
|
|
1012
1431
|
this.updateActiveFormats();
|
|
1013
1432
|
});
|
|
1014
|
-
this.dispatchChange();
|
|
1015
1433
|
}
|
|
1016
1434
|
});
|
|
1017
1435
|
}
|
|
@@ -1022,6 +1440,7 @@ let LuksoMarkdownEditor = class extends TailwindStyledElement(style) {
|
|
|
1022
1440
|
if (this.activeFormats.h1) return 1;
|
|
1023
1441
|
if (this.activeFormats.h2) return 2;
|
|
1024
1442
|
if (this.activeFormats.h3) return 3;
|
|
1443
|
+
if (this.activeFormats.h4) return 4;
|
|
1025
1444
|
return 0;
|
|
1026
1445
|
}
|
|
1027
1446
|
/**
|
|
@@ -1089,10 +1508,14 @@ let LuksoMarkdownEditor = class extends TailwindStyledElement(style) {
|
|
|
1089
1508
|
const textarea = this.textareaEl?.shadowRoot?.querySelector(
|
|
1090
1509
|
"textarea"
|
|
1091
1510
|
);
|
|
1092
|
-
if (!textarea)
|
|
1511
|
+
if (!textarea) {
|
|
1512
|
+
return false;
|
|
1513
|
+
}
|
|
1093
1514
|
const start = textarea.selectionStart ?? 0;
|
|
1094
1515
|
const end = textarea.selectionEnd ?? 0;
|
|
1095
|
-
if (start !== end)
|
|
1516
|
+
if (start !== end) {
|
|
1517
|
+
return false;
|
|
1518
|
+
}
|
|
1096
1519
|
const value = this.value;
|
|
1097
1520
|
const lineStart = value.lastIndexOf("\n", start - 1) + 1;
|
|
1098
1521
|
let lineEnd = value.indexOf("\n", start);
|
|
@@ -1117,9 +1540,8 @@ let LuksoMarkdownEditor = class extends TailwindStyledElement(style) {
|
|
|
1117
1540
|
const newCursor2 = before2.length;
|
|
1118
1541
|
requestAnimationFrame(() => {
|
|
1119
1542
|
textarea.setSelectionRange(newCursor2, newCursor2);
|
|
1120
|
-
this.
|
|
1543
|
+
this.emitChangeAndRefresh();
|
|
1121
1544
|
});
|
|
1122
|
-
this.dispatchChange();
|
|
1123
1545
|
return true;
|
|
1124
1546
|
}
|
|
1125
1547
|
const before = value.slice(0, start);
|
|
@@ -1130,11 +1552,8 @@ ${indent}${marker} `;
|
|
|
1130
1552
|
this.value = newValue;
|
|
1131
1553
|
textarea.value = newValue;
|
|
1132
1554
|
const newCursor = start + prefix.length;
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
this.updateActiveFormats();
|
|
1136
|
-
});
|
|
1137
|
-
this.dispatchChange();
|
|
1555
|
+
textarea.setSelectionRange(newCursor, newCursor);
|
|
1556
|
+
this.emitChangeAndRefresh();
|
|
1138
1557
|
return true;
|
|
1139
1558
|
}
|
|
1140
1559
|
if (orderedMatch) {
|
|
@@ -1150,30 +1569,39 @@ ${indent}${marker} `;
|
|
|
1150
1569
|
const newCursor2 = before2.length;
|
|
1151
1570
|
requestAnimationFrame(() => {
|
|
1152
1571
|
textarea.setSelectionRange(newCursor2, newCursor2);
|
|
1153
|
-
this.
|
|
1572
|
+
this.emitChangeAndRefresh();
|
|
1154
1573
|
});
|
|
1155
|
-
this.dispatchChange();
|
|
1156
1574
|
return true;
|
|
1157
1575
|
}
|
|
1158
|
-
const
|
|
1576
|
+
const currentNumber = parseInt(numberStr, 10);
|
|
1577
|
+
const nextNumber = currentNumber + 1;
|
|
1159
1578
|
const before = value.slice(0, start);
|
|
1160
1579
|
const after = value.slice(start);
|
|
1161
1580
|
const prefix = `
|
|
1162
1581
|
${indent}${nextNumber}. `;
|
|
1163
|
-
|
|
1164
|
-
const
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
)
|
|
1169
|
-
|
|
1170
|
-
|
|
1582
|
+
let newValue = before + prefix + after;
|
|
1583
|
+
const lines = newValue.split("\n");
|
|
1584
|
+
const insertLineIndex = before.split("\n").length;
|
|
1585
|
+
const renumberStartIndex = insertLineIndex + 1;
|
|
1586
|
+
let currentNum = nextNumber + 1;
|
|
1587
|
+
const orderedRegex = /^(\s*)(\d+)\.\s+(.*)$/;
|
|
1588
|
+
for (let i = renumberStartIndex; i < lines.length; i++) {
|
|
1589
|
+
const line = lines[i];
|
|
1590
|
+
const match = line.match(orderedRegex);
|
|
1591
|
+
if (match && match[1] === indent) {
|
|
1592
|
+
const content2 = match[3];
|
|
1593
|
+
lines[i] = `${indent}${currentNum}. ${content2}`;
|
|
1594
|
+
currentNum++;
|
|
1595
|
+
} else if (match && match[1].length < indent.length) {
|
|
1596
|
+
break;
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
newValue = lines.join("\n");
|
|
1600
|
+
this.value = newValue;
|
|
1601
|
+
textarea.value = newValue;
|
|
1171
1602
|
const newCursor = start + prefix.length;
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
this.updateActiveFormats();
|
|
1175
|
-
});
|
|
1176
|
-
this.dispatchChange();
|
|
1603
|
+
textarea.setSelectionRange(newCursor, newCursor);
|
|
1604
|
+
this.emitChangeAndRefresh();
|
|
1177
1605
|
return true;
|
|
1178
1606
|
}
|
|
1179
1607
|
return false;
|
|
@@ -1186,10 +1614,14 @@ ${indent}${nextNumber}. `;
|
|
|
1186
1614
|
const textarea = this.textareaEl?.shadowRoot?.querySelector(
|
|
1187
1615
|
"textarea"
|
|
1188
1616
|
);
|
|
1189
|
-
if (!textarea)
|
|
1617
|
+
if (!textarea) {
|
|
1618
|
+
return false;
|
|
1619
|
+
}
|
|
1190
1620
|
const start = textarea.selectionStart ?? 0;
|
|
1191
1621
|
const end = textarea.selectionEnd ?? 0;
|
|
1192
|
-
if (start !== end)
|
|
1622
|
+
if (start !== end) {
|
|
1623
|
+
return false;
|
|
1624
|
+
}
|
|
1193
1625
|
const value = this.value;
|
|
1194
1626
|
const lineStart = value.lastIndexOf("\n", start - 1) + 1;
|
|
1195
1627
|
let lineEnd = value.indexOf("\n", start);
|
|
@@ -1215,21 +1647,85 @@ ${indent}${nextNumber}. `;
|
|
|
1215
1647
|
} else if (orderedMatch) {
|
|
1216
1648
|
const currentIndent = orderedMatch[1] ?? "";
|
|
1217
1649
|
const content = orderedMatch[3] ?? "";
|
|
1218
|
-
|
|
1219
|
-
|
|
1650
|
+
if (content.trim() === "") {
|
|
1651
|
+
newLine = currentLine;
|
|
1652
|
+
const nestedLine = `${currentIndent}${indent}1. `;
|
|
1653
|
+
let newValue2 = before + newLine + "\n" + nestedLine + after;
|
|
1654
|
+
const parentIndent = currentIndent;
|
|
1655
|
+
const lines = newValue2.split("\n");
|
|
1656
|
+
const currentLineIndex = Math.floor(lineStart / (newValue2.indexOf("\n") + 1)) || newValue2.slice(0, lineStart).split("\n").length - 1;
|
|
1657
|
+
const currentLineMatch = lines[currentLineIndex]?.match(
|
|
1658
|
+
/^(\s*)(\d+)\.\s*(.*)$/
|
|
1659
|
+
);
|
|
1660
|
+
const nextExpectedNumber = currentLineMatch ? parseInt(currentLineMatch[2], 10) : 1;
|
|
1661
|
+
const orderedRegex = /^(\s*)(\d+)\.\s*(.*)$/;
|
|
1662
|
+
let currentNumber = nextExpectedNumber;
|
|
1663
|
+
for (let i = currentLineIndex + 2; i < lines.length; i++) {
|
|
1664
|
+
const line = lines[i];
|
|
1665
|
+
const orderedMatch2 = line.match(orderedRegex);
|
|
1666
|
+
if (orderedMatch2 && orderedMatch2[1] === parentIndent) {
|
|
1667
|
+
const content2 = orderedMatch2[3];
|
|
1668
|
+
lines[i] = `${parentIndent}${currentNumber}. ${content2}`;
|
|
1669
|
+
currentNumber++;
|
|
1670
|
+
} else if (orderedMatch2 && orderedMatch2[1].length < parentIndent.length) {
|
|
1671
|
+
break;
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
newValue2 = lines.join("\n");
|
|
1675
|
+
const newCursor2 = lineStart + newLine.length + 1 + nestedLine.length;
|
|
1676
|
+
this.value = newValue2;
|
|
1677
|
+
textarea.value = newValue2;
|
|
1678
|
+
requestAnimationFrame(() => {
|
|
1679
|
+
textarea.setSelectionRange(newCursor2, newCursor2);
|
|
1680
|
+
this.emitChangeAndRefresh();
|
|
1681
|
+
});
|
|
1682
|
+
return true;
|
|
1683
|
+
} else {
|
|
1684
|
+
const newIndent = currentIndent + indent;
|
|
1685
|
+
let newNumber = 1;
|
|
1686
|
+
const beforeText = value.slice(0, lineStart);
|
|
1687
|
+
const lines = beforeText.split("\n");
|
|
1688
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
1689
|
+
const line = lines[i];
|
|
1690
|
+
const match = line.match(/^(\s*)(\d+)\.\s*(.*)$/);
|
|
1691
|
+
if (match && match[1] === newIndent) {
|
|
1692
|
+
newNumber = parseInt(match[2], 10) + 1;
|
|
1693
|
+
break;
|
|
1694
|
+
} else if (match && match[1].length < newIndent.length) {
|
|
1695
|
+
break;
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1698
|
+
newLine = `${newIndent}${newNumber}. ${content}`;
|
|
1699
|
+
newCursorOffset = indent.length;
|
|
1700
|
+
}
|
|
1220
1701
|
} else {
|
|
1221
1702
|
return false;
|
|
1222
1703
|
}
|
|
1223
|
-
|
|
1704
|
+
let newValue = before + newLine + after;
|
|
1705
|
+
if (orderedMatch) {
|
|
1706
|
+
const newIndent = (orderedMatch[1] ?? "") + indent;
|
|
1707
|
+
newValue = this.renumberOrderedListItems(
|
|
1708
|
+
newValue,
|
|
1709
|
+
lineStart + newLine.length,
|
|
1710
|
+
// Start renumbering after the current line
|
|
1711
|
+
newIndent
|
|
1712
|
+
// Use the new indentation level
|
|
1713
|
+
);
|
|
1714
|
+
const parentIndent = orderedMatch[1] ?? "";
|
|
1715
|
+
newValue = this.renumberOrderedListItems(
|
|
1716
|
+
newValue,
|
|
1717
|
+
lineStart,
|
|
1718
|
+
// Start from this line for parent level
|
|
1719
|
+
parentIndent
|
|
1720
|
+
// Use the parent level indentation
|
|
1721
|
+
);
|
|
1722
|
+
}
|
|
1224
1723
|
this.value = newValue;
|
|
1225
1724
|
textarea.value = newValue;
|
|
1226
1725
|
const cursorInLine = start - lineStart;
|
|
1227
1726
|
const newCursor = lineStart + cursorInLine + newCursorOffset;
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
this.updateActiveFormats();
|
|
1231
|
-
});
|
|
1232
|
-
this.dispatchChange();
|
|
1727
|
+
textarea.setSelectionRange(newCursor, newCursor);
|
|
1728
|
+
this.emitChangeAndRefresh();
|
|
1233
1729
|
return true;
|
|
1234
1730
|
}
|
|
1235
1731
|
/**
|
|
@@ -1308,9 +1804,8 @@ ${indent}${nextNumber}. `;
|
|
|
1308
1804
|
);
|
|
1309
1805
|
requestAnimationFrame(() => {
|
|
1310
1806
|
textarea.setSelectionRange(newCursor, newCursor);
|
|
1311
|
-
this.
|
|
1807
|
+
this.emitChangeAndRefresh();
|
|
1312
1808
|
});
|
|
1313
|
-
this.dispatchChange();
|
|
1314
1809
|
return true;
|
|
1315
1810
|
}
|
|
1316
1811
|
/**
|
|
@@ -1437,32 +1932,52 @@ ${indent}${nextNumber}. `;
|
|
|
1437
1932
|
markerEndPosition = indent.length + numberStr.length + 2;
|
|
1438
1933
|
hasContent = content.trim().length > 0;
|
|
1439
1934
|
}
|
|
1440
|
-
|
|
1935
|
+
const isAtOrAfterMarker = cursorPositionInLine >= markerEndPosition;
|
|
1936
|
+
if (isAtOrAfterMarker && !hasContent) {
|
|
1441
1937
|
this.saveUndoStateBeforeChange();
|
|
1442
1938
|
const before = value.slice(0, lineStart);
|
|
1443
|
-
const after = value.slice(
|
|
1444
|
-
|
|
1445
|
-
)
|
|
1446
|
-
|
|
1939
|
+
const after = value.slice(lineEnd);
|
|
1940
|
+
let newValue;
|
|
1941
|
+
if (lineEnd === value.length) {
|
|
1942
|
+
newValue = before.endsWith("\n") ? before.slice(0, -1) : before;
|
|
1943
|
+
} else {
|
|
1944
|
+
newValue = before + after.slice(1);
|
|
1945
|
+
}
|
|
1447
1946
|
if (orderedMatch) {
|
|
1448
1947
|
const indent = orderedMatch[1] ?? "";
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1948
|
+
const lines = newValue.split("\n");
|
|
1949
|
+
const startLineIndex = Math.max(0, before.split("\n").length - 1);
|
|
1950
|
+
let nextNumber = 1;
|
|
1951
|
+
for (let i = startLineIndex - 1; i >= 0; i--) {
|
|
1952
|
+
const line = lines[i];
|
|
1953
|
+
const match = line.match(/^(\s*)(\d+)\.\s*(.*)$/);
|
|
1954
|
+
if (match && match[1] === indent) {
|
|
1955
|
+
nextNumber = parseInt(match[2], 10) + 1;
|
|
1956
|
+
break;
|
|
1957
|
+
}
|
|
1958
|
+
}
|
|
1959
|
+
for (let i = startLineIndex; i < lines.length; i++) {
|
|
1960
|
+
const line = lines[i];
|
|
1961
|
+
const match = line.match(/^(\s*)(\d+)\.\s*(.*)$/);
|
|
1962
|
+
if (match && match[1] === indent) {
|
|
1963
|
+
lines[i] = `${indent}${nextNumber}. ${match[3]}`;
|
|
1964
|
+
nextNumber++;
|
|
1965
|
+
} else if (match && match[1].length < indent.length) {
|
|
1966
|
+
break;
|
|
1967
|
+
}
|
|
1968
|
+
}
|
|
1969
|
+
newValue = lines.join("\n");
|
|
1454
1970
|
}
|
|
1455
1971
|
this.value = newValue;
|
|
1456
1972
|
textarea.value = newValue;
|
|
1457
1973
|
let newCursor = before.length;
|
|
1458
|
-
if (before.endsWith("\n")
|
|
1974
|
+
if (newCursor > 0 && before.endsWith("\n")) {
|
|
1459
1975
|
newCursor = before.length - 1;
|
|
1460
1976
|
}
|
|
1461
1977
|
requestAnimationFrame(() => {
|
|
1462
1978
|
textarea.setSelectionRange(newCursor, newCursor);
|
|
1463
|
-
this.
|
|
1979
|
+
this.emitChangeAndRefresh();
|
|
1464
1980
|
});
|
|
1465
|
-
this.dispatchChange();
|
|
1466
1981
|
return true;
|
|
1467
1982
|
}
|
|
1468
1983
|
return false;
|
|
@@ -1495,10 +2010,9 @@ ${indent}${nextNumber}. `;
|
|
|
1495
2010
|
previousState.selection.end
|
|
1496
2011
|
);
|
|
1497
2012
|
}
|
|
1498
|
-
this.
|
|
2013
|
+
this.emitChangeAndRefresh();
|
|
1499
2014
|
this.isUndoRedoAction = false;
|
|
1500
2015
|
});
|
|
1501
|
-
this.dispatchChange();
|
|
1502
2016
|
}
|
|
1503
2017
|
/**
|
|
1504
2018
|
* Perform a redo operation, reapplying a previously undone state.
|
|
@@ -1527,10 +2041,9 @@ ${indent}${nextNumber}. `;
|
|
|
1527
2041
|
nextState.selection.end
|
|
1528
2042
|
);
|
|
1529
2043
|
}
|
|
1530
|
-
this.
|
|
2044
|
+
this.emitChangeAndRefresh();
|
|
1531
2045
|
this.isUndoRedoAction = false;
|
|
1532
2046
|
});
|
|
1533
|
-
this.dispatchChange();
|
|
1534
2047
|
}
|
|
1535
2048
|
connectedCallback() {
|
|
1536
2049
|
super.connectedCallback();
|
|
@@ -1542,14 +2055,26 @@ ${indent}${nextNumber}. `;
|
|
|
1542
2055
|
this.saveInitialUndoState();
|
|
1543
2056
|
this.updateActiveFormats();
|
|
1544
2057
|
this.addKeyboardListeners();
|
|
2058
|
+
if (this.isPreview && this.value.trim()) {
|
|
2059
|
+
this.scheduleAccessibilityCheck();
|
|
2060
|
+
}
|
|
1545
2061
|
});
|
|
1546
2062
|
}
|
|
2063
|
+
updated(changedProperties) {
|
|
2064
|
+
super.updated(changedProperties);
|
|
2065
|
+
if (changedProperties.has("previewBackgroundColor") && this.isPreview && this.value.trim()) {
|
|
2066
|
+
this.scheduleAccessibilityCheck();
|
|
2067
|
+
}
|
|
2068
|
+
}
|
|
1547
2069
|
disconnectedCallback() {
|
|
1548
2070
|
super.disconnectedCallback();
|
|
1549
2071
|
document.removeEventListener("click", this.handleOutsideClick);
|
|
1550
2072
|
if (this.undoTimeout) {
|
|
1551
2073
|
clearTimeout(this.undoTimeout);
|
|
1552
2074
|
}
|
|
2075
|
+
if (this.accessibilityCheckTimeout) {
|
|
2076
|
+
clearTimeout(this.accessibilityCheckTimeout);
|
|
2077
|
+
}
|
|
1553
2078
|
this.removeKeyboardListeners();
|
|
1554
2079
|
this.savedSelectionForPreview = null;
|
|
1555
2080
|
}
|
|
@@ -1605,7 +2130,7 @@ ${indent}${nextNumber}. `;
|
|
|
1605
2130
|
}
|
|
1606
2131
|
toolbarTemplate() {
|
|
1607
2132
|
return x`
|
|
1608
|
-
<div class="flex items-center gap-
|
|
2133
|
+
<div class="flex items-center gap-1">
|
|
1609
2134
|
<div class=${cn(this.styles().headingMenu())}>
|
|
1610
2135
|
<!-- Heading -->
|
|
1611
2136
|
<lukso-tooltip text="Heading options" placement="top">
|
|
@@ -1615,6 +2140,7 @@ ${indent}${nextNumber}. `;
|
|
|
1615
2140
|
e.stopPropagation();
|
|
1616
2141
|
this.isColorDropdownOpen = false;
|
|
1617
2142
|
this.isListDropdownOpen = false;
|
|
2143
|
+
this.isAlignmentDropdownOpen = false;
|
|
1618
2144
|
this.isHeadingDropdownOpen = !this.isHeadingDropdownOpen;
|
|
1619
2145
|
}}
|
|
1620
2146
|
aria-expanded=${this.isHeadingDropdownOpen ? "true" : "false"}
|
|
@@ -1688,6 +2214,18 @@ ${indent}${nextNumber}. `;
|
|
|
1688
2214
|
>
|
|
1689
2215
|
Heading 3
|
|
1690
2216
|
</lukso-dropdown-option>
|
|
2217
|
+
<lukso-dropdown-option
|
|
2218
|
+
?is-selected=${this.getActiveHeadingLevel() === 4}
|
|
2219
|
+
@click=${(e) => {
|
|
2220
|
+
e.stopPropagation();
|
|
2221
|
+
this.restoreFocusAndSelection();
|
|
2222
|
+
this.applyHeading(4);
|
|
2223
|
+
this.isHeadingDropdownOpen = false;
|
|
2224
|
+
}}
|
|
2225
|
+
size="medium"
|
|
2226
|
+
>
|
|
2227
|
+
Heading 4
|
|
2228
|
+
</lukso-dropdown-option>
|
|
1691
2229
|
</lukso-dropdown>
|
|
1692
2230
|
</div>
|
|
1693
2231
|
|
|
@@ -1717,6 +2255,7 @@ ${indent}${nextNumber}. `;
|
|
|
1717
2255
|
this.restoreFocusAndSelection();
|
|
1718
2256
|
this.isHeadingDropdownOpen = false;
|
|
1719
2257
|
this.isColorDropdownOpen = false;
|
|
2258
|
+
this.isAlignmentDropdownOpen = false;
|
|
1720
2259
|
this.isListDropdownOpen = !this.isListDropdownOpen;
|
|
1721
2260
|
}}
|
|
1722
2261
|
aria-expanded=${this.isListDropdownOpen ? "true" : "false"}
|
|
@@ -1789,6 +2328,108 @@ ${indent}${nextNumber}. `;
|
|
|
1789
2328
|
this.activeFormats.link
|
|
1790
2329
|
)}
|
|
1791
2330
|
|
|
2331
|
+
<!-- Text Alignment -->
|
|
2332
|
+
<div class=${this.styles().alignmentMenu()}>
|
|
2333
|
+
<lukso-tooltip text="Text alignment" placement="top">
|
|
2334
|
+
<lukso-button
|
|
2335
|
+
id=${this.alignmentTriggerId}
|
|
2336
|
+
@click=${(e) => {
|
|
2337
|
+
e.stopPropagation();
|
|
2338
|
+
this.restoreFocusAndSelection();
|
|
2339
|
+
this.isHeadingDropdownOpen = false;
|
|
2340
|
+
this.isColorDropdownOpen = false;
|
|
2341
|
+
this.isListDropdownOpen = false;
|
|
2342
|
+
this.isAlignmentDropdownOpen = !this.isAlignmentDropdownOpen;
|
|
2343
|
+
}}
|
|
2344
|
+
aria-expanded=${this.isAlignmentDropdownOpen ? "true" : "false"}
|
|
2345
|
+
aria-label="Text alignment"
|
|
2346
|
+
variant="secondary"
|
|
2347
|
+
size="small"
|
|
2348
|
+
custom-class=${this.toolbarButton({
|
|
2349
|
+
active: this.activeFormats.alignment !== "left"
|
|
2350
|
+
})}
|
|
2351
|
+
is-icon
|
|
2352
|
+
>
|
|
2353
|
+
<lukso-icon
|
|
2354
|
+
name=${this.getAlignmentIcon()}
|
|
2355
|
+
size="small"
|
|
2356
|
+
pack="vuesax"
|
|
2357
|
+
variant="linear"
|
|
2358
|
+
></lukso-icon>
|
|
2359
|
+
</lukso-button>
|
|
2360
|
+
</lukso-tooltip>
|
|
2361
|
+
<lukso-dropdown
|
|
2362
|
+
id="alignmentDropdown"
|
|
2363
|
+
trigger-id=""
|
|
2364
|
+
size="medium"
|
|
2365
|
+
?is-open=${this.isAlignmentDropdownOpen}
|
|
2366
|
+
>
|
|
2367
|
+
<lukso-dropdown-option
|
|
2368
|
+
?is-selected=${this.activeFormats.alignment === "left"}
|
|
2369
|
+
@click=${(e) => {
|
|
2370
|
+
e.stopPropagation();
|
|
2371
|
+
this.restoreFocusAndSelection();
|
|
2372
|
+
this.applyAlignment("left");
|
|
2373
|
+
this.isAlignmentDropdownOpen = false;
|
|
2374
|
+
}}
|
|
2375
|
+
size="medium"
|
|
2376
|
+
aria-label="Align left"
|
|
2377
|
+
>
|
|
2378
|
+
<div style="display: flex; align-items: center; gap: 8px;">
|
|
2379
|
+
<lukso-icon
|
|
2380
|
+
name="textalign-left"
|
|
2381
|
+
size="small"
|
|
2382
|
+
pack="vuesax"
|
|
2383
|
+
variant="linear"
|
|
2384
|
+
></lukso-icon>
|
|
2385
|
+
Left
|
|
2386
|
+
</div>
|
|
2387
|
+
</lukso-dropdown-option>
|
|
2388
|
+
<lukso-dropdown-option
|
|
2389
|
+
?is-selected=${this.activeFormats.alignment === "center"}
|
|
2390
|
+
@click=${(e) => {
|
|
2391
|
+
e.stopPropagation();
|
|
2392
|
+
this.restoreFocusAndSelection();
|
|
2393
|
+
this.applyAlignment("center");
|
|
2394
|
+
this.isAlignmentDropdownOpen = false;
|
|
2395
|
+
}}
|
|
2396
|
+
size="medium"
|
|
2397
|
+
aria-label="Align center"
|
|
2398
|
+
>
|
|
2399
|
+
<div style="display: flex; align-items: center; gap: 8px;">
|
|
2400
|
+
<lukso-icon
|
|
2401
|
+
name="textalign-center"
|
|
2402
|
+
size="small"
|
|
2403
|
+
pack="vuesax"
|
|
2404
|
+
variant="linear"
|
|
2405
|
+
></lukso-icon>
|
|
2406
|
+
Center
|
|
2407
|
+
</div>
|
|
2408
|
+
</lukso-dropdown-option>
|
|
2409
|
+
<lukso-dropdown-option
|
|
2410
|
+
?is-selected=${this.activeFormats.alignment === "right"}
|
|
2411
|
+
@click=${(e) => {
|
|
2412
|
+
e.stopPropagation();
|
|
2413
|
+
this.restoreFocusAndSelection();
|
|
2414
|
+
this.applyAlignment("right");
|
|
2415
|
+
this.isAlignmentDropdownOpen = false;
|
|
2416
|
+
}}
|
|
2417
|
+
size="medium"
|
|
2418
|
+
aria-label="Align right"
|
|
2419
|
+
>
|
|
2420
|
+
<div style="display: flex; align-items: center; gap: 8px;">
|
|
2421
|
+
<lukso-icon
|
|
2422
|
+
name="textalign-right"
|
|
2423
|
+
size="small"
|
|
2424
|
+
pack="vuesax"
|
|
2425
|
+
variant="linear"
|
|
2426
|
+
></lukso-icon>
|
|
2427
|
+
Right
|
|
2428
|
+
</div>
|
|
2429
|
+
</lukso-dropdown-option>
|
|
2430
|
+
</lukso-dropdown>
|
|
2431
|
+
</div>
|
|
2432
|
+
|
|
1792
2433
|
<!-- Color -->
|
|
1793
2434
|
<div class=${this.styles().colorMenu()}>
|
|
1794
2435
|
<lukso-tooltip text="Text color" placement="top">
|
|
@@ -1799,6 +2440,7 @@ ${indent}${nextNumber}. `;
|
|
|
1799
2440
|
this.restoreFocusAndSelection();
|
|
1800
2441
|
this.isHeadingDropdownOpen = false;
|
|
1801
2442
|
this.isListDropdownOpen = false;
|
|
2443
|
+
this.isAlignmentDropdownOpen = false;
|
|
1802
2444
|
if (!this.isColorDropdownOpen) {
|
|
1803
2445
|
const ta = this.textareaEl?.shadowRoot?.querySelector("textarea");
|
|
1804
2446
|
if (ta) {
|
|
@@ -1844,6 +2486,7 @@ ${indent}${nextNumber}. `;
|
|
|
1844
2486
|
this.isColorDropdownOpen = false;
|
|
1845
2487
|
}}
|
|
1846
2488
|
type="button"
|
|
2489
|
+
aria-label="Clear color"
|
|
1847
2490
|
>
|
|
1848
2491
|
Clear
|
|
1849
2492
|
</button>` : E}
|
|
@@ -1855,6 +2498,7 @@ ${indent}${nextNumber}. `;
|
|
|
1855
2498
|
style="background-color: ${color}"
|
|
1856
2499
|
title=${color}
|
|
1857
2500
|
aria-pressed=${this.activeFormats.activeColor === color ? "true" : "false"}
|
|
2501
|
+
aria-label="Color ${color}"
|
|
1858
2502
|
@click=${(e) => {
|
|
1859
2503
|
e.stopPropagation();
|
|
1860
2504
|
this.selectColor(color);
|
|
@@ -1871,47 +2515,88 @@ ${indent}${nextNumber}. `;
|
|
|
1871
2515
|
</div>
|
|
1872
2516
|
`;
|
|
1873
2517
|
}
|
|
2518
|
+
accessibilityIndicatorTemplate() {
|
|
2519
|
+
if (!this.hasAccessibilityViolations || this.accessibilityViolations.length === 0 || !this.isPreview) {
|
|
2520
|
+
return E;
|
|
2521
|
+
}
|
|
2522
|
+
const tooltipText = formatViolationsForTooltip(this.accessibilityViolations);
|
|
2523
|
+
return x`
|
|
2524
|
+
<div
|
|
2525
|
+
class="accessibility-indicator has-violations absolute top-2 right-2 z-10"
|
|
2526
|
+
style="pointer-events: auto;"
|
|
2527
|
+
>
|
|
2528
|
+
<lukso-tooltip placement="left">
|
|
2529
|
+
<div slot="text">
|
|
2530
|
+
<div .innerHTML=${tooltipText}></div>
|
|
2531
|
+
</div>
|
|
2532
|
+
<div
|
|
2533
|
+
class="flex cursor-help"
|
|
2534
|
+
role="alert"
|
|
2535
|
+
aria-label="Accessibility violations found"
|
|
2536
|
+
>
|
|
2537
|
+
<lukso-icon
|
|
2538
|
+
name="warning-2"
|
|
2539
|
+
size="small"
|
|
2540
|
+
color="red-65"
|
|
2541
|
+
pack="vuesax"
|
|
2542
|
+
variant="linear"
|
|
2543
|
+
></lukso-icon>
|
|
2544
|
+
</div>
|
|
2545
|
+
</lukso-tooltip>
|
|
2546
|
+
</div>
|
|
2547
|
+
`;
|
|
2548
|
+
}
|
|
1874
2549
|
render() {
|
|
1875
2550
|
const { wrapper, header, toolbar, area, editor, preview } = this.styles({
|
|
1876
2551
|
isFullWidth: this.isFullWidth
|
|
1877
2552
|
});
|
|
2553
|
+
if (!this.previewBackgroundColor) {
|
|
2554
|
+
this.previewBackgroundColor = DEFAULT_PREVIEW_BACKGROUND_COLOR;
|
|
2555
|
+
}
|
|
1878
2556
|
return x`
|
|
1879
2557
|
<div class=${wrapper()}>
|
|
1880
2558
|
${this.labelTemplate()} ${this.descriptionTemplate()}
|
|
1881
2559
|
|
|
1882
|
-
<div class
|
|
1883
|
-
<div class=${
|
|
1884
|
-
|
|
2560
|
+
<div class="flex flex-col gap-2">
|
|
2561
|
+
<div class=${header()}>
|
|
2562
|
+
<div class=${toolbar()}>${this.toolbarTemplate()}</div>
|
|
2563
|
+
${this.buttonTemplate(
|
|
1885
2564
|
"eye",
|
|
1886
2565
|
() => this.togglePreview(),
|
|
1887
2566
|
"Toggle preview",
|
|
1888
2567
|
this.isPreview
|
|
1889
2568
|
)}
|
|
1890
|
-
|
|
2569
|
+
</div>
|
|
1891
2570
|
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
2571
|
+
<div class=${area()}>
|
|
2572
|
+
${!this.isPreview ? x`<div class=${editor()}>
|
|
2573
|
+
<lukso-textarea
|
|
2574
|
+
.value=${this.value}
|
|
2575
|
+
name=${this.name ? this.name : E}
|
|
2576
|
+
size=${this.size ? this.size : E}
|
|
2577
|
+
rows=${this.rows ? this.rows : E}
|
|
2578
|
+
placeholder=${this.placeholder ? this.placeholder : E}
|
|
2579
|
+
error=${this.error ? this.error : E}
|
|
2580
|
+
?is-full-width=${true}
|
|
2581
|
+
?is-disabled=${this.isDisabled}
|
|
2582
|
+
?is-readonly=${this.isReadonly}
|
|
2583
|
+
?is-non-resizable=${this.isNonResizable}
|
|
2584
|
+
@on-input=${this.handleTextareaInput}
|
|
2585
|
+
@on-key-up=${this.handleTextareaKeyUp}
|
|
2586
|
+
@on-input-click=${this.handleTextareaClick}
|
|
2587
|
+
></lukso-textarea>
|
|
2588
|
+
${this.accessibilityIndicatorTemplate()}
|
|
2589
|
+
</div>` : x`<div
|
|
2590
|
+
class=${preview()}
|
|
2591
|
+
style="background-color: ${this.previewBackgroundColor};"
|
|
2592
|
+
>
|
|
2593
|
+
<lukso-markdown
|
|
2594
|
+
value=${this.value}
|
|
2595
|
+
prose-classes="prose prose-base prose-gray"
|
|
2596
|
+
></lukso-markdown>
|
|
2597
|
+
${this.accessibilityIndicatorTemplate()}
|
|
2598
|
+
</div>`}
|
|
2599
|
+
</div>
|
|
1915
2600
|
</div>
|
|
1916
2601
|
</div>
|
|
1917
2602
|
`;
|
|
@@ -1921,7 +2606,7 @@ __decorateClass([
|
|
|
1921
2606
|
n({ type: String })
|
|
1922
2607
|
], LuksoMarkdownEditor.prototype, "value", 2);
|
|
1923
2608
|
__decorateClass([
|
|
1924
|
-
n({ type: String })
|
|
2609
|
+
n({ type: String, reflect: true })
|
|
1925
2610
|
], LuksoMarkdownEditor.prototype, "name", 2);
|
|
1926
2611
|
__decorateClass([
|
|
1927
2612
|
n({ type: String })
|
|
@@ -1959,6 +2644,13 @@ __decorateClass([
|
|
|
1959
2644
|
__decorateClass([
|
|
1960
2645
|
n({ type: String })
|
|
1961
2646
|
], LuksoMarkdownEditor.prototype, "placeholder", 2);
|
|
2647
|
+
__decorateClass([
|
|
2648
|
+
n({
|
|
2649
|
+
type: String,
|
|
2650
|
+
attribute: "preview-background-color",
|
|
2651
|
+
reflect: true
|
|
2652
|
+
})
|
|
2653
|
+
], LuksoMarkdownEditor.prototype, "previewBackgroundColor", 2);
|
|
1962
2654
|
__decorateClass([
|
|
1963
2655
|
r()
|
|
1964
2656
|
], LuksoMarkdownEditor.prototype, "savedSelectionForPreview", 2);
|
|
@@ -1971,6 +2663,9 @@ __decorateClass([
|
|
|
1971
2663
|
__decorateClass([
|
|
1972
2664
|
r()
|
|
1973
2665
|
], LuksoMarkdownEditor.prototype, "isListDropdownOpen", 2);
|
|
2666
|
+
__decorateClass([
|
|
2667
|
+
r()
|
|
2668
|
+
], LuksoMarkdownEditor.prototype, "isAlignmentDropdownOpen", 2);
|
|
1974
2669
|
__decorateClass([
|
|
1975
2670
|
r()
|
|
1976
2671
|
], LuksoMarkdownEditor.prototype, "currentSelection", 2);
|
|
@@ -1980,6 +2675,12 @@ __decorateClass([
|
|
|
1980
2675
|
__decorateClass([
|
|
1981
2676
|
r()
|
|
1982
2677
|
], LuksoMarkdownEditor.prototype, "activeFormats", 2);
|
|
2678
|
+
__decorateClass([
|
|
2679
|
+
r()
|
|
2680
|
+
], LuksoMarkdownEditor.prototype, "accessibilityViolations", 2);
|
|
2681
|
+
__decorateClass([
|
|
2682
|
+
r()
|
|
2683
|
+
], LuksoMarkdownEditor.prototype, "hasAccessibilityViolations", 2);
|
|
1983
2684
|
__decorateClass([
|
|
1984
2685
|
e("lukso-textarea")
|
|
1985
2686
|
], LuksoMarkdownEditor.prototype, "textareaEl", 2);
|