inline-style-editor 1.4.2 → 1.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "inline-style-editor",
3
- "version": "1.4.2",
3
+ "version": "1.5.3",
4
4
  "description": "Update CSS rules or add inline style to elements visualy",
5
5
  "scripts": {
6
6
  "build": "rollup --config",
@@ -38,21 +38,13 @@
38
38
  margin: auto;
39
39
  }
40
40
 
41
- .prop-name {
42
- display: flex;
43
- align-items: center;
44
-
45
- &>span {
46
- margin-left: 4px;
47
- }
41
+ .current-value {
42
+ font-size: 12px;
43
+ color: #64748b;
44
+ min-width: 40px;
45
+ text-align: right;
48
46
  }
49
47
 
50
- .delete {
51
- cursor: pointer;
52
- font-size: 20px;
53
- margin: 0 4px;
54
- margin-bottom: 2px;
55
- }
56
48
 
57
49
  .select-tab {
58
50
  display: flex;
@@ -91,21 +83,100 @@
91
83
  }
92
84
 
93
85
  .editor .prop-section {
94
- display: flex;
95
- align-items: center;
96
- margin: 5px 0;
97
- align-items: baseline;
86
+ background: #f8fafc;
87
+ border-radius: 6px;
88
+ padding: 8px;
89
+ margin: 6px 0;
90
+
91
+ &:first-child {
92
+ margin-top: 0;
93
+ }
94
+
95
+ &.slider {
96
+ .prop-header + input[type="range"] {
97
+ display: inline-block;
98
+ width: calc(100% - 50px);
99
+ vertical-align: middle;
100
+ }
101
+
102
+ .current-value {
103
+ display: inline-block;
104
+ vertical-align: middle;
105
+ }
106
+ }
98
107
  }
99
108
 
100
- .editor .prop-section>span {
101
- margin: 0 5px;
109
+ .prop-header {
110
+ display: flex;
111
+ align-items: center;
112
+ justify-content: space-between;
113
+ margin-bottom: 6px;
102
114
  }
103
115
 
104
- .editor .prop-section :first-child {
116
+ .prop-name {
117
+ display: flex;
118
+ align-items: center;
119
+ gap: 8px;
105
120
  color: #5d5d5d;
106
121
  font-weight: bold;
107
122
  }
108
123
 
124
+ .selected-label {
125
+ font-size: 12px;
126
+ color: #64748b;
127
+ font-weight: 500;
128
+ }
129
+
130
+ .close-button,
131
+ .delete-btn {
132
+ display: flex;
133
+ align-items: center;
134
+ justify-content: center;
135
+ width: 20px;
136
+ height: 20px;
137
+ padding: 0;
138
+ border: none;
139
+ cursor: pointer;
140
+ flex-shrink: 0;
141
+ transition: background 0.15s ease, color 0.15s ease, transform 0.15s ease;
142
+
143
+ svg {
144
+ width: 12px;
145
+ height: 12px;
146
+ }
147
+ }
148
+
149
+ .delete-btn {
150
+ border-radius: 4px;
151
+ background: transparent;
152
+ color: #94a3b8;
153
+
154
+ &:hover {
155
+ background: #fee2e2;
156
+ color: #dc2626;
157
+ }
158
+ }
159
+
160
+ .close-button {
161
+ position: absolute;
162
+ top: -8px;
163
+ right: -8px;
164
+ border-radius: 50%;
165
+ background: #64748b;
166
+ color: white;
167
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
168
+
169
+ &:hover {
170
+ background: #475569;
171
+ transform: scale(1.1);
172
+ }
173
+
174
+ svg {
175
+ width: 10px;
176
+ height: 10px;
177
+ }
178
+ }
179
+
109
180
  .editor .btn {
110
181
  &.active {
111
182
  background-color: #4d84bf;
@@ -125,19 +196,41 @@
125
196
  padding: 5px;
126
197
  }
127
198
 
128
- .close-button {
129
- position: absolute;
130
- top: -7px;
131
- right: -7px;
132
- background-color: #dbdbdb;
133
- color: #818181;
134
- width: 15px;
135
- height: 15px;
199
+ .icon-selector {
136
200
  display: flex;
137
- justify-content: center;
201
+ gap: 2px;
138
202
  align-items: center;
203
+ }
204
+
205
+ .icon-btn {
206
+ display: flex;
207
+ align-items: center;
208
+ justify-content: center;
209
+ width: 26px;
210
+ height: 26px;
211
+ padding: 4px;
212
+ border: 1.5px solid transparent;
213
+ border-radius: 5px;
214
+ background: transparent;
139
215
  cursor: pointer;
140
- border-radius: 3px;
216
+ color: #94a3b8;
217
+
218
+ &:hover {
219
+ background: #e2e8f0;
220
+ color: #475569;
221
+ }
222
+
223
+ &.selected {
224
+ background: white;
225
+ border-color: #3b82f6;
226
+ color: #2563eb;
227
+ box-shadow: 0 1px 3px 0 rgba(59, 130, 246, 0.3), 0 0 0 1px rgba(59, 130, 246, 0.1);
228
+ }
229
+
230
+ svg {
231
+ width: 16px;
232
+ height: 16px;
233
+ }
141
234
  }
142
235
 
143
236
  .layout_default.picker_wrapper {
@@ -20,6 +20,7 @@
20
20
  "textPath",
21
21
  "tref",
22
22
  "tspan",
23
+ "g",
23
24
  ];
24
25
 
25
26
  const borderProps = ["border-radius", "border-width", "border-color", "border-style"];
@@ -55,6 +56,23 @@
55
56
  "background-color": { type: "color" },
56
57
  };
57
58
 
59
+ const propertyIcons = {
60
+ stroke: `<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><line x1="2" y1="14" x2="14" y2="2"/></svg>`,
61
+ fill: `<svg viewBox="0 0 16 16" fill="currentColor"><rect x="2" y="2" width="12" height="12" rx="2"/></svg>`,
62
+ color: `<svg viewBox="0 0 16 16" fill="currentColor"><text x="3" y="11" font-size="10" font-weight="bold">A</text><rect x="2" y="13" width="12" height="2"/></svg>`,
63
+ "border-color": `<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="2" y="2" width="12" height="12" rx="1"/></svg>`,
64
+ "background-color": `<svg viewBox="0 0 16 16" fill="currentColor"><rect x="4" y="4" width="10" height="10" rx="1" opacity="0.5"/><rect x="2" y="2" width="10" height="10" rx="1"/></svg>`,
65
+ "border-radius": `<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M2 10 L2 2 L10 2" stroke-linecap="round"/><path d="M2 10 Q2 14 6 14 L14 14" stroke-linecap="round"/></svg>`,
66
+ "border-width": `<svg viewBox="0 0 16 16" fill="currentColor"><rect x="2" y="2" width="12" height="2"/><rect x="2" y="7" width="12" height="3"/><rect x="2" y="13" width="12" height="1"/></svg>`,
67
+ "border-style": `<svg viewBox="0 0 16 16" fill="currentColor"><rect x="2" y="2" width="12" height="1.5"/><rect x="2" y="7" width="3" height="1.5"/><rect x="7" y="7" width="3" height="1.5"/><rect x="2" y="12" width="1.5" height="1.5"/><rect x="5" y="12" width="1.5" height="1.5"/><rect x="8" y="12" width="1.5" height="1.5"/><rect x="11" y="12" width="1.5" height="1.5"/></svg>`,
68
+ "font-size": `<svg viewBox="0 0 16 16" fill="currentColor"><text x="1" y="12" font-size="12" font-weight="bold">T</text><text x="9" y="12" font-size="8" font-weight="bold">T</text></svg>`,
69
+ "font-weight": `<svg viewBox="0 0 16 16" fill="currentColor"><text x="3" y="13" font-size="14" font-weight="bold">B</text></svg>`,
70
+ "font-family": `<svg viewBox="0 0 16 16" fill="currentColor"><text x="4" y="13" font-size="14" font-style="italic" font-family="serif">F</text></svg>`,
71
+ "stroke-width": `<svg viewBox="0 0 16 16" fill="currentColor"><rect x="2" y="2" width="12" height="2"/><rect x="2" y="7" width="12" height="3"/><rect x="2" y="13" width="12" height="1"/></svg>`,
72
+ "stroke-dasharray": `<svg viewBox="0 0 16 16" fill="currentColor"><rect x="1" y="7" width="4" height="2"/><rect x="7" y="7" width="4" height="2"/><rect x="13" y="7" width="2" height="2"/></svg>`,
73
+ "stroke-linejoin": `<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linejoin="round"><polyline points="3,12 8,4 13,12"/></svg>`,
74
+ };
75
+
58
76
  // Props
59
77
  const props = $props();
60
78
  const getElems = props.getElems ?? null;
@@ -69,6 +87,7 @@
69
87
  if (cssRuleName === "inline") return "Selected element";
70
88
  return cssRuleName;
71
89
  });
90
+ const ignoredProps = props.ignoredProps ?? [];
72
91
 
73
92
  const typeText = "text";
74
93
  const typeBorder = "border";
@@ -153,6 +172,7 @@
153
172
  function initAndGroup() {
154
173
  const allProps = { ...cssPropByType, ...customProps };
155
174
  const _allCurrentPropDefs = pick(allProps, propByType[curType]);
175
+ ignoredProps.forEach((prop) => delete _allCurrentPropDefs[prop]);
156
176
  Object.keys(_allCurrentPropDefs).forEach((key) => {
157
177
  const propSelectType = _allCurrentPropDefs[key].type;
158
178
  let retrieveType = "number";
@@ -172,7 +192,7 @@
172
192
  }
173
193
  });
174
194
 
175
- propsByType = Object.entries(_allCurrentPropDefs)
195
+ const _propsByType = Object.entries(_allCurrentPropDefs)
176
196
  .reduce((byType, [propName, selectorDef]) => {
177
197
  const selectorType = selectorDef.type;
178
198
  const existing = byType.find((x) => x.type === selectorType);
@@ -190,6 +210,18 @@
190
210
  if (inputTypeOrder[a.type] > inputTypeOrder[b.type]) return 1;
191
211
  return 0;
192
212
  });
213
+
214
+ // Pre-select fill over stroke if the element has a non-transparent fill
215
+ const colorGroup = _propsByType.find((g) => g.type === "color");
216
+ if (colorGroup) {
217
+ const fillIndex = colorGroup.props.indexOf("fill");
218
+ const fillValue = _allCurrentPropDefs["fill"]?.value;
219
+ if (fillIndex !== -1 && fillValue && fillValue !== "#00000000") {
220
+ colorGroup.selected = fillIndex;
221
+ }
222
+ }
223
+
224
+ propsByType = _propsByType;
193
225
  allCurrentPropDefs = _allCurrentPropDefs;
194
226
  updateHelpers();
195
227
  }
@@ -248,7 +280,10 @@
248
280
  return elems.reduce((typesByElem, elemDef) => {
249
281
  const elem = elemDef[0];
250
282
  const types = [];
251
- if (elem.firstChild && (elem.firstChild.nodeType === 3 || elem.firstChild.tagName === "tspan")) {
283
+ if (
284
+ elem.firstChild &&
285
+ ((!elem.firstElementChild && elem.firstChild.nodeType === 3) || elem.firstChild.tagName === "tspan")
286
+ ) {
252
287
  // Node.TEXT_NODE
253
288
  types.push(typeText);
254
289
  }
@@ -444,13 +479,37 @@
444
479
  close();
445
480
  }
446
481
  function deleteProp(propName) {
447
- if (currentRule === "inline") {
448
- currentElement.style.removeProperty(propName);
482
+ const propDef = allCurrentPropDefs[propName];
483
+ const isCustomProp = propName in customProps;
484
+
485
+ // Check if custom prop has a defaultValue
486
+ if (isCustomProp && propDef && "defaultValue" in customProps[propName]) {
487
+ const defaultValue = customProps[propName].defaultValue;
488
+ if (propDef.setter) {
489
+ propDef.setter(currentElement, defaultValue);
490
+ }
491
+ propDef.value = defaultValue;
492
+ propDef.displayed = defaultValue;
493
+ onStyleChanged(currentElement, currentRule, propName, defaultValue);
449
494
  } else {
450
- currentRule.style.removeProperty(propName);
495
+ // Standard CSS property reset
496
+ if (currentRule === "inline") {
497
+ currentElement.style.removeProperty(propName);
498
+ } else {
499
+ currentRule.style.removeProperty(propName);
500
+ }
501
+ onStyleChanged(currentElement, currentRule, propName, null);
502
+ // Update the displayed value without rebuilding (which would reset selection)
503
+ if (propDef) {
504
+ const propSelectType = propDef.type;
505
+ let retrieveType = "number";
506
+ if (propSelectType === "color") retrieveType = "rgb";
507
+ else if (propSelectType === "select") retrieveType = "raw";
508
+ propDef.displayed = getComputedPropValue(currentElement, propName, "raw");
509
+ propDef.value = getComputedPropValue(currentElement, propName, retrieveType);
510
+ }
451
511
  }
452
- onStyleChanged(currentElement, currentRule, propName, null);
453
- initAndGroup();
512
+ updateHelpers();
454
513
  }
455
514
 
456
515
  function selectRule(ruleIndex) {
@@ -480,7 +539,11 @@
480
539
  </svg>
481
540
 
482
541
  <div class="ise" bind:this={self}>
483
- <div class="close-button" onclick={close}>x</div>
542
+ <button class="close-button" title="Close" onclick={close}>
543
+ <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.5">
544
+ <path d="M4 4 L12 12 M12 4 L4 12" />
545
+ </svg>
546
+ </button>
484
547
  {#if targetsToSearch.length > 1}
485
548
  <div class="select-tab">
486
549
  <b> Element </b>
@@ -497,73 +560,89 @@
497
560
  {/each}
498
561
  </div>
499
562
  {/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)}>
563
+ {#if allRules[selectedElemIndex]?.length > 1}
564
+ <div class="select-tab">
565
+ <b> Applied to: </b>
566
+ {#if nbChars(getRuleNamesTransformed(allRules[selectedElemIndex])) > 30}
567
+ <select onchange={(e) => selectRule(e.target.value)}>
568
+ {#each getRuleNames(allRules[selectedElemIndex]) as ruleName, ruleIndex}
569
+ <option selected={selectedRuleIndex === ruleIndex} value={ruleIndex}
570
+ >{getCssRuleName(ruleName, clickedElement)}</option
571
+ >
572
+ {/each}
573
+ </select>
574
+ {:else}
504
575
  {#each getRuleNames(allRules[selectedElemIndex]) as ruleName, ruleIndex}
505
- <option selected={selectedRuleIndex === ruleIndex} value={ruleIndex}
506
- >{getCssRuleName(ruleName, clickedElement)}</option
576
+ <span
577
+ title={ruleName}
578
+ class:selected={selectedRuleIndex === ruleIndex}
579
+ onclick={() => {
580
+ selectRule(ruleIndex);
581
+ }}
582
+ >
583
+ {getCssRuleName(ruleName, clickedElement)}</span
507
584
  >
508
585
  {/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
586
  {/if}
538
- {/each}
539
- </div>
587
+ </div>
588
+ {/if}
589
+ {#if (allTypes[selectedElemIndex] || []).filter((t) => t !== "custom" || (currentRule === "inline" && hasDisplayedCustom)).length > 1}
590
+ <div class="select-tab">
591
+ <b> Property type: </b>
592
+ {#each allTypes[selectedElemIndex] || [] as type, typeIndex}
593
+ <!-- Only display "custom" on "inline" rule -->
594
+ {#if type !== "custom" || (currentRule === "inline" && type === "custom" && hasDisplayedCustom)}
595
+ <span
596
+ class:selected={selectedTypeIndex === typeIndex}
597
+ onclick={() => {
598
+ selectedTypeIndex = typeIndex;
599
+ }}
600
+ >
601
+ {type === "stroke" ? "SVG paint" : capitalizeFirstLetter(type)}
602
+ </span>
603
+ {/if}
604
+ {/each}
605
+ </div>
606
+ {/if}
540
607
  {#if allTypes[selectedElemIndex]}
541
608
  <div class="editor">
542
609
  {#each propsByType as choices}
543
610
  {@const selectedName = choices.props[choices.selected]}
544
611
  <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>
612
+ <div class="prop-header">
613
+ <div class="prop-name">
614
+ {#if choices.props.length > 1}
615
+ <div class="icon-selector">
616
+ {#each choices.props as propName, i}
617
+ <button
618
+ class="icon-btn"
619
+ class:selected={i === choices.selected}
620
+ title={choices.type === "color"
621
+ ? `${capitalizeFirstLetter(propName)} color`
622
+ : pascalCaseToSentence(propName)}
623
+ onclick={async () => {
624
+ choices.selected = i;
625
+ await tick();
626
+ }}
627
+ >
628
+ {@html propertyIcons[propName]}
629
+ </button>
630
+ {/each}
631
+ </div>
632
+ <span class="selected-label"
633
+ >{choices.type === "color"
634
+ ? `${capitalizeFirstLetter(selectedName)} color`
635
+ : pascalCaseToSentence(selectedName)}</span
636
+ >
637
+ {:else}
638
+ <span>{pascalCaseToSentence(selectedName)}</span>
639
+ {/if}
640
+ </div>
641
+ <button class="delete-btn" title="Reset to default" onclick={() => deleteProp(selectedName)}>
642
+ <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2">
643
+ <path d="M4 4 L12 12 M12 4 L4 12" />
644
+ </svg>
645
+ </button>
567
646
  </div>
568
647
  {#if choices.type === "slider"}
569
648
  <input