react-text-range 1.0.5 → 1.0.7

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 CHANGED
@@ -1 +1,37 @@
1
1
  # react-text-range
2
+
3
+ ## using
4
+
5
+ ```js
6
+ // ...
7
+ import { TextContainer, RangeState, ReactTextRange } from "./ReactTextRange";
8
+
9
+ const MyTextContainer: TextContainer = React.forwardRef(({ children }, ref) =>
10
+ <div ref={ref} style={{
11
+ fontSize: 30,
12
+ width: '320px',
13
+ backgroundColor: 'rgba(253, 224, 71, .2)',
14
+ userSelect: 'none',
15
+ padding: '20px',
16
+ }}>
17
+ {children}
18
+ </div>
19
+ );
20
+
21
+ const App: FunctionComponent = () => {
22
+ const [myPos, setMyPos] = useState<RangeState>({ left: 23, right: 37 })
23
+ return (
24
+ <div style={{ margin: 20 }}>
25
+ <ReactTextRange initLeftPos={23} initRightPos={37}
26
+ Container={MyTextContainer} onChange={setMyPos}>
27
+ Some text or even some real good text here and there and here again
28
+ </ReactTextRange>
29
+ <div>
30
+ <span>{myPos?.left}</span>
31
+ &nbsp;
32
+ <span>{myPos?.right}</span>
33
+ </div>
34
+ </div>
35
+ )
36
+ }
37
+ ```
@@ -1,5 +1,5 @@
1
1
  import React, { FC } from 'react';
2
- import './main.css';
2
+ import './index.css';
3
3
  export declare const FullWindow: FC<{
4
4
  children: React.ReactNode;
5
5
  }>;
@@ -0,0 +1,17 @@
1
+ import { FC } from 'react';
2
+ import React from 'react';
3
+ export type TextContainer = React.ForwardRefExoticComponent<{
4
+ children: React.ReactNode;
5
+ } & React.RefAttributes<HTMLDivElement>>;
6
+ export interface RangeState {
7
+ left: number;
8
+ right: number;
9
+ }
10
+ export declare const ReactTextRange: FC<{
11
+ initLeftPos: number;
12
+ initRightPos: number;
13
+ Container: TextContainer;
14
+ children: string;
15
+ onChange: (state: RangeState) => void;
16
+ }>;
17
+ export default ReactTextRange;
@@ -1,6 +1,6 @@
1
1
  import { FC } from 'react';
2
2
  import { HandlerPos } from './handler-pos';
3
- import './main.css';
3
+ import './index.css';
4
4
  export declare const SelectionHandler: FC<{
5
5
  pos: HandlerPos | null;
6
6
  grab: boolean;
@@ -1,7 +1,12 @@
1
1
  import { FC } from 'react';
2
- export declare const TextSelectionZone: FC<{
2
+ import React from 'react';
3
+ export type TextContainer = React.ForwardRefExoticComponent<{
4
+ children: React.ReactNode;
5
+ } & React.RefAttributes<HTMLDivElement>>;
6
+ export declare const ReactTextRange: FC<{
3
7
  initLeftPos: number;
4
8
  initRightPos: number;
9
+ Container: TextContainer;
5
10
  children: string;
6
11
  }>;
7
- export default TextSelectionZone;
12
+ export default ReactTextRange;
@@ -2,4 +2,5 @@ export interface HandlerPos {
2
2
  left: number;
3
3
  top: number;
4
4
  height: number;
5
+ pos: number;
5
6
  }
@@ -0,0 +1 @@
1
+ export {};
@@ -1,3 +1,2 @@
1
- import FullWindow from "./FullWindow";
2
- import TextSelectionZone from "./TextSelectionZone";
3
- export { FullWindow, TextSelectionZone };
1
+ import { TextContainer, RangeState, ReactTextRange } from "./ReactTextRange";
2
+ export { RangeState, TextContainer, ReactTextRange };
@@ -0,0 +1,6 @@
1
+ export interface Rect {
2
+ top: number;
3
+ left: number;
4
+ width: number;
5
+ height: number;
6
+ }
@@ -1,5 +1,6 @@
1
1
  import React from 'react';
2
2
  import { HandlerPos } from './handler-pos';
3
+ import { Rect } from './rect';
3
4
  declare global {
4
5
  interface Document {
5
6
  caretPositionFromPoint: any;
@@ -9,5 +10,5 @@ export declare const useTextSelectionEditor: (initLeftPos: number, initRightPos:
9
10
  React.MutableRefObject<HTMLDivElement | null>,
10
11
  HandlerPos | null,
11
12
  HandlerPos | null,
12
- DOMRectList | null
13
+ Rect[] | null
13
14
  ];
@@ -0,0 +1,17 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Document</title>
8
+ </head>
9
+
10
+ <body>
11
+ <div id="root"></div>
12
+ <script defer src="/index.js"></script>
13
+ </body>
14
+
15
+ </body>
16
+
17
+ </html>
package/dist/cjs/index.js CHANGED
@@ -3,75 +3,33 @@
3
3
  var React = require('react');
4
4
 
5
5
  function _interopNamespaceDefault(e) {
6
- var n = Object.create(null);
7
- if (e) {
8
- Object.keys(e).forEach(function (k) {
9
- if (k !== 'default') {
10
- var d = Object.getOwnPropertyDescriptor(e, k);
11
- Object.defineProperty(n, k, d.get ? d : {
12
- enumerable: true,
13
- get: function () { return e[k]; }
6
+ var n = Object.create(null);
7
+ if (e) {
8
+ Object.keys(e).forEach(function (k) {
9
+ if (k !== 'default') {
10
+ var d = Object.getOwnPropertyDescriptor(e, k);
11
+ Object.defineProperty(n, k, d.get ? d : {
12
+ enumerable: true,
13
+ get: function () { return e[k]; }
14
+ });
15
+ }
14
16
  });
15
- }
16
- });
17
- }
18
- n.default = e;
19
- return Object.freeze(n);
20
- }
21
-
22
- var React__namespace = /*#__PURE__*/_interopNamespaceDefault(React);
23
-
24
- function styleInject(css, ref) {
25
- if ( ref === void 0 ) ref = {};
26
- var insertAt = ref.insertAt;
27
-
28
- if (!css || typeof document === 'undefined') { return; }
29
-
30
- var head = document.head || document.getElementsByTagName('head')[0];
31
- var style = document.createElement('style');
32
- style.type = 'text/css';
33
-
34
- if (insertAt === 'top') {
35
- if (head.firstChild) {
36
- head.insertBefore(style, head.firstChild);
37
- } else {
38
- head.appendChild(style);
39
17
  }
40
- } else {
41
- head.appendChild(style);
42
- }
43
-
44
- if (style.styleSheet) {
45
- style.styleSheet.cssText = css;
46
- } else {
47
- style.appendChild(document.createTextNode(css));
48
- }
18
+ n.default = e;
19
+ return Object.freeze(n);
49
20
  }
50
21
 
51
- var css_248z = "@tailwind base;@tailwind components;@tailwind utilities;";
52
- styleInject(css_248z,{"insertAt":"top"});
53
-
54
- const fullWindow = {
55
- height: '100vh',
56
- widows: '100%',
57
- };
58
- const centroContainer = Object.assign(Object.assign({}, fullWindow), { display: 'flex', alignItems: 'center', justifyContent: 'center', flexDirection: 'column' });
59
- const FullWindow = ({ children }) => {
60
- return (React.createElement("div", { style: centroContainer }, children));
61
- };
62
-
63
- const _TextContainer = ({ children }, ref) => {
64
- return (React.createElement("div", { ref: ref, draggable: false, className: "basis-80 w-80 bg-yellow-50 p-11 text-3xl select-none bg-transparent" }, children));
65
- };
66
- const TextContainer = React.forwardRef(_TextContainer);
22
+ var React__namespace = /*#__PURE__*/_interopNamespaceDefault(React);
67
23
 
68
24
  const getHandlerRect = (index, node) => {
69
25
  const range = document.createRange();
70
26
  range.setStart(node, index);
71
27
  range.setEnd(node, index);
72
- const rects = range.getClientRects();
73
- if (rects.length === 1) {
74
- return rects[0];
28
+ if (range.getClientRects) {
29
+ const rects = range.getClientRects();
30
+ if (rects.length === 1) {
31
+ return rects[0];
32
+ }
75
33
  }
76
34
  return null;
77
35
  };
@@ -79,7 +37,12 @@ const getAllRects = (node, start, end) => {
79
37
  const range = document.createRange();
80
38
  range.setStart(node, start);
81
39
  range.setEnd(node, end);
82
- return range.getClientRects();
40
+ if (range.getClientRects) {
41
+ return range.getClientRects();
42
+ }
43
+ else {
44
+ return new DOMRectList();
45
+ }
83
46
  };
84
47
  const getNodeAndOffsetFromPoint = (x, y) => {
85
48
  let range;
@@ -92,8 +55,10 @@ const getNodeAndOffsetFromPoint = (x, y) => {
92
55
  }
93
56
  else if (document.caretRangeFromPoint) {
94
57
  range = document.caretRangeFromPoint(x, y);
95
- textNode = range.startContainer;
96
- offset = range.startOffset;
58
+ if (range) {
59
+ textNode = range.startContainer;
60
+ offset = range.startOffset;
61
+ }
97
62
  }
98
63
  else {
99
64
  return null;
@@ -115,6 +80,11 @@ const useTextSelectionEditor = (initLeftPos, initRightPos, leftDrag, rightDrag)
115
80
  const [rects, setRects] = React.useState(null);
116
81
  // reference
117
82
  const textDiv = React.useRef(null);
83
+ React.useEffect(() => {
84
+ if (textDiv.current) {
85
+ textDiv.current.style.position = 'absolute';
86
+ }
87
+ }, [textDiv]);
118
88
  // mouse move handler
119
89
  // left handler
120
90
  React.useEffect(() => {
@@ -169,10 +139,12 @@ const useTextSelectionEditor = (initLeftPos, initRightPos, leftDrag, rightDrag)
169
139
  setRects(null);
170
140
  }
171
141
  else {
142
+ const divRect = textDiv.current.getBoundingClientRect();
172
143
  setLeftHandler({
173
144
  height: rect.height,
174
- left: rect.left,
175
- top: rect.top,
145
+ left: rect.left - divRect.left,
146
+ top: rect.top - divRect.top,
147
+ pos: currentLeftPos,
176
148
  });
177
149
  }
178
150
  }
@@ -188,10 +160,12 @@ const useTextSelectionEditor = (initLeftPos, initRightPos, leftDrag, rightDrag)
188
160
  setRects(null);
189
161
  }
190
162
  else {
163
+ const divRect = textDiv.current.getBoundingClientRect();
191
164
  setRightHandler({
192
165
  height: rect.height,
193
- left: rect.left,
194
- top: rect.top,
166
+ left: rect.left - divRect.left,
167
+ top: rect.top - divRect.top,
168
+ pos: currentRightPos,
195
169
  });
196
170
  }
197
171
  }
@@ -199,8 +173,26 @@ const useTextSelectionEditor = (initLeftPos, initRightPos, leftDrag, rightDrag)
199
173
  React.useEffect(() => {
200
174
  var _a;
201
175
  const n = (_a = textDiv.current) === null || _a === void 0 ? void 0 : _a.childNodes[0];
202
- if (n)
203
- setRects(getAllRects(n, currentLeftPos, currentRightPos));
176
+ if (!n) {
177
+ setRects(null);
178
+ return;
179
+ }
180
+ const rawRects = getAllRects(n, currentLeftPos, currentRightPos);
181
+ let rectArray = [];
182
+ if (rawRects && textDiv.current) {
183
+ const divRect = textDiv.current.getBoundingClientRect();
184
+ for (let i = 0; i < rawRects.length; ++i) {
185
+ const aa = rawRects.item(i);
186
+ if (aa)
187
+ rectArray.push({
188
+ height: aa.height,
189
+ left: aa.left - divRect.left,
190
+ top: aa.top - divRect.top,
191
+ width: aa.width,
192
+ });
193
+ }
194
+ }
195
+ setRects(rectArray);
204
196
  }, [currentLeftPos, currentRightPos]);
205
197
  // return
206
198
  return [textDiv, leftHandler, rightHandler, rects];
@@ -230,6 +222,36 @@ var SvgQuoteRight = function SvgQuoteRight(props) {
230
222
  })));
231
223
  };
232
224
 
225
+ function styleInject(css, ref) {
226
+ if ( ref === void 0 ) ref = {};
227
+ var insertAt = ref.insertAt;
228
+
229
+ if (!css || typeof document === 'undefined') { return; }
230
+
231
+ var head = document.head || document.getElementsByTagName('head')[0];
232
+ var style = document.createElement('style');
233
+ style.type = 'text/css';
234
+
235
+ if (insertAt === 'top') {
236
+ if (head.firstChild) {
237
+ head.insertBefore(style, head.firstChild);
238
+ } else {
239
+ head.appendChild(style);
240
+ }
241
+ } else {
242
+ head.appendChild(style);
243
+ }
244
+
245
+ if (style.styleSheet) {
246
+ style.styleSheet.cssText = css;
247
+ } else {
248
+ style.appendChild(document.createTextNode(css));
249
+ }
250
+ }
251
+
252
+ var css_248z = "/*! tailwindcss v3.3.3 | MIT License | https://tailwindcss.com*/*,:after,:before{border:0 solid #e5e7eb;box-sizing:border-box}:after,:before{--tw-content:\"\"}html{-webkit-text-size-adjust:100%;font-feature-settings:normal;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-variation-settings:normal;line-height:1.5;-moz-tab-size:4;tab-size:4}body{line-height:inherit;margin:0}hr{border-top-width:1px;color:inherit;height:0}abbr:where([title]){text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{border-collapse:collapse;border-color:inherit;text-indent:0}button,input,optgroup,select,textarea{font-feature-settings:inherit;color:inherit;font-family:inherit;font-size:100%;font-variation-settings:inherit;font-weight:inherit;line-height:inherit;margin:0;padding:0}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::placeholder,textarea::placeholder{color:#9ca3af;opacity:1}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{height:auto;max-width:100%}[hidden]{display:none}*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.absolute{position:absolute}.relative{position:relative}.flex{display:flex}.rounded-l-md{border-bottom-left-radius:.375rem;border-top-left-radius:.375rem}.rounded-r-md{border-bottom-right-radius:.375rem;border-top-right-radius:.375rem}.bg-yellow-300{--tw-bg-opacity:1;background-color:rgb(253 224 71/var(--tw-bg-opacity))}.opacity-80{opacity:.8}";
253
+ styleInject(css_248z,{"insertAt":"top"});
254
+
233
255
  const SelectionHandler = ({ pos, grab, setGrab, left }) => {
234
256
  return (pos &&
235
257
  React.createElement("div", { draggable: false, className: `bg-yellow-300 opacity-80 ${left ? 'rounded-l-md' : 'rounded-r-md'}`, style: {
@@ -255,34 +277,40 @@ const SelectionHandler = ({ pos, grab, setGrab, left }) => {
255
277
  : React.createElement(SvgQuoteRight, null)));
256
278
  };
257
279
 
258
- const TextSelectionZone = ({ initLeftPos, initRightPos, children, }) => {
280
+ const ReactTextRange = ({ initLeftPos, initRightPos, Container, children, onChange, }) => {
259
281
  const [mouseOnLeft, setMouseOnLeft] = React.useState(false);
260
282
  const [mouseOnRight, setMouseOnRight] = React.useState(false);
261
283
  const [textDiv, leftHandler, rightHandler, rects] = useTextSelectionEditor(initLeftPos, initRightPos, mouseOnLeft, mouseOnRight);
262
- let divs = [];
263
- if (rects) {
264
- for (let i = 0; i < rects.length; ++i) {
265
- const aa = rects.item(i);
266
- if (aa)
267
- divs.push(aa);
284
+ React.useEffect(() => {
285
+ if (leftHandler && rightHandler) {
286
+ onChange({
287
+ left: leftHandler.pos,
288
+ right: rightHandler.pos,
289
+ });
268
290
  }
269
- }
291
+ }, [leftHandler, rightHandler]);
270
292
  return (React.createElement("div", { draggable: false, style: {
271
- display: 'flex',
293
+ position: 'relative',
272
294
  } },
273
- divs.map((d, i) => React.createElement("div", { key: i, className: 'bg-yellow-300', style: {
274
- userSelect: 'none',
275
- position: 'absolute',
276
- top: `${d.top}px`,
277
- left: `${d.left}px`,
278
- width: `${d.width}px`,
279
- height: `${d.height}px`,
280
- zIndex: -2,
281
- } }, "\u00A0")),
295
+ React.createElement(SelectionRects, { rects: rects }),
296
+ React.createElement(Container, { ref: textDiv }, children),
282
297
  React.createElement(SelectionHandler, { grab: mouseOnLeft, left: true, pos: leftHandler, setGrab: (v) => setMouseOnLeft(v) }),
283
- React.createElement(TextContainer, { ref: textDiv }, children),
284
298
  React.createElement(SelectionHandler, { grab: mouseOnRight, left: false, pos: rightHandler, setGrab: (v) => setMouseOnRight(v) })));
285
299
  };
300
+ const SelectionRects = ({ rects }) => {
301
+ if (!rects)
302
+ return null;
303
+ return (React.createElement(React.Fragment, null, rects.map((d, i) => React.createElement(SelectionRect, { key: i, rect: d }))));
304
+ };
305
+ const SelectionRect = ({ rect }) => {
306
+ return (React.createElement("div", { className: 'bg-yellow-300', style: {
307
+ userSelect: 'none',
308
+ position: 'absolute',
309
+ top: `${rect.top}px`,
310
+ left: `${rect.left}px`,
311
+ width: `${rect.width}px`,
312
+ height: `${rect.height}px`,
313
+ } }, "\u00A0"));
314
+ };
286
315
 
287
- exports.FullWindow = FullWindow;
288
- exports.TextSelectionZone = TextSelectionZone;
316
+ exports.ReactTextRange = ReactTextRange;
@@ -1,5 +1,5 @@
1
1
  import React, { FC } from 'react';
2
- import './main.css';
2
+ import './index.css';
3
3
  export declare const FullWindow: FC<{
4
4
  children: React.ReactNode;
5
5
  }>;
@@ -0,0 +1,17 @@
1
+ import { FC } from 'react';
2
+ import React from 'react';
3
+ export type TextContainer = React.ForwardRefExoticComponent<{
4
+ children: React.ReactNode;
5
+ } & React.RefAttributes<HTMLDivElement>>;
6
+ export interface RangeState {
7
+ left: number;
8
+ right: number;
9
+ }
10
+ export declare const ReactTextRange: FC<{
11
+ initLeftPos: number;
12
+ initRightPos: number;
13
+ Container: TextContainer;
14
+ children: string;
15
+ onChange: (state: RangeState) => void;
16
+ }>;
17
+ export default ReactTextRange;
@@ -1,6 +1,6 @@
1
1
  import { FC } from 'react';
2
2
  import { HandlerPos } from './handler-pos';
3
- import './main.css';
3
+ import './index.css';
4
4
  export declare const SelectionHandler: FC<{
5
5
  pos: HandlerPos | null;
6
6
  grab: boolean;
@@ -1,7 +1,12 @@
1
1
  import { FC } from 'react';
2
- export declare const TextSelectionZone: FC<{
2
+ import React from 'react';
3
+ export type TextContainer = React.ForwardRefExoticComponent<{
4
+ children: React.ReactNode;
5
+ } & React.RefAttributes<HTMLDivElement>>;
6
+ export declare const ReactTextRange: FC<{
3
7
  initLeftPos: number;
4
8
  initRightPos: number;
9
+ Container: TextContainer;
5
10
  children: string;
6
11
  }>;
7
- export default TextSelectionZone;
12
+ export default ReactTextRange;
@@ -2,4 +2,5 @@ export interface HandlerPos {
2
2
  left: number;
3
3
  top: number;
4
4
  height: number;
5
+ pos: number;
5
6
  }
@@ -0,0 +1 @@
1
+ export {};
@@ -1,3 +1,2 @@
1
- import FullWindow from "./FullWindow";
2
- import TextSelectionZone from "./TextSelectionZone";
3
- export { FullWindow, TextSelectionZone };
1
+ import { TextContainer, RangeState, ReactTextRange } from "./ReactTextRange";
2
+ export { RangeState, TextContainer, ReactTextRange };
@@ -0,0 +1,17 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Document</title>
8
+ </head>
9
+
10
+ <body>
11
+ <div id="root"></div>
12
+ <script type="module" src="/index.js"></script>
13
+ </body>
14
+
15
+ </body>
16
+
17
+ </html>
package/dist/esm/index.js CHANGED
@@ -1,57 +1,15 @@
1
1
  import * as React from 'react';
2
2
  import React__default, { useState, useRef, useEffect } from 'react';
3
3
 
4
- function styleInject(css, ref) {
5
- if ( ref === void 0 ) ref = {};
6
- var insertAt = ref.insertAt;
7
-
8
- if (!css || typeof document === 'undefined') { return; }
9
-
10
- var head = document.head || document.getElementsByTagName('head')[0];
11
- var style = document.createElement('style');
12
- style.type = 'text/css';
13
-
14
- if (insertAt === 'top') {
15
- if (head.firstChild) {
16
- head.insertBefore(style, head.firstChild);
17
- } else {
18
- head.appendChild(style);
19
- }
20
- } else {
21
- head.appendChild(style);
22
- }
23
-
24
- if (style.styleSheet) {
25
- style.styleSheet.cssText = css;
26
- } else {
27
- style.appendChild(document.createTextNode(css));
28
- }
29
- }
30
-
31
- var css_248z = "@tailwind base;@tailwind components;@tailwind utilities;";
32
- styleInject(css_248z,{"insertAt":"top"});
33
-
34
- const fullWindow = {
35
- height: '100vh',
36
- widows: '100%',
37
- };
38
- const centroContainer = Object.assign(Object.assign({}, fullWindow), { display: 'flex', alignItems: 'center', justifyContent: 'center', flexDirection: 'column' });
39
- const FullWindow = ({ children }) => {
40
- return (React__default.createElement("div", { style: centroContainer }, children));
41
- };
42
-
43
- const _TextContainer = ({ children }, ref) => {
44
- return (React__default.createElement("div", { ref: ref, draggable: false, className: "basis-80 w-80 bg-yellow-50 p-11 text-3xl select-none bg-transparent" }, children));
45
- };
46
- const TextContainer = React__default.forwardRef(_TextContainer);
47
-
48
4
  const getHandlerRect = (index, node) => {
49
5
  const range = document.createRange();
50
6
  range.setStart(node, index);
51
7
  range.setEnd(node, index);
52
- const rects = range.getClientRects();
53
- if (rects.length === 1) {
54
- return rects[0];
8
+ if (range.getClientRects) {
9
+ const rects = range.getClientRects();
10
+ if (rects.length === 1) {
11
+ return rects[0];
12
+ }
55
13
  }
56
14
  return null;
57
15
  };
@@ -59,7 +17,12 @@ const getAllRects = (node, start, end) => {
59
17
  const range = document.createRange();
60
18
  range.setStart(node, start);
61
19
  range.setEnd(node, end);
62
- return range.getClientRects();
20
+ if (range.getClientRects) {
21
+ return range.getClientRects();
22
+ }
23
+ else {
24
+ return new DOMRectList();
25
+ }
63
26
  };
64
27
  const getNodeAndOffsetFromPoint = (x, y) => {
65
28
  let range;
@@ -72,8 +35,10 @@ const getNodeAndOffsetFromPoint = (x, y) => {
72
35
  }
73
36
  else if (document.caretRangeFromPoint) {
74
37
  range = document.caretRangeFromPoint(x, y);
75
- textNode = range.startContainer;
76
- offset = range.startOffset;
38
+ if (range) {
39
+ textNode = range.startContainer;
40
+ offset = range.startOffset;
41
+ }
77
42
  }
78
43
  else {
79
44
  return null;
@@ -95,6 +60,11 @@ const useTextSelectionEditor = (initLeftPos, initRightPos, leftDrag, rightDrag)
95
60
  const [rects, setRects] = useState(null);
96
61
  // reference
97
62
  const textDiv = useRef(null);
63
+ useEffect(() => {
64
+ if (textDiv.current) {
65
+ textDiv.current.style.position = 'absolute';
66
+ }
67
+ }, [textDiv]);
98
68
  // mouse move handler
99
69
  // left handler
100
70
  useEffect(() => {
@@ -149,10 +119,12 @@ const useTextSelectionEditor = (initLeftPos, initRightPos, leftDrag, rightDrag)
149
119
  setRects(null);
150
120
  }
151
121
  else {
122
+ const divRect = textDiv.current.getBoundingClientRect();
152
123
  setLeftHandler({
153
124
  height: rect.height,
154
- left: rect.left,
155
- top: rect.top,
125
+ left: rect.left - divRect.left,
126
+ top: rect.top - divRect.top,
127
+ pos: currentLeftPos,
156
128
  });
157
129
  }
158
130
  }
@@ -168,10 +140,12 @@ const useTextSelectionEditor = (initLeftPos, initRightPos, leftDrag, rightDrag)
168
140
  setRects(null);
169
141
  }
170
142
  else {
143
+ const divRect = textDiv.current.getBoundingClientRect();
171
144
  setRightHandler({
172
145
  height: rect.height,
173
- left: rect.left,
174
- top: rect.top,
146
+ left: rect.left - divRect.left,
147
+ top: rect.top - divRect.top,
148
+ pos: currentRightPos,
175
149
  });
176
150
  }
177
151
  }
@@ -179,8 +153,26 @@ const useTextSelectionEditor = (initLeftPos, initRightPos, leftDrag, rightDrag)
179
153
  useEffect(() => {
180
154
  var _a;
181
155
  const n = (_a = textDiv.current) === null || _a === void 0 ? void 0 : _a.childNodes[0];
182
- if (n)
183
- setRects(getAllRects(n, currentLeftPos, currentRightPos));
156
+ if (!n) {
157
+ setRects(null);
158
+ return;
159
+ }
160
+ const rawRects = getAllRects(n, currentLeftPos, currentRightPos);
161
+ let rectArray = [];
162
+ if (rawRects && textDiv.current) {
163
+ const divRect = textDiv.current.getBoundingClientRect();
164
+ for (let i = 0; i < rawRects.length; ++i) {
165
+ const aa = rawRects.item(i);
166
+ if (aa)
167
+ rectArray.push({
168
+ height: aa.height,
169
+ left: aa.left - divRect.left,
170
+ top: aa.top - divRect.top,
171
+ width: aa.width,
172
+ });
173
+ }
174
+ }
175
+ setRects(rectArray);
184
176
  }, [currentLeftPos, currentRightPos]);
185
177
  // return
186
178
  return [textDiv, leftHandler, rightHandler, rects];
@@ -210,6 +202,36 @@ var SvgQuoteRight = function SvgQuoteRight(props) {
210
202
  })));
211
203
  };
212
204
 
205
+ function styleInject(css, ref) {
206
+ if ( ref === void 0 ) ref = {};
207
+ var insertAt = ref.insertAt;
208
+
209
+ if (!css || typeof document === 'undefined') { return; }
210
+
211
+ var head = document.head || document.getElementsByTagName('head')[0];
212
+ var style = document.createElement('style');
213
+ style.type = 'text/css';
214
+
215
+ if (insertAt === 'top') {
216
+ if (head.firstChild) {
217
+ head.insertBefore(style, head.firstChild);
218
+ } else {
219
+ head.appendChild(style);
220
+ }
221
+ } else {
222
+ head.appendChild(style);
223
+ }
224
+
225
+ if (style.styleSheet) {
226
+ style.styleSheet.cssText = css;
227
+ } else {
228
+ style.appendChild(document.createTextNode(css));
229
+ }
230
+ }
231
+
232
+ var css_248z = "/*! tailwindcss v3.3.3 | MIT License | https://tailwindcss.com*/*,:after,:before{border:0 solid #e5e7eb;box-sizing:border-box}:after,:before{--tw-content:\"\"}html{-webkit-text-size-adjust:100%;font-feature-settings:normal;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-variation-settings:normal;line-height:1.5;-moz-tab-size:4;tab-size:4}body{line-height:inherit;margin:0}hr{border-top-width:1px;color:inherit;height:0}abbr:where([title]){text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{border-collapse:collapse;border-color:inherit;text-indent:0}button,input,optgroup,select,textarea{font-feature-settings:inherit;color:inherit;font-family:inherit;font-size:100%;font-variation-settings:inherit;font-weight:inherit;line-height:inherit;margin:0;padding:0}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::placeholder,textarea::placeholder{color:#9ca3af;opacity:1}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{height:auto;max-width:100%}[hidden]{display:none}*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.absolute{position:absolute}.relative{position:relative}.flex{display:flex}.rounded-l-md{border-bottom-left-radius:.375rem;border-top-left-radius:.375rem}.rounded-r-md{border-bottom-right-radius:.375rem;border-top-right-radius:.375rem}.bg-yellow-300{--tw-bg-opacity:1;background-color:rgb(253 224 71/var(--tw-bg-opacity))}.opacity-80{opacity:.8}";
233
+ styleInject(css_248z,{"insertAt":"top"});
234
+
213
235
  const SelectionHandler = ({ pos, grab, setGrab, left }) => {
214
236
  return (pos &&
215
237
  React__default.createElement("div", { draggable: false, className: `bg-yellow-300 opacity-80 ${left ? 'rounded-l-md' : 'rounded-r-md'}`, style: {
@@ -235,33 +257,40 @@ const SelectionHandler = ({ pos, grab, setGrab, left }) => {
235
257
  : React__default.createElement(SvgQuoteRight, null)));
236
258
  };
237
259
 
238
- const TextSelectionZone = ({ initLeftPos, initRightPos, children, }) => {
260
+ const ReactTextRange = ({ initLeftPos, initRightPos, Container, children, onChange, }) => {
239
261
  const [mouseOnLeft, setMouseOnLeft] = useState(false);
240
262
  const [mouseOnRight, setMouseOnRight] = useState(false);
241
263
  const [textDiv, leftHandler, rightHandler, rects] = useTextSelectionEditor(initLeftPos, initRightPos, mouseOnLeft, mouseOnRight);
242
- let divs = [];
243
- if (rects) {
244
- for (let i = 0; i < rects.length; ++i) {
245
- const aa = rects.item(i);
246
- if (aa)
247
- divs.push(aa);
264
+ useEffect(() => {
265
+ if (leftHandler && rightHandler) {
266
+ onChange({
267
+ left: leftHandler.pos,
268
+ right: rightHandler.pos,
269
+ });
248
270
  }
249
- }
271
+ }, [leftHandler, rightHandler]);
250
272
  return (React__default.createElement("div", { draggable: false, style: {
251
- display: 'flex',
273
+ position: 'relative',
252
274
  } },
253
- divs.map((d, i) => React__default.createElement("div", { key: i, className: 'bg-yellow-300', style: {
254
- userSelect: 'none',
255
- position: 'absolute',
256
- top: `${d.top}px`,
257
- left: `${d.left}px`,
258
- width: `${d.width}px`,
259
- height: `${d.height}px`,
260
- zIndex: -2,
261
- } }, "\u00A0")),
275
+ React__default.createElement(SelectionRects, { rects: rects }),
276
+ React__default.createElement(Container, { ref: textDiv }, children),
262
277
  React__default.createElement(SelectionHandler, { grab: mouseOnLeft, left: true, pos: leftHandler, setGrab: (v) => setMouseOnLeft(v) }),
263
- React__default.createElement(TextContainer, { ref: textDiv }, children),
264
278
  React__default.createElement(SelectionHandler, { grab: mouseOnRight, left: false, pos: rightHandler, setGrab: (v) => setMouseOnRight(v) })));
265
279
  };
280
+ const SelectionRects = ({ rects }) => {
281
+ if (!rects)
282
+ return null;
283
+ return (React__default.createElement(React__default.Fragment, null, rects.map((d, i) => React__default.createElement(SelectionRect, { key: i, rect: d }))));
284
+ };
285
+ const SelectionRect = ({ rect }) => {
286
+ return (React__default.createElement("div", { className: 'bg-yellow-300', style: {
287
+ userSelect: 'none',
288
+ position: 'absolute',
289
+ top: `${rect.top}px`,
290
+ left: `${rect.left}px`,
291
+ width: `${rect.width}px`,
292
+ height: `${rect.height}px`,
293
+ } }, "\u00A0"));
294
+ };
266
295
 
267
- export { FullWindow, TextSelectionZone };
296
+ export { ReactTextRange };
@@ -0,0 +1,6 @@
1
+ export interface Rect {
2
+ top: number;
3
+ left: number;
4
+ width: number;
5
+ height: number;
6
+ }
@@ -1,5 +1,6 @@
1
1
  import React from 'react';
2
2
  import { HandlerPos } from './handler-pos';
3
+ import { Rect } from './rect';
3
4
  declare global {
4
5
  interface Document {
5
6
  caretPositionFromPoint: any;
@@ -9,5 +10,5 @@ export declare const useTextSelectionEditor: (initLeftPos: number, initRightPos:
9
10
  React.MutableRefObject<HTMLDivElement | null>,
10
11
  HandlerPos | null,
11
12
  HandlerPos | null,
12
- DOMRectList | null
13
+ Rect[] | null
13
14
  ];
package/dist/index.d.ts CHANGED
@@ -1,13 +1,18 @@
1
1
  import React, { FC } from 'react';
2
2
 
3
- declare const FullWindow: FC<{
3
+ type TextContainer = React.ForwardRefExoticComponent<{
4
4
  children: React.ReactNode;
5
- }>;
6
-
7
- declare const TextSelectionZone: FC<{
5
+ } & React.RefAttributes<HTMLDivElement>>;
6
+ interface RangeState {
7
+ left: number;
8
+ right: number;
9
+ }
10
+ declare const ReactTextRange: FC<{
8
11
  initLeftPos: number;
9
12
  initRightPos: number;
13
+ Container: TextContainer;
10
14
  children: string;
15
+ onChange: (state: RangeState) => void;
11
16
  }>;
12
17
 
13
- export { FullWindow, TextSelectionZone };
18
+ export { type RangeState, ReactTextRange, type TextContainer };
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "react-text-range",
3
- "version": "1.0.5",
3
+ "version": "1.0.7",
4
4
  "description": "text selection editor for React",
5
5
  "main": "./dist/cjs/index.js",
6
6
  "module": "./dist/esm/index.js",
7
7
  "types": "./dist/esm/index.d.ts",
8
8
  "scripts": {
9
+ "build:sample": "rollup -c && cp ./src/server.js ./dist/cjs && cp ./src/index.html ./dist/cjs",
9
10
  "build": "rollup -c",
10
11
  "test": "jest --config jestconfig.json",
11
12
  "prepare": "npm run build",
@@ -29,17 +30,21 @@
29
30
  "LICENSE",
30
31
  "README.md"
31
32
  ],
32
- "peerDependencies": {
33
- "react": ">=16",
34
- "react-dom": ">=16"
35
- },
36
33
  "devDependencies": {
34
+ "@babel/core": "^7.23.0",
35
+ "@babel/preset-env": "^7.22.20",
36
+ "@babel/preset-react": "^7.22.15",
37
+ "@rollup/plugin-babel": "^6.0.3",
38
+ "@rollup/plugin-commonjs": "^25.0.4",
39
+ "@rollup/plugin-node-resolve": "^15.2.1",
40
+ "@rollup/plugin-replace": "^5.0.2",
37
41
  "@rollup/plugin-typescript": "^11.1.4",
38
42
  "@svgr/rollup": "^8.1.0",
39
43
  "@testing-library/react": "^14.0.0",
40
44
  "@types/jest": "^29.5.5",
41
45
  "@types/react": "^18.2.24",
42
46
  "autoprefixer": "^10.4.16",
47
+ "connect": "^3.7.0",
43
48
  "jest": "^29.7.0",
44
49
  "jest-canvas-mock": "^2.5.2",
45
50
  "jest-environment-jsdom": "^29.7.0",
@@ -48,10 +53,16 @@
48
53
  "react-dom": "^18.2.0",
49
54
  "rollup": "^3.29.4",
50
55
  "rollup-plugin-dts": "^6.0.2",
56
+ "rollup-plugin-peer-deps-external": "^2.2.4",
51
57
  "rollup-plugin-postcss": "^4.0.2",
58
+ "serve-static": "^1.15.0",
52
59
  "tailwindcss": "^3.3.3",
53
60
  "ts-jest": "^29.1.1",
54
61
  "tslib": "^2.6.2",
55
62
  "typescript": "^5.2.2"
63
+ },
64
+ "peerDependencies": {
65
+ "react": ">=16",
66
+ "react-dom": ">=16"
56
67
  }
57
- }
68
+ }