highlighter-pen 1.0.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/README.md ADDED
@@ -0,0 +1,49 @@
1
+ <a href="https://www.morganbkf.com/highlighter-pen/" target="_blank">
2
+ <img src="https://github.com/mimoklef/highlighter-pen/blob/main/assets/demotitle.png?raw=true" />
3
+ </a>
4
+
5
+
6
+ A simple JavaScript library that replaces the native text selection with a marker-like highlight effect.
7
+
8
+ It behaves like a real highlighter: drag your mouse over text and see a custom overlay instead of the default selection.
9
+
10
+ <a href="https://www.morganbkf.com/highlighter-pen/" target="_blank">
11
+ <img width=40% src="https://github.com/mimoklef/highlighter-pen/blob/main/assets/subtitle.jpg?raw=true" />
12
+ </a>
13
+
14
+
15
+
16
+ ## ✨ Features
17
+
18
+ - Marker-style highlight effect
19
+ - Works across multiple lines
20
+ - Keeps native selection inside inputs and textareas
21
+ - No dependencies
22
+ - Plug & play (1 script)
23
+
24
+
25
+
26
+ ## 🚀 Demo
27
+
28
+ [You can access this demo and try it yourself](https://www.morganbkf.com/highlighter-pen/)
29
+
30
+
31
+
32
+ ## 📦 Installation
33
+
34
+ ### CDN (recommended)
35
+
36
+ ```html
37
+ <script src="https://cdn.jsdelivr.net/gh/mimoklef/highlighter-pen@v1.0.0/dist/highlighter-pen.js"></script>
38
+ <script>
39
+ HighlighterPen().init();
40
+ </script>
41
+ ```
42
+
43
+
44
+
45
+ ## 🙋🏻‍♂️ Author
46
+
47
+
48
+ Made with ❤️ by [Morgan Bouyakhlef](https://www.morganbkf.com/)
49
+
@@ -0,0 +1,216 @@
1
+ /*!
2
+ * Highlighter pen v1.0.0
3
+ * Marker-like selection overlay using <marker> + optional native input selection yellow.
4
+ * https://github.com/mimoklef/highlighter-pen/
5
+ * © 2026 Morgan Bouyakhlef
6
+ * Released under the MIT License
7
+ */
8
+ (function (global) {
9
+ "use strict";
10
+
11
+ const DEFAULTS = {
12
+ markerImage: "https://cdn.jsdelivr.net/gh/mimoklef/highlighter-pen@v1.0.1/assets/marker.png",
13
+ markerZIndex: 10, // marker overlays on text
14
+ hideNativeSelection: true, // hide ::selection (so only your marker is visible)
15
+ inputSelectionYellow: true, // keep native selection in inputs/textarea and tint it yellow
16
+ inputSelectionColor: "rgba(255,235,59,0.95)",
17
+ exclude: "input, textarea, select, button, label" // clicking these cancels selection
18
+ };
19
+
20
+ function createStyle(id, cssText) {
21
+ let el = document.getElementById(id);
22
+ if (el) return el;
23
+ el = document.createElement("style");
24
+ el.id = id;
25
+ el.textContent = cssText;
26
+ document.head.appendChild(el);
27
+ return el;
28
+ }
29
+
30
+ function isTextLikeInput(el) {
31
+ if (!el) return false;
32
+ if (el.tagName === "TEXTAREA") return true;
33
+ if (el.tagName !== "INPUT") return false;
34
+ const type = (el.getAttribute("type") || "text").toLowerCase();
35
+ return ["text", "email", "password", "search", "tel", "url", "number"].includes(type);
36
+ }
37
+
38
+ function HighlighterPen(userOptions) {
39
+ const opt = Object.assign({}, DEFAULTS, userOptions || {});
40
+ const STYLE_MAIN_ID = "hp-style-main";
41
+ const STYLE_INPUT_ID = "hp-style-input";
42
+ const STYLE_HIDESEL_ID = "hp-style-hidesel";
43
+
44
+ let overlays = [];
45
+ let padTop = 0;
46
+ let padBottom = 0;
47
+ let destroyed = false;
48
+
49
+ let onSelectionChange, onScroll, onResize, onPointerDown;
50
+
51
+ function injectStyles() {
52
+ createStyle(
53
+ STYLE_MAIN_ID,
54
+ `
55
+ marker{
56
+ border-image-source: url("${opt.markerImage}");
57
+ border-image-slice: 0 27 0 29 fill;
58
+ border-image-width: 0px 1ch 0px 1ch;
59
+ border-image-outset: 0px 1ch 0px 1ch;
60
+ border-image-repeat: round stretch;
61
+ background-color: transparent;
62
+ -webkit-box-decoration-break: clone;
63
+ box-decoration-break: clone;
64
+ padding: 0.8em 0;
65
+ mix-blend-mode: multiply;
66
+ opacity: 1;
67
+ z-index: ${opt.markerZIndex} !important;
68
+ }
69
+ `.trim()
70
+ );
71
+
72
+ if (opt.hideNativeSelection) {
73
+ createStyle(
74
+ STYLE_HIDESEL_ID,
75
+ `
76
+ ::selection { background: none; }
77
+ ::-moz-selection { background: none; }
78
+ `.trim()
79
+ );
80
+ }
81
+
82
+ if (opt.inputSelectionYellow) {
83
+ createStyle(
84
+ STYLE_INPUT_ID,
85
+ `
86
+ input::selection, textarea::selection { background: ${opt.inputSelectionColor}; color: inherit; }
87
+ input::-moz-selection, textarea::-moz-selection { background: ${opt.inputSelectionColor}; color: inherit; }
88
+ `.trim()
89
+ );
90
+ }
91
+ }
92
+
93
+ function readMarkerPaddingPx() {
94
+ const t = document.createElement("marker");
95
+ t.textContent = "A";
96
+ t.style.position = "fixed";
97
+ t.style.left = "-9999px";
98
+ t.style.top = "-9999px";
99
+ t.style.pointerEvents = "none";
100
+ document.body.appendChild(t);
101
+
102
+ const cs = getComputedStyle(t);
103
+ padTop = parseFloat(cs.paddingTop) || 0;
104
+ padBottom = parseFloat(cs.paddingBottom) || 0;
105
+
106
+ t.remove();
107
+ }
108
+
109
+ function clearOverlays() {
110
+ overlays.forEach((el) => el.remove());
111
+ overlays = [];
112
+ }
113
+
114
+ function draw() {
115
+ if (destroyed) return;
116
+
117
+ const sel = window.getSelection();
118
+ if (!sel || sel.rangeCount === 0 || sel.isCollapsed) {
119
+ clearOverlays();
120
+ return;
121
+ }
122
+
123
+ // Keep native selection (yellow) inside text inputs/textarea
124
+ const ae = document.activeElement;
125
+ if (isTextLikeInput(ae)) {
126
+ clearOverlays();
127
+ return;
128
+ }
129
+
130
+ const range = sel.getRangeAt(0);
131
+
132
+ // avoid nesting markers
133
+ const sp = range.startContainer.parentElement;
134
+ const ep = range.endContainer.parentElement;
135
+ if ((sp && sp.closest("marker")) || (ep && ep.closest("marker"))) return;
136
+
137
+ const rects = Array.from(range.getClientRects()).filter(
138
+ (r) => r.width > 1 && r.height > 1
139
+ );
140
+
141
+ clearOverlays();
142
+ if (!rects.length) return;
143
+
144
+ for (const r of rects) {
145
+ const m = document.createElement("marker");
146
+
147
+ m.style.position = "fixed";
148
+ m.style.left = `${r.left}px`;
149
+ m.style.top = `${Math.round(r.top - padTop)}px`;
150
+ m.style.width = `${Math.round(r.width)}px`;
151
+ m.style.height = `${Math.round(r.height + padTop + padBottom)}px`;
152
+ m.style.display = "block";
153
+ m.style.pointerEvents = "none";
154
+ m.style.margin = "0";
155
+
156
+ // avoid double-padding (already included in top/height)
157
+ m.style.padding = "0";
158
+
159
+ document.body.appendChild(m);
160
+ overlays.push(m);
161
+ }
162
+ }
163
+
164
+ function cancelSelection() {
165
+ const sel = window.getSelection();
166
+ if (sel) sel.removeAllRanges();
167
+ clearOverlays();
168
+ }
169
+
170
+ function init() {
171
+ injectStyles();
172
+
173
+ requestAnimationFrame(() => {
174
+ if (destroyed) return;
175
+ readMarkerPaddingPx();
176
+ draw();
177
+ });
178
+
179
+ onSelectionChange = () => requestAnimationFrame(draw);
180
+ onScroll = () => requestAnimationFrame(draw);
181
+ onResize = () => requestAnimationFrame(draw);
182
+
183
+ onPointerDown = (e) => {
184
+ const el = e.target && e.target.closest(opt.exclude);
185
+ if (!el) return;
186
+
187
+ // keep native selection inside text-like inputs/textarea
188
+ if (isTextLikeInput(el)) return;
189
+
190
+ cancelSelection();
191
+ };
192
+
193
+ document.addEventListener("selectionchange", onSelectionChange);
194
+ window.addEventListener("scroll", onScroll, { passive: true });
195
+ window.addEventListener("resize", onResize);
196
+ document.addEventListener("pointerdown", onPointerDown, true);
197
+
198
+ return api;
199
+ }
200
+
201
+ function destroy() {
202
+ destroyed = true;
203
+ clearOverlays();
204
+
205
+ document.removeEventListener("selectionchange", onSelectionChange);
206
+ window.removeEventListener("scroll", onScroll);
207
+ window.removeEventListener("resize", onResize);
208
+ document.removeEventListener("pointerdown", onPointerDown, true);
209
+ }
210
+
211
+ const api = { init, draw, clear: clearOverlays, cancelSelection, destroy, options: opt };
212
+ return api;
213
+ }
214
+
215
+ global.HighlighterPen = HighlighterPen;
216
+ })(typeof window !== "undefined" ? window : this);
package/package.json ADDED
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "highlighter-pen",
3
+ "version": "1.0.1",
4
+ "description": "Marker-like text selection overlay (live) using JavaScript + CSS injection",
5
+ "license": "MIT",
6
+ "main": "dist/highlighter-pen.js",
7
+ "files": ["dist"],
8
+ "author": "Morgan Bouyakhlef",
9
+ "keywords": ["highlight", "highlight pen", "text-selection", "highlighter", "highlighter pen", "javascript", "css", "ui", "ux", "dom", "pen"]
10
+ }