@sikka/hawa 0.0.231 → 0.0.233

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": "@sikka/hawa",
3
- "version": "0.0.231",
3
+ "version": "0.0.233",
4
4
  "description": "SaaS Oriented UI Kit",
5
5
  "main": "lib/index.js",
6
6
  "module": "es/index.es.js",
@@ -113,6 +113,9 @@
113
113
  "react-select": "^5.3.2",
114
114
  "rich-textarea": "^0.21.1",
115
115
  "rollup-plugin-typescript2": "^0.34.1",
116
+ "slate": "^0.94.1",
117
+ "slate-history": "^0.93.0",
118
+ "slate-react": "^0.97.1",
116
119
  "start": "^5.1.0"
117
120
  }
118
121
  }
@@ -17,8 +17,6 @@ type ComponentTypes = {
17
17
  foo?: string
18
18
  }
19
19
 
20
- // TODO: Handle copy pasting by assigning to local storage ?
21
-
22
20
  const styleClasses = {
23
21
  bold: "font-bold",
24
22
  italic: "italic",
@@ -26,68 +24,101 @@ const styleClasses = {
26
24
  strike: "line-through",
27
25
  }
28
26
 
29
- export const FloatingComment: React.FunctionComponent<ComponentTypes> = (
27
+ export const FloatingCommentCE: React.FunctionComponent<ComponentTypes> = (
30
28
  props
31
29
  ) => {
32
- const [text, setText] = useState({
30
+ const [text, _setText] = useState({
33
31
  content: "",
34
32
  stylings: [], // A styling is an object with 2 indices specifying a substring of text, and the applied effect
35
33
  revert: [0, 0],
36
34
  })
37
35
 
38
36
  const field = useRef(null)
37
+ const _text = useRef(text)
38
+ const setText = (data) => {
39
+ _text.current = data
40
+ _setText(data)
41
+ }
39
42
 
40
- useEffect(() => {
41
- let [start, end] = text.revert
43
+ // Full reversion achieved !
44
+ const getFieldSelection = () => {
45
+ if (document.activeElement != field.current) return [0, 0]
42
46
 
43
- if (start == 0 && end == 0) return
47
+ let selection = window.getSelection()
48
+ let nodes = Array.from(field.current.childNodes)
49
+ nodes = nodes.filter(
50
+ (item: any) => !["#text", "BR"].includes(item.nodeName)
51
+ )
44
52
 
45
- console.log(start)
46
- console.log(end)
53
+ let startParent: any = selection.anchorNode.parentNode
54
+ let startNodeIndex =
55
+ startParent == field.current
56
+ ? nodes.length
57
+ : parseInt(startParent.dataset.childIndex)
58
+
59
+ let startPrecedingSum = nodes
60
+ .slice(0, startNodeIndex)
61
+ .map((span: any) => span.textContent.length)
62
+ .reduce((a, b) => a + b, 0)
63
+
64
+ let endParent: any = selection.anchorNode.parentNode
65
+ let endNodeIndex =
66
+ endParent == field.current
67
+ ? nodes.length
68
+ : parseInt(endParent.dataset.childIndex)
69
+
70
+ let endPrecedingSum = nodes
71
+ .slice(0, endNodeIndex)
72
+ .map((span: any) => span.textContent.length)
73
+ .reduce((a, b) => a + b, 0)
74
+
75
+ let result = [
76
+ startPrecedingSum + selection.anchorOffset,
77
+ endPrecedingSum + selection.focusOffset,
78
+ ]
47
79
 
48
- // TODO: To get the child node based on the index, get lengths of each child node
49
- // add to each child node the sum of all previous child nodes
50
- // Create ranges for 0 - n, n - n1, n - n2, ... where nx is the length of a child node
51
- // Add 1 to the index
52
- // Iterate through each range, and check if the index is greater than the start of the range, and less than or equal the end of the range
53
- let startNode = null
54
- let endNode = null
80
+ // Sort to make the minimum selection the start selection
81
+ return result.sort((a, b) => a - b)
82
+ }
55
83
 
56
- let total = 0
57
- let nodes = Array.from(field.current.childNodes)
58
- for (let i = 0; i < nodes.length; i++) {
59
- let node: any = nodes[i]
60
- let sum = node.textContent.length + total
84
+ useEffect(() => {
85
+ setTimeout(function () {
86
+ let [start, end] = text.revert
61
87
 
62
- if (start > total && start <= sum) {
63
- startNode = nodes[i]
64
- }
88
+ if (start == 0 && end == 0) return
65
89
 
66
- if (end > total && end <= sum) {
67
- endNode = nodes[i]
68
- }
90
+ let startNode = null
91
+ let endNode = null
69
92
 
70
- total += node.textContent.length
71
- }
93
+ let total = 0
94
+ let nodes = Array.from(field.current.childNodes)
95
+
96
+ for (let i = 0; i < nodes.length; i++) {
97
+ let node: any = nodes[i]
98
+ let sum = node.textContent.length + total
72
99
 
73
- // console.log(
74
- // Array.from(field.current.childNodes).map(
75
- // (item) => item.textContent.length
76
- // )
77
- // )
100
+ if (startNode == null && start >= total && start <= sum) {
101
+ startNode = nodes[i]
102
+ start -= total
103
+ }
78
104
 
79
- console.log(startNode)
80
- console.log(endNode)
105
+ if (endNode == null && end > total && end <= sum) {
106
+ endNode = nodes[i]
107
+ end -= total
108
+ }
81
109
 
82
- var range = document.createRange()
83
- var sel = window.getSelection()
110
+ total += node.textContent.length
111
+ }
112
+
113
+ var range = document.createRange()
114
+ var sel = window.getSelection()
84
115
 
85
- range.setStart(startNode.firstChild, start)
86
- range.setEnd(endNode.firstChild, end)
87
- range.collapse(true)
116
+ range.setStart(startNode.firstChild, start)
117
+ range.setEnd(endNode.firstChild, end)
88
118
 
89
- sel.removeAllRanges()
90
- sel.addRange(range)
119
+ sel.removeAllRanges()
120
+ sel.addRange(range)
121
+ }, 0)
91
122
  }, [text.revert])
92
123
 
93
124
  // utility functions
@@ -247,6 +278,7 @@ export const FloatingComment: React.FunctionComponent<ComponentTypes> = (
247
278
  setText({
248
279
  ...text,
249
280
  stylings: result,
281
+ revert: [selectionStart, selectionEnd],
250
282
  })
251
283
  }
252
284
 
@@ -279,9 +311,11 @@ export const FloatingComment: React.FunctionComponent<ComponentTypes> = (
279
311
  }
280
312
 
281
313
  const onChange = (value) => {
314
+ let [selectionStart, selectionEnd] = getFieldSelection()
315
+
282
316
  let difference = value.length - text.content.length
283
317
 
284
- let selection = field.current.selectionStart - difference
318
+ let selection = selectionStart - difference
285
319
 
286
320
  let stylings = text.stylings.slice()
287
321
  let succeeding = getSucceedStylings(selection)
@@ -320,14 +354,21 @@ export const FloatingComment: React.FunctionComponent<ComponentTypes> = (
320
354
 
321
355
  stylings = stylings.filter((_, index) => !spliced.includes(index))
322
356
 
323
- setText({ ...text, content: value, stylings: stylings })
357
+ // if (difference == 1) difference = 0
358
+
359
+ setText({
360
+ ...text,
361
+ content: value,
362
+ stylings: stylings,
363
+ revert: [selectionStart, selectionEnd],
364
+ })
324
365
  }
325
366
 
326
367
  const getContent = () => {
327
- let content = text.content
368
+ let content = _text.current.content
328
369
 
329
370
  // Get all styling indices
330
- let indices = text.stylings
371
+ let indices = _text.current.stylings
331
372
  .map(({ start, finish }) => [start, finish])
332
373
  .flat()
333
374
 
@@ -343,7 +384,7 @@ export const FloatingComment: React.FunctionComponent<ComponentTypes> = (
343
384
  if (indices[0] != 0) indices.unshift(0)
344
385
 
345
386
  // Add last index if not present
346
- let last = text.content.length
387
+ let last = content.length
347
388
  if (indices[indices.length - 1] != last) indices.push(last)
348
389
 
349
390
  let result = []
@@ -355,24 +396,26 @@ export const FloatingComment: React.FunctionComponent<ComponentTypes> = (
355
396
  return result
356
397
  }
357
398
 
358
- const getFieldSelection = () => {
359
- if (document.activeElement != field.current) return [0, 0]
399
+ // dangerouslySetInnerHTML incorrectly renders when the entire text is highlighted, copied, and then pasted in succession
400
+ useEffect(() => {
401
+ let html = getContent()
402
+ .map((_data, index) => {
403
+ let [start, data] = _data
360
404
 
361
- let selection = window.getSelection()
362
- let start
363
- let end
405
+ // Get stylings encompassing an index within it's range
406
+ let stylings = getIntersectStylings(start)
364
407
 
365
- if (selection.rangeCount) {
366
- let range = selection.getRangeAt(0)
367
- start = range.startOffset
368
- end = range.endOffset
369
- }
408
+ return `<span class="${stylings
409
+ .map((styling) => styleClasses[styling.type])
410
+ .join(" ")}" data-child-index="${index}">${data}</span>`
411
+ })
412
+ .join("")
370
413
 
371
- return [start, end]
372
- }
414
+ field.current.innerHTML = html
415
+ }, [text])
373
416
 
374
417
  return (
375
- <div className="align-center box-border flex h-min w-[400px] flex-col items-center justify-center rounded bg-gray-300 shadow-md">
418
+ <div className="align-center box-border flex h-min w-[400px] flex-col items-center justify-center rounded bg-blue-300 shadow-md">
376
419
  <div className={clsx("flex w-full flex-row justify-start p-2")}>
377
420
  <Property
378
421
  name="B"
@@ -383,19 +426,22 @@ export const FloatingComment: React.FunctionComponent<ComponentTypes> = (
383
426
  />
384
427
  <Property
385
428
  name="I"
386
- onClick={() => {
429
+ onMouseDown={(event) => {
430
+ event.preventDefault()
387
431
  perform("italic")
388
432
  }}
389
433
  />
390
434
  <Property
391
435
  name="U"
392
- onClick={() => {
436
+ onMouseDown={(event) => {
437
+ event.preventDefault()
393
438
  perform("under")
394
439
  }}
395
440
  />
396
441
  <Property
397
442
  name="S"
398
- onClick={() => {
443
+ onMouseDown={(event) => {
444
+ event.preventDefault()
399
445
  perform("strike")
400
446
  }}
401
447
  />
@@ -405,105 +451,11 @@ export const FloatingComment: React.FunctionComponent<ComponentTypes> = (
405
451
  <div
406
452
  ref={field}
407
453
  contentEditable="true"
408
- dangerouslySetInnerHTML={{
409
- __html: getContent()
410
- .map((_data, index) => {
411
- let [start, data] = _data
412
-
413
- // Get stylings encompassing an index within it's range
414
- let stylings = getIntersectStylings(start)
415
-
416
- return `<span class="${stylings
417
- .map((styling) => styleClasses[styling.type])
418
- .join("")}">${data}</span>`
419
- // return `<span
420
- // class="${stylings
421
- // .map((styling) => styleClasses[styling.type])
422
- // .join(" ")}"
423
-
424
- // >
425
- // ${data}
426
- // </span>`
427
- })
428
- .join(" "),
429
- }}
430
454
  className="h-[150px] w-full resize-none border-none p-2 outline-none"
431
- onInput={(event) => {
432
- let target: any = event.target
433
-
434
- let [start, end] = getFieldSelection()
435
-
436
- // console.log(target)
437
- // console.log(target.selectionStart)
438
- // console.log(target.selectionEnd)
439
-
440
- setText({
441
- ...text,
442
- content: target.textContent,
443
- revert: [start, end],
444
- })
455
+ onInput={(event: any) => {
456
+ onChange(event.target.textContent)
445
457
  }}
446
458
  ></div>
447
-
448
- {/* <RichTextarea
449
- ref={field}
450
- value={text.content}
451
- style={{ width: "100%" }} // tailwind w-full does not work
452
- className="order-none h-[150px] resize-none p-2 outline-none"
453
- onChange={(e) => {
454
- onChange(e.target.value)
455
- }}
456
- >
457
- {(value) => {
458
- // Get all styling indices
459
- let indices = text.stylings
460
- .map(({ start, finish }) => [start, finish])
461
- .flat()
462
-
463
- // Sort ascendingly
464
- indices = indices.sort((a, b) => a - b)
465
-
466
- // Remove duplicates
467
- indices = indices.filter(
468
- (element, index) => indices.indexOf(element) == index
469
- )
470
-
471
- // Add first index if not present
472
- if (indices[0] != 0) indices.unshift(0)
473
-
474
- // Add last index if not present
475
- let last = text.content.length
476
- if (indices[indices.length - 1] != last) indices.push(last)
477
-
478
- let result = []
479
-
480
- for (let i = 0; i < indices.length - 1; i++) {
481
- result.push([
482
- indices[i],
483
- value.substring(indices[i], indices[i + 1]),
484
- ])
485
- }
486
-
487
- return result.map((_data, index) => {
488
- let [start, data] = _data
489
-
490
- // Get stylings encompassing an index within it's range
491
- let stylings = getIntersectStylings(start)
492
-
493
- return (
494
- <span
495
- key={index}
496
- className={`${stylings
497
- .map((styling) => styleClasses[styling.type])
498
- .join(" ")}
499
- `}
500
- >
501
- {data}
502
- </span>
503
- )
504
- })
505
- }}
506
- </RichTextarea> */}
507
459
  </div>
508
460
  <div className="h-[1px] w-full bg-slate-600">&nbsp;</div>
509
461
  <button className="my-1 rounded bg-cyan-800 p-2 py-1 text-white">
@@ -0,0 +1,149 @@
1
+ // Import React dependencies.
2
+ import React, { useState, useCallback } from "react"
3
+ // Import the Slate editor factory.
4
+ import { Editor, Transforms, Element, createEditor } from "slate"
5
+
6
+ // Import the Slate components and React plugin.
7
+ import { Slate, Editable, withReact } from "slate-react"
8
+
9
+ // TypeScript users only add this code
10
+ import { BaseEditor, Descendant } from "slate"
11
+ import { ReactEditor } from "slate-react"
12
+
13
+ type CustomElement = { type: "paragraph"; children: CustomText[] }
14
+ type CustomText = { text: string }
15
+
16
+ declare module "slate" {
17
+ interface CustomTypes {
18
+ Editor: BaseEditor & ReactEditor
19
+ Element: CustomElement
20
+ Text: CustomText
21
+ }
22
+ }
23
+
24
+ const initialValue: Descendant[] = [
25
+ {
26
+ type: "paragraph",
27
+ children: [{ text: "" }],
28
+ },
29
+ ]
30
+
31
+ let styles = {
32
+ bold: { fontWeight: "bold" },
33
+ italic: { fontStyle: "italic" },
34
+ underline: { textDecoration: "underline" },
35
+ strikethrough: { textDecoration: "line-through" },
36
+ }
37
+
38
+ const Leaf = (props) => {
39
+ let data = Object.assign({}, props.leaf)
40
+ delete data.text
41
+
42
+ let keys = Object.entries(data)
43
+ .filter(([key, value]) => value)
44
+ .map(([key, _]) => key)
45
+
46
+ let types = {}
47
+
48
+ if (keys.length != 0) {
49
+ keys.map((type) => {
50
+ Object.entries(styles[type]).map(([key, value]) => {
51
+ types[key] = value
52
+ })
53
+ })
54
+ }
55
+
56
+ return (
57
+ <span {...props.attributes} style={types}>
58
+ {props.children}
59
+ </span>
60
+ )
61
+ }
62
+
63
+ const Property = (props) => {
64
+ return (
65
+ <div
66
+ className="border-box mr-[5px] flex h-[32px] w-[32px] items-center justify-center rounded bg-gray-400 p-2"
67
+ onMouseDown={props.onMouseDown}
68
+ >
69
+ {props.name}
70
+ </div>
71
+ )
72
+ }
73
+
74
+ export const FloatingCommentSlate = () => {
75
+ const [editor] = useState(() => withReact(createEditor()))
76
+
77
+ // Define a leaf rendering function that is memoized with `useCallback`.
78
+ const renderLeaf = useCallback((props) => {
79
+ return <Leaf {...props} />
80
+ }, [])
81
+
82
+ const perform = (event, type) => {
83
+ event.preventDefault() // This does not take focus away from field which allows the function to retrieve the current selection data
84
+
85
+ let current = Editor.marks(editor)[type] || false
86
+ console.log(Editor.marks(editor))
87
+ // if (!types.includes(type)) types.push(type)
88
+
89
+ Editor.addMark(editor, type, !current)
90
+ }
91
+
92
+ return (
93
+ <div className="align-center box-border flex h-min w-[400px] flex-col items-center justify-center rounded bg-blue-300 shadow-md">
94
+ <div className={"flex w-full flex-row justify-start p-2"}>
95
+ <Property
96
+ name="B"
97
+ onMouseDown={(event) => {
98
+ perform(event, "bold")
99
+ }}
100
+ />
101
+ <Property
102
+ name="I"
103
+ onMouseDown={(event) => {
104
+ perform(event, "italic")
105
+ }}
106
+ />
107
+ <Property
108
+ name="U"
109
+ onMouseDown={(event) => {
110
+ perform(event, "underline")
111
+ }}
112
+ />
113
+ <Property
114
+ name="S"
115
+ onMouseDown={(event) => {
116
+ perform(event, "strikethrough")
117
+ }}
118
+ />
119
+ </div>
120
+ <div className="h-[1px] w-full bg-slate-600">&nbsp;</div>
121
+ <div className="w-full">
122
+ <Slate editor={editor} initialValue={initialValue}>
123
+ <Editable
124
+ renderLeaf={renderLeaf}
125
+ className="h-[150px] p-2"
126
+ onKeyDown={(event) => {
127
+ if (!event.ctrlKey) {
128
+ return
129
+ }
130
+
131
+ switch (event.key) {
132
+ // When "B" is pressed, bold the text in the selection.
133
+ case "b": {
134
+ event.preventDefault()
135
+ Editor.addMark(editor, "bold", true)
136
+ break
137
+ }
138
+ }
139
+ }}
140
+ />
141
+ </Slate>
142
+ </div>
143
+ <div className="h-[1px] w-full bg-slate-600">&nbsp;</div>
144
+ <button className="my-1 rounded bg-cyan-800 p-2 py-1 text-white">
145
+ Submit
146
+ </button>
147
+ </div>
148
+ )
149
+ }
@@ -33,6 +33,8 @@ export * from "./HawaDatepicker"
33
33
  export * from "./UserFeedback"
34
34
  export * from "./ArrowCarousel"
35
35
  export * from "./FloatingComment"
36
+ export * from "./FloatingCommentCE"
37
+ export * from "./FloatingCommentSlate"
36
38
  // Inputs
37
39
  export * from "./HawaTextField"
38
40
  export * from "./HawaCardInput"
package/src/styles.css CHANGED
@@ -1503,6 +1503,10 @@ video {
1503
1503
  --tw-bg-opacity: 1;
1504
1504
  background-color: rgb(191 219 254 / var(--tw-bg-opacity));
1505
1505
  }
1506
+ .bg-blue-300 {
1507
+ --tw-bg-opacity: 1;
1508
+ background-color: rgb(147 197 253 / var(--tw-bg-opacity));
1509
+ }
1506
1510
  .bg-blue-50 {
1507
1511
  --tw-bg-opacity: 1;
1508
1512
  background-color: rgb(239 246 255 / var(--tw-bg-opacity));
@@ -1,6 +0,0 @@
1
- import React from "react";
2
- type ComponentTypes = {
3
- foo?: string;
4
- };
5
- export declare const FloatingComment: React.FunctionComponent<ComponentTypes>;
6
- export {};
@@ -1,6 +0,0 @@
1
- import React from "react";
2
- type ComponentTypes = {
3
- foo?: string;
4
- };
5
- export declare const FloatingComment: React.FunctionComponent<ComponentTypes>;
6
- export {};