inline-style-editor 1.4.0 → 1.4.2
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/inline-style-editor.js +8 -9
- package/dist/inline-style-editor.js.map +1 -1
- package/dist/inline-style-editor.mjs +5613 -3550
- package/dist/inline-style-editor.mjs.map +1 -1
- package/index.d.ts +2 -0
- package/package.json +11 -10
- package/src/assets/index.scss +2 -0
- package/src/assets/style.scss +175 -0
- package/src/components/ColorPicker.svelte +56 -0
- package/src/components/InlineStyleEditor.svelte +616 -0
- package/src/index.js +8 -0
- package/src/util/boxesContour.js +91 -0
- package/src/util/fonts.js +89 -0
- package/src/util/path.js +46 -0
- package/src/util/util.js +36 -0
|
@@ -0,0 +1,616 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { onMount, onDestroy, tick } from "svelte";
|
|
3
|
+
|
|
4
|
+
import "../assets/index.scss";
|
|
5
|
+
import { pick, debounce, pascalCaseToSentence, capitalizeFirstLetter, nbChars } from "../util/util";
|
|
6
|
+
import { computeContours } from "../util/boxesContour";
|
|
7
|
+
import ColorPicker from "./ColorPicker.svelte";
|
|
8
|
+
import { getFonts } from "../util/fonts";
|
|
9
|
+
|
|
10
|
+
const strokeElements = [
|
|
11
|
+
"altGlyph",
|
|
12
|
+
"circle",
|
|
13
|
+
"ellipse",
|
|
14
|
+
"line",
|
|
15
|
+
"path",
|
|
16
|
+
"polygon",
|
|
17
|
+
"polyline",
|
|
18
|
+
"rect",
|
|
19
|
+
"text",
|
|
20
|
+
"textPath",
|
|
21
|
+
"tref",
|
|
22
|
+
"tspan",
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
const borderProps = ["border-radius", "border-width", "border-color", "border-style"];
|
|
26
|
+
const backgroundProps = ["background-color"];
|
|
27
|
+
const fontProps = ["font-family", "font-size", "font-weight", "color"];
|
|
28
|
+
const pathProps = ["stroke-width", "stroke", "stroke-dasharray", "stroke-linejoin", "fill"];
|
|
29
|
+
const cssPropByType = {
|
|
30
|
+
"border-radius": { type: "slider", min: 0, max: 30, suffix: "px" },
|
|
31
|
+
"border-width": { type: "slider", min: 0, max: 30, suffix: "px" },
|
|
32
|
+
"border-style": {
|
|
33
|
+
type: "select",
|
|
34
|
+
choices: () => ["none", "dotted", "dashed", "solid", "double", "groove", "ridge", "inset", "outset"],
|
|
35
|
+
},
|
|
36
|
+
"border-color": { type: "color" },
|
|
37
|
+
"font-family": { type: "select", choices: getFontFamilies },
|
|
38
|
+
"font-size": { type: "slider", min: 0, max: 40, suffix: "px" },
|
|
39
|
+
"font-weight": { type: "slider", min: 0, max: 800 },
|
|
40
|
+
color: { type: "color" },
|
|
41
|
+
"stroke-width": {
|
|
42
|
+
type: "slider",
|
|
43
|
+
min: 0,
|
|
44
|
+
max: 20,
|
|
45
|
+
step: 0.5,
|
|
46
|
+
suffix: "px",
|
|
47
|
+
},
|
|
48
|
+
stroke: { type: "color" },
|
|
49
|
+
"stroke-linejoin": {
|
|
50
|
+
type: "select",
|
|
51
|
+
choices: () => ["bevel", "miter", "round"],
|
|
52
|
+
},
|
|
53
|
+
fill: { type: "color" },
|
|
54
|
+
"stroke-dasharray": { type: "slider", min: 0, max: 30, suffix: "px" },
|
|
55
|
+
"background-color": { type: "color" },
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// Props
|
|
59
|
+
const props = $props();
|
|
60
|
+
const getElems = props.getElems ?? null;
|
|
61
|
+
const listenOnClick = props.listenOnClick ?? false;
|
|
62
|
+
const onStyleChanged = props.onStyleChanged ?? (() => {});
|
|
63
|
+
const customProps = props.customProps ?? {};
|
|
64
|
+
const inlineDeletable = props.inlineDeletable ?? (() => true);
|
|
65
|
+
const cssRuleFilter = props.cssRuleFilter ?? null;
|
|
66
|
+
const getCssRuleName =
|
|
67
|
+
props.getCssRuleName ??
|
|
68
|
+
((cssRuleName, element) => {
|
|
69
|
+
if (cssRuleName === "inline") return "Selected element";
|
|
70
|
+
return cssRuleName;
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const typeText = "text";
|
|
74
|
+
const typeBorder = "border";
|
|
75
|
+
const typeStroke = "stroke";
|
|
76
|
+
const typeBackground = "background";
|
|
77
|
+
const customType = "custom";
|
|
78
|
+
const propByType = {
|
|
79
|
+
[typeText]: fontProps,
|
|
80
|
+
[typeBorder]: borderProps,
|
|
81
|
+
[typeStroke]: pathProps,
|
|
82
|
+
[typeBackground]: backgroundProps,
|
|
83
|
+
[customType]: Object.keys(customProps),
|
|
84
|
+
};
|
|
85
|
+
const inputTypeOrder = { slider: 0, select: 1, color: 2 };
|
|
86
|
+
|
|
87
|
+
// State variables
|
|
88
|
+
let elementToListen = $state(null);
|
|
89
|
+
let clickedElement = $state(null);
|
|
90
|
+
let positionAnchor = $state();
|
|
91
|
+
let self = $state();
|
|
92
|
+
let helperElemWrapper = $state();
|
|
93
|
+
let pathWithHoles = $state("");
|
|
94
|
+
let pageDimensions = $state({ width: 0, height: 0 });
|
|
95
|
+
let targetsToSearch = $state([[]]);
|
|
96
|
+
let allRules = $state([]); // list of list of CSS rules, for every target element
|
|
97
|
+
let allTypes = $state([]); // list of list of types (e.g color, border), for every target element
|
|
98
|
+
let selectedElemIndex = $state(0);
|
|
99
|
+
let selectedRuleIndex = $state(0);
|
|
100
|
+
let selectedTypeIndex = $state(0);
|
|
101
|
+
let propsByType = $state(); // propType -> {[props], selected}
|
|
102
|
+
let allCurrentPropDefs = $state({}); // propName => selectorDef
|
|
103
|
+
let bringableToFront = $state([]); // null = not bringable, true = bringable, false = was bringed
|
|
104
|
+
let hasDisplayedCustom = $state(false);
|
|
105
|
+
|
|
106
|
+
// Reactive derived values
|
|
107
|
+
const currentElement = $derived(targetsToSearch[selectedElemIndex]?.[0]);
|
|
108
|
+
const currentRule = $derived(allRules[selectedElemIndex]?.[selectedRuleIndex]);
|
|
109
|
+
let curType = $state();
|
|
110
|
+
|
|
111
|
+
// Effects
|
|
112
|
+
$effect(() => {
|
|
113
|
+
if (elementToListen !== null) {
|
|
114
|
+
init();
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
$effect(() => {
|
|
119
|
+
if (allTypes[selectedElemIndex]?.[selectedTypeIndex] !== curType) {
|
|
120
|
+
curType = allTypes[selectedElemIndex]?.[selectedTypeIndex];
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
$effect(() => {
|
|
125
|
+
if (curType || selectedRuleIndex || selectedElemIndex) {
|
|
126
|
+
initAndGroup();
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
onMount(() => {
|
|
131
|
+
close();
|
|
132
|
+
elementToListen = self.parentNode;
|
|
133
|
+
document.body.appendChild(self);
|
|
134
|
+
document.body.appendChild(helperElemWrapper);
|
|
135
|
+
document.body.appendChild(positionAnchor);
|
|
136
|
+
udpatePageDimensions();
|
|
137
|
+
// make sure the layout is computed to get the client size
|
|
138
|
+
setTimeout(() => {
|
|
139
|
+
udpatePageDimensions();
|
|
140
|
+
}, 1000);
|
|
141
|
+
window.addEventListener("resize", udpatePageDimensions);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
onDestroy(() => {
|
|
145
|
+
window.removeEventListener("resize", udpatePageDimensions);
|
|
146
|
+
if (listenOnClick) elementToListen.removeEventListener("click", _open);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
function getFontFamilies() {
|
|
150
|
+
return getFonts();
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function initAndGroup() {
|
|
154
|
+
const allProps = { ...cssPropByType, ...customProps };
|
|
155
|
+
const _allCurrentPropDefs = pick(allProps, propByType[curType]);
|
|
156
|
+
Object.keys(_allCurrentPropDefs).forEach((key) => {
|
|
157
|
+
const propSelectType = _allCurrentPropDefs[key].type;
|
|
158
|
+
let retrieveType = "number";
|
|
159
|
+
if (propSelectType === "color") retrieveType = "rgb";
|
|
160
|
+
else if (propSelectType === "select") retrieveType = "raw";
|
|
161
|
+
if (_allCurrentPropDefs[key].getter) {
|
|
162
|
+
const val = _allCurrentPropDefs[key].getter(currentElement);
|
|
163
|
+
if (val === null) {
|
|
164
|
+
delete _allCurrentPropDefs[key];
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
_allCurrentPropDefs[key].value = val;
|
|
168
|
+
_allCurrentPropDefs[key].displayed = val;
|
|
169
|
+
} else {
|
|
170
|
+
_allCurrentPropDefs[key].displayed = getComputedPropValue(currentElement, key, "raw");
|
|
171
|
+
_allCurrentPropDefs[key].value = getComputedPropValue(currentElement, key, retrieveType);
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
propsByType = Object.entries(_allCurrentPropDefs)
|
|
176
|
+
.reduce((byType, [propName, selectorDef]) => {
|
|
177
|
+
const selectorType = selectorDef.type;
|
|
178
|
+
const existing = byType.find((x) => x.type === selectorType);
|
|
179
|
+
if (!existing)
|
|
180
|
+
byType.push({
|
|
181
|
+
selected: 0,
|
|
182
|
+
props: [propName],
|
|
183
|
+
type: selectorType,
|
|
184
|
+
});
|
|
185
|
+
else existing.props.push(propName);
|
|
186
|
+
return byType;
|
|
187
|
+
}, [])
|
|
188
|
+
.sort((a, b) => {
|
|
189
|
+
if (inputTypeOrder[a.type] < inputTypeOrder[b.type]) return -1;
|
|
190
|
+
if (inputTypeOrder[a.type] > inputTypeOrder[b.type]) return 1;
|
|
191
|
+
return 0;
|
|
192
|
+
});
|
|
193
|
+
allCurrentPropDefs = _allCurrentPropDefs;
|
|
194
|
+
updateHelpers();
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function getRuleNames(rules) {
|
|
198
|
+
if (!rules) return [];
|
|
199
|
+
return rules.map((rule, i) => {
|
|
200
|
+
if (rule === "inline") return "inline";
|
|
201
|
+
const cssSelector = rule.selectorText;
|
|
202
|
+
const title = rule.parentStyleSheet.title || `${i}`;
|
|
203
|
+
return `${title}: ${cssSelector}`;
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function getRuleNamesTransformed(rules) {
|
|
208
|
+
return getRuleNames(rules).map((name) => getCssRuleName(name, clickedElement));
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
let warningDisplayed = new Set();
|
|
212
|
+
function getMatchedCSSRules(elems) {
|
|
213
|
+
const sheets = document.styleSheets;
|
|
214
|
+
return elems.reduce((matchedRulesByElem, elemDef) => {
|
|
215
|
+
const el = elemDef[0];
|
|
216
|
+
const matchedRules = ["inline"];
|
|
217
|
+
for (let i in sheets) {
|
|
218
|
+
try {
|
|
219
|
+
const rules = sheets[i].cssRules;
|
|
220
|
+
for (let r in rules) {
|
|
221
|
+
let selectorText = rules[r].selectorText;
|
|
222
|
+
if (!selectorText || rules[r].selectorText.length > 50) continue; // skip selectors too long
|
|
223
|
+
if (selectorText.split(",").some((selector) => selector === "*")) continue; // skip * selector
|
|
224
|
+
if (selectorText.endsWith(":hover"))
|
|
225
|
+
selectorText = selectorText.substring(0, selectorText.length - ":hover".length);
|
|
226
|
+
if (el.matches(selectorText)) {
|
|
227
|
+
if (cssRuleFilter !== null && !cssRuleFilter(el, rules[r].selectorText)) continue;
|
|
228
|
+
matchedRules.push(rules[r]);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
} catch (err) {
|
|
232
|
+
if (!warningDisplayed.has(i)) {
|
|
233
|
+
console.warn(
|
|
234
|
+
"Style editor: Not able to access",
|
|
235
|
+
sheets[i].ownerNode,
|
|
236
|
+
"sheet. Try CORS loading the sheet if you want to edit it.",
|
|
237
|
+
);
|
|
238
|
+
warningDisplayed.add(i);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
matchedRulesByElem.push(matchedRules);
|
|
243
|
+
return matchedRulesByElem;
|
|
244
|
+
}, []);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function getEditableTypes(elems) {
|
|
248
|
+
return elems.reduce((typesByElem, elemDef) => {
|
|
249
|
+
const elem = elemDef[0];
|
|
250
|
+
const types = [];
|
|
251
|
+
if (elem.firstChild && (elem.firstChild.nodeType === 3 || elem.firstChild.tagName === "tspan")) {
|
|
252
|
+
// Node.TEXT_NODE
|
|
253
|
+
types.push(typeText);
|
|
254
|
+
}
|
|
255
|
+
const elemTagName = elem.tagName.toLowerCase();
|
|
256
|
+
let bringable = false;
|
|
257
|
+
if (strokeElements.includes(elemTagName)) {
|
|
258
|
+
types.push(typeStroke);
|
|
259
|
+
const parentTag = elem.parentElement.tagName.toLowerCase();
|
|
260
|
+
if (
|
|
261
|
+
parentTag === "g" &&
|
|
262
|
+
elem.previousElementSibling &&
|
|
263
|
+
elem.previousElementSibling.tagName.toLowerCase() == elemTagName
|
|
264
|
+
) {
|
|
265
|
+
bringable = true;
|
|
266
|
+
}
|
|
267
|
+
} else {
|
|
268
|
+
types.push(typeBorder);
|
|
269
|
+
types.push(typeBackground);
|
|
270
|
+
}
|
|
271
|
+
if (bringable) bringableToFront.push(true);
|
|
272
|
+
else bringableToFront.push(null);
|
|
273
|
+
typesByElem.push(types);
|
|
274
|
+
return typesByElem;
|
|
275
|
+
}, []);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function init() {
|
|
279
|
+
if (listenOnClick) elementToListen.addEventListener("click", _open);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function _open(e) {
|
|
283
|
+
open(e.target, e.pageX, e.pageY);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
export async function open(el, x, y) {
|
|
287
|
+
clickedElement = el;
|
|
288
|
+
udpatePageDimensions();
|
|
289
|
+
if (el.classList.contains("overlay-over")) return overlayClicked();
|
|
290
|
+
else if (self.contains(el)) return;
|
|
291
|
+
selectedElemIndex = 0;
|
|
292
|
+
selectedRuleIndex = 0;
|
|
293
|
+
selectedTypeIndex = 0;
|
|
294
|
+
bringableToFront = [];
|
|
295
|
+
allTypes = [];
|
|
296
|
+
allRules = [];
|
|
297
|
+
if (getElems) targetsToSearch = getElems(el);
|
|
298
|
+
else targetsToSearch = [[el, "Clicked"]];
|
|
299
|
+
allTypes = getEditableTypes(targetsToSearch);
|
|
300
|
+
hasDisplayedCustom = false;
|
|
301
|
+
allRules = getMatchedCSSRules(targetsToSearch);
|
|
302
|
+
for (let def of Object.values(customProps)) {
|
|
303
|
+
if (def.getter(el) !== null) {
|
|
304
|
+
hasDisplayedCustom = true;
|
|
305
|
+
break;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
if (Object.keys(customProps).length) {
|
|
309
|
+
allTypes[0].push(customType);
|
|
310
|
+
}
|
|
311
|
+
await tick();
|
|
312
|
+
initAndGroup();
|
|
313
|
+
if (x && y) show(x, y);
|
|
314
|
+
else {
|
|
315
|
+
const rect = getBoundingBoxInfos(el, 15);
|
|
316
|
+
show(rect.left, rect.top);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
export function close() {
|
|
321
|
+
self.style.display = "none";
|
|
322
|
+
helperElemWrapper.style.display = "none";
|
|
323
|
+
pathWithHoles = "";
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
export function isOpened() {
|
|
327
|
+
return self.style.display === "block";
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function overlayClicked() {
|
|
331
|
+
close();
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function show(x, y) {
|
|
335
|
+
self.style.display = "block";
|
|
336
|
+
self.style.opacity = 0;
|
|
337
|
+
const popupDimension = self.getBoundingClientRect();
|
|
338
|
+
x = x + popupDimension.width + 20 > pageDimensions.width ? x - popupDimension.width - 20 : x + 20;
|
|
339
|
+
y = y + popupDimension.height + 20 > pageDimensions.height ? y - popupDimension.height - 20 : y + 20;
|
|
340
|
+
y = Math.max(y, 0);
|
|
341
|
+
self.style.left = x + "px";
|
|
342
|
+
self.style.top = y + "px";
|
|
343
|
+
helperElemWrapper.style.display = "block";
|
|
344
|
+
self.style.opacity = 1;
|
|
345
|
+
updateHelpers();
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
async function updateHelpers() {
|
|
349
|
+
await tick();
|
|
350
|
+
if (!currentRule) return;
|
|
351
|
+
let matching;
|
|
352
|
+
if (currentRule === "inline") matching = [currentElement];
|
|
353
|
+
else {
|
|
354
|
+
const selector = currentRule.selectorText.replace(/(:hover)|:focus/g, "");
|
|
355
|
+
matching = Array.from(document.querySelectorAll(selector));
|
|
356
|
+
}
|
|
357
|
+
const boundingBoxes = matching.map((el) => getBoundingBoxInfos(el, 10));
|
|
358
|
+
pathWithHoles = computeContours(boundingBoxes, pageDimensions);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function getBoundingBoxInfos(el, padding = 0) {
|
|
362
|
+
const rect = el.getBoundingClientRect();
|
|
363
|
+
return {
|
|
364
|
+
left: rect.left + window.scrollX - padding,
|
|
365
|
+
top: rect.top + window.scrollY - padding,
|
|
366
|
+
width: rect.width + padding * 2,
|
|
367
|
+
height: rect.height + padding * 2,
|
|
368
|
+
right: rect.left + window.scrollX + rect.width + padding,
|
|
369
|
+
bottom: rect.top + window.scrollY + rect.height + padding,
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function _updateProp(propName, val, suffix) {
|
|
374
|
+
const finalValue = suffix ? val + suffix : val;
|
|
375
|
+
if (currentRule === "inline") {
|
|
376
|
+
if (allCurrentPropDefs[propName].setter) {
|
|
377
|
+
allCurrentPropDefs[propName].setter(currentElement, val);
|
|
378
|
+
} else {
|
|
379
|
+
const style = currentElement.style; // do not trigger reactivity on currentElement
|
|
380
|
+
style[propName] = finalValue;
|
|
381
|
+
}
|
|
382
|
+
} else currentRule.style.setProperty(propName, finalValue);
|
|
383
|
+
allCurrentPropDefs[propName].value = val;
|
|
384
|
+
allCurrentPropDefs[propName].displayed = finalValue;
|
|
385
|
+
|
|
386
|
+
onStyleChanged(currentElement, currentRule, propName, finalValue);
|
|
387
|
+
updateHelpers();
|
|
388
|
+
}
|
|
389
|
+
const updateProp = debounce(_updateProp, 100);
|
|
390
|
+
|
|
391
|
+
function udpatePageDimensions() {
|
|
392
|
+
const bodyStyle = getComputedStyle(document.body);
|
|
393
|
+
const marginLeft = parseInt(bodyStyle.marginLeft);
|
|
394
|
+
const marginRight = parseInt(bodyStyle.marginRight);
|
|
395
|
+
const marginTop = parseInt(bodyStyle.marginTop);
|
|
396
|
+
const marginBottom = parseInt(bodyStyle.marginBottom);
|
|
397
|
+
pageDimensions = {
|
|
398
|
+
width: document.body.offsetWidth + marginLeft + marginRight,
|
|
399
|
+
height: document.body.offsetHeight + marginTop + marginBottom,
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
function cssRgbToHex(rgbStr) {
|
|
404
|
+
const m = rgbStr.match(/[0-9\.]+/g).map((i) => parseFloat(i));
|
|
405
|
+
if (m.length === 3) m.push(1);
|
|
406
|
+
return m.reduce((hexStr, cur, i) => {
|
|
407
|
+
if (i === 3)
|
|
408
|
+
hexStr += Math.round(cur * 255)
|
|
409
|
+
.toString(16)
|
|
410
|
+
.padStart(2, "0");
|
|
411
|
+
else hexStr += cur.toString(16).padStart(2, "0");
|
|
412
|
+
return hexStr;
|
|
413
|
+
}, "#");
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// type one of: "number", "rgb", "font", "raw"
|
|
417
|
+
function parsePropvalue(value, type = "number") {
|
|
418
|
+
if (type == "raw") return value;
|
|
419
|
+
if (type == "number" && /[0-9]+(px)|(em)|(rem)/.test(value)) return parseInt(value);
|
|
420
|
+
if (type == "rgb") {
|
|
421
|
+
if (value === "none") return "#00000000";
|
|
422
|
+
if (value.includes("rgb") || value[0] == "#") return cssRgbToHex(value);
|
|
423
|
+
}
|
|
424
|
+
return value;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function getComputedPropValue(el, cssProp, type = "number") {
|
|
428
|
+
let currentStyleValue = currentRule?.style?.[cssProp];
|
|
429
|
+
if (!currentStyleValue) {
|
|
430
|
+
const computed = getComputedStyle(el);
|
|
431
|
+
currentStyleValue = computed[cssProp];
|
|
432
|
+
}
|
|
433
|
+
return parsePropvalue(currentStyleValue, type);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
function bringToFront() {
|
|
437
|
+
bringableToFront[selectedElemIndex] = false;
|
|
438
|
+
currentElement.parentNode.appendChild(currentElement);
|
|
439
|
+
onStyleChanged(currentElement, currentRule, "bringtofront", null);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function deleteElem() {
|
|
443
|
+
currentElement.remove();
|
|
444
|
+
close();
|
|
445
|
+
}
|
|
446
|
+
function deleteProp(propName) {
|
|
447
|
+
if (currentRule === "inline") {
|
|
448
|
+
currentElement.style.removeProperty(propName);
|
|
449
|
+
} else {
|
|
450
|
+
currentRule.style.removeProperty(propName);
|
|
451
|
+
}
|
|
452
|
+
onStyleChanged(currentElement, currentRule, propName, null);
|
|
453
|
+
initAndGroup();
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
function selectRule(ruleIndex) {
|
|
457
|
+
const newRule = allRules[selectedElemIndex]?.[ruleIndex];
|
|
458
|
+
if (newRule !== "inline" && selectedTypeIndex === allTypes[selectedElemIndex].length - 1) {
|
|
459
|
+
selectedTypeIndex = 0;
|
|
460
|
+
}
|
|
461
|
+
selectedRuleIndex = ruleIndex;
|
|
462
|
+
}
|
|
463
|
+
</script>
|
|
464
|
+
|
|
465
|
+
<div bind:this={positionAnchor} style="position: absolute;"></div>
|
|
466
|
+
<svg
|
|
467
|
+
bind:this={helperElemWrapper}
|
|
468
|
+
class="ise-helper-wrapper"
|
|
469
|
+
version="1.1"
|
|
470
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
471
|
+
xmlns:xlink="http://www.w3.org/1999/xlink"
|
|
472
|
+
width={pageDimensions.width}
|
|
473
|
+
height={pageDimensions.height}
|
|
474
|
+
onclick={overlayClicked}
|
|
475
|
+
>
|
|
476
|
+
<clipPath id="overlay-clip" clip-rule="evenodd">
|
|
477
|
+
<path d={pathWithHoles} />
|
|
478
|
+
</clipPath>
|
|
479
|
+
<rect y="0" x="0" height="100%" width="100%" class="overlay-over" />
|
|
480
|
+
</svg>
|
|
481
|
+
|
|
482
|
+
<div class="ise" bind:this={self}>
|
|
483
|
+
<div class="close-button" onclick={close}>x</div>
|
|
484
|
+
{#if targetsToSearch.length > 1}
|
|
485
|
+
<div class="select-tab">
|
|
486
|
+
<b> Element </b>
|
|
487
|
+
{#each targetsToSearch as [_, name], elemIndex}
|
|
488
|
+
<span
|
|
489
|
+
class:selected={selectedElemIndex === elemIndex}
|
|
490
|
+
onclick={() => {
|
|
491
|
+
selectedElemIndex = elemIndex;
|
|
492
|
+
selectedRuleIndex = 0;
|
|
493
|
+
}}
|
|
494
|
+
>
|
|
495
|
+
{name}
|
|
496
|
+
</span>
|
|
497
|
+
{/each}
|
|
498
|
+
</div>
|
|
499
|
+
{/if}
|
|
500
|
+
<div class="select-tab">
|
|
501
|
+
<b> Applied to: </b>
|
|
502
|
+
{#if nbChars(getRuleNamesTransformed(allRules[selectedElemIndex])) > 30}
|
|
503
|
+
<select onchange={(e) => selectRule(e.target.value)}>
|
|
504
|
+
{#each getRuleNames(allRules[selectedElemIndex]) as ruleName, ruleIndex}
|
|
505
|
+
<option selected={selectedRuleIndex === ruleIndex} value={ruleIndex}
|
|
506
|
+
>{getCssRuleName(ruleName, clickedElement)}</option
|
|
507
|
+
>
|
|
508
|
+
{/each}
|
|
509
|
+
</select>
|
|
510
|
+
{:else}
|
|
511
|
+
{#each getRuleNames(allRules[selectedElemIndex]) as ruleName, ruleIndex}
|
|
512
|
+
<span
|
|
513
|
+
title={ruleName}
|
|
514
|
+
class:selected={selectedRuleIndex === ruleIndex}
|
|
515
|
+
onclick={() => {
|
|
516
|
+
selectRule(ruleIndex);
|
|
517
|
+
}}
|
|
518
|
+
>
|
|
519
|
+
{getCssRuleName(ruleName, clickedElement)}</span
|
|
520
|
+
>
|
|
521
|
+
{/each}
|
|
522
|
+
{/if}
|
|
523
|
+
</div>
|
|
524
|
+
<div class="select-tab">
|
|
525
|
+
<b> Property type: </b>
|
|
526
|
+
{#each allTypes[selectedElemIndex] || [] as type, typeIndex}
|
|
527
|
+
<!-- Only display "custom" on "inline" rule -->
|
|
528
|
+
{#if type !== "custom" || (currentRule === "inline" && type === "custom" && hasDisplayedCustom)}
|
|
529
|
+
<span
|
|
530
|
+
class:selected={selectedTypeIndex === typeIndex}
|
|
531
|
+
onclick={() => {
|
|
532
|
+
selectedTypeIndex = typeIndex;
|
|
533
|
+
}}
|
|
534
|
+
>
|
|
535
|
+
{type === "stroke" ? "SVG paint" : capitalizeFirstLetter(type)}
|
|
536
|
+
</span>
|
|
537
|
+
{/if}
|
|
538
|
+
{/each}
|
|
539
|
+
</div>
|
|
540
|
+
{#if allTypes[selectedElemIndex]}
|
|
541
|
+
<div class="editor">
|
|
542
|
+
{#each propsByType as choices}
|
|
543
|
+
{@const selectedName = choices.props[choices.selected]}
|
|
544
|
+
<div class="prop-section {choices.type}">
|
|
545
|
+
<div class="prop-name">
|
|
546
|
+
{#if choices.props.length > 1}
|
|
547
|
+
<select
|
|
548
|
+
onchange={async (e) => {
|
|
549
|
+
choices.selected = e.target.value;
|
|
550
|
+
await tick();
|
|
551
|
+
}}
|
|
552
|
+
>
|
|
553
|
+
{#each choices.props as propName, i}
|
|
554
|
+
<option selected={i === choices.selected} value={i}>
|
|
555
|
+
{#if choices.type === "color"}
|
|
556
|
+
{capitalizeFirstLetter(propName)} color
|
|
557
|
+
{:else}
|
|
558
|
+
{pascalCaseToSentence(propName)}
|
|
559
|
+
{/if}
|
|
560
|
+
</option>
|
|
561
|
+
{/each}
|
|
562
|
+
</select>
|
|
563
|
+
{:else}
|
|
564
|
+
<span> {pascalCaseToSentence(selectedName)} </span>
|
|
565
|
+
{/if}
|
|
566
|
+
<span class="delete" onclick={() => deleteProp(selectedName)}>✕</span>
|
|
567
|
+
</div>
|
|
568
|
+
{#if choices.type === "slider"}
|
|
569
|
+
<input
|
|
570
|
+
type="range"
|
|
571
|
+
min={allCurrentPropDefs[selectedName].min}
|
|
572
|
+
max={allCurrentPropDefs[selectedName].max}
|
|
573
|
+
step={allCurrentPropDefs[selectedName].step || 1}
|
|
574
|
+
value={allCurrentPropDefs[selectedName].value}
|
|
575
|
+
onchange={(e) =>
|
|
576
|
+
updateProp(
|
|
577
|
+
selectedName,
|
|
578
|
+
e.target.value,
|
|
579
|
+
allCurrentPropDefs[selectedName].suffix,
|
|
580
|
+
e.target,
|
|
581
|
+
)}
|
|
582
|
+
/>
|
|
583
|
+
<span class="current-value">
|
|
584
|
+
{allCurrentPropDefs[selectedName].displayed}
|
|
585
|
+
</span>
|
|
586
|
+
{:else if choices.type == "select"}
|
|
587
|
+
{@const choices = allCurrentPropDefs[selectedName].choices()}
|
|
588
|
+
<select onchange={(e) => updateProp(selectedName, e.target.value)}>
|
|
589
|
+
{#if !choices.includes(allCurrentPropDefs[selectedName].value)}
|
|
590
|
+
<option selected="true"> --- </option>
|
|
591
|
+
{/if}
|
|
592
|
+
{#each choices as choice}
|
|
593
|
+
<option selected={choice == allCurrentPropDefs[selectedName].value || null}>
|
|
594
|
+
{choice}
|
|
595
|
+
</option>
|
|
596
|
+
{/each}
|
|
597
|
+
</select>
|
|
598
|
+
{:else if choices.type == "color"}
|
|
599
|
+
<ColorPicker
|
|
600
|
+
value={allCurrentPropDefs[selectedName].value}
|
|
601
|
+
onChange={(color) => updateProp(selectedName, color)}
|
|
602
|
+
/>
|
|
603
|
+
{/if}
|
|
604
|
+
</div>
|
|
605
|
+
{/each}
|
|
606
|
+
{#if currentRule === "inline" && bringableToFront[selectedElemIndex] !== null}
|
|
607
|
+
<div class="btn" class:active={bringableToFront[selectedElemIndex] === true} onclick={bringToFront}>
|
|
608
|
+
Bring to front
|
|
609
|
+
</div>
|
|
610
|
+
{/if}
|
|
611
|
+
{#if currentRule === "inline" && inlineDeletable(currentElement)}
|
|
612
|
+
<div class="btn delete-elem" onclick={deleteElem}>Delete element</div>
|
|
613
|
+
{/if}
|
|
614
|
+
</div>
|
|
615
|
+
{/if}
|
|
616
|
+
</div>
|
package/src/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { mount } from 'svelte';
|
|
2
|
+
import StyleEditor from './components/InlineStyleEditor.svelte';
|
|
3
|
+
export default class InlineStyleEditor {
|
|
4
|
+
constructor(options) {
|
|
5
|
+
const { target = document.body, ...props } = options;
|
|
6
|
+
return mount(StyleEditor, { target, props });
|
|
7
|
+
}
|
|
8
|
+
}
|