@sikka/hawa 0.0.236 → 0.0.237

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.236",
3
+ "version": "0.0.237",
4
4
  "description": "SaaS Oriented UI Kit",
5
5
  "main": "lib/index.js",
6
6
  "module": "es/index.es.js",
@@ -27,11 +27,19 @@ const stylers = {
27
27
  strike: { id: "strike", css: "line-through", content: "S" },
28
28
  }
29
29
 
30
- // FIXME: Highlighting a part of styled text with a bit on the left with an overall length not equal to clipboard copied text will result in paste issues
30
+ // FIXME: ? Highlighting a part of styled text with a bit on the left with an overall length not equal to clipboard copied text will result in paste issues
31
31
 
32
32
  // FIXME: Highlighting the beginning characters of styled text and then pasting text sometimes doesn't register as right intersecting
33
33
  // This expecially happens when the selection is for example, [0, 2] and the styling is [0, 3], this might be failure of addition which doesn't offset the styling
34
34
 
35
+ // TODO: Refactor styling splicing into one method
36
+ // TODO: Refactor function that simplifies a list of stylings
37
+ // TODO: Turn stylings into a class, this should also change .finish to .end
38
+
39
+ // Possible logic changes:
40
+ // Paste = Removal + Addition -> Styling Removal + Styling Addition
41
+ // Drag & Drop = Removal + Addition -> Styling Removal + Styling Addition
42
+
35
43
  export const FloatingComment: React.FunctionComponent<ComponentTypes> = (
36
44
  props
37
45
  ) => {
@@ -41,6 +49,7 @@ export const FloatingComment: React.FunctionComponent<ComponentTypes> = (
41
49
  revert: [0, 0],
42
50
  lastCopy: [],
43
51
  pasted: { status: false, length: 0 },
52
+ dropped: { status: false, text: "", previous: [0, 0] },
44
53
  })
45
54
 
46
55
  const field = useRef(null)
@@ -55,42 +64,101 @@ export const FloatingComment: React.FunctionComponent<ComponentTypes> = (
55
64
  return i
56
65
  }
57
66
 
58
- // Full reversion achieved !
59
- const getFieldSelection = () => {
60
- if (document.activeElement != field.current) return [0, 0]
61
-
67
+ const getSelectionPrecedingSum = (name) => {
62
68
  let selection = window.getSelection()
63
69
  let nodes = Array.from(field.current.childNodes)
64
70
 
71
+ // All current occurences for text or br:
72
+ // Pasting on empty text (text)
73
+ // Cutting/removing all text (br)
74
+ // Typing the first character in empty text (text)
75
+ // Dragging text to the end of the text (text)
76
+
65
77
  nodes = nodes.filter(
66
78
  (item: any) => !["#text", "BR"].includes(item.nodeName)
67
79
  )
68
80
 
69
- let startParent: any = selection.anchorNode.parentNode
81
+ let parent: any = selection[name].parentNode
82
+ let special = 0
83
+
84
+ // Special case for empty text
85
+ // if (parent == field.current) {
86
+ // console.log("hi")
87
+ // }
88
+
89
+ // Special case for dropping text near or inside styled text
90
+ if (!Array.from(parent.parentNode.classList).includes("selection-ignore")) {
91
+ parent = parent.parentNode
70
92
 
71
- let startNodeIndex =
72
- startParent == field.current
73
- ? nodes.length
74
- : // : parseInt(startParent.dataset.childIndex)
75
- getChildIndex(startParent)
93
+ let index = getChildIndex(selection[name].parentNode)
94
+ special = Array.from(parent.childNodes)
95
+ .slice(0, index)
96
+ .map((e: any) => e.textContent.length)
97
+ .reduce((a, b) => a + b, 0)
98
+ }
99
+
100
+ let index = parent == field.current ? nodes.length : getChildIndex(parent)
101
+
102
+ let sum =
103
+ nodes
104
+ .slice(0, index)
105
+ .map((span: any) => span.textContent.length)
106
+ .reduce((a, b) => a + b, 0) + special
107
+
108
+ return sum
109
+ }
110
+
111
+ const getFieldSelection = () => {
112
+ if (document.activeElement != field.current) return [0, 0]
76
113
 
77
- let startPrecedingSum = nodes
78
- .slice(0, startNodeIndex)
79
- .map((span: any) => span.textContent.length)
80
- .reduce((a, b) => a + b, 0)
114
+ let selection = window.getSelection()
115
+ // let nodes = Array.from(field.current.childNodes)
81
116
 
82
- let endParent: any = selection.focusNode.parentNode
83
- let endNodeIndex =
84
- endParent == field.current
85
- ? nodes.length
86
- : // : parseInt(endParent.dataset.childIndex)
87
- getChildIndex(endParent)
117
+ // console.log(nodes)
88
118
 
89
- let endPrecedingSum = nodes
90
- .slice(0, endNodeIndex)
91
- .map((span: any) => span.textContent.length)
92
- .reduce((a, b) => a + b, 0)
119
+ // nodes = nodes.filter(
120
+ // (item: any) => !["#text", "BR"].includes(item.nodeName)
121
+ // )
93
122
 
123
+ // console.log(selection.anchorNode)
124
+ // console.log(selection.focusNode)
125
+ // console.log(selection.anchorNode.parentNode)
126
+ // console.log(selection.focusNode.parentNode)
127
+ // console.log(selection.anchorNode.parentNode.parentNode)
128
+ // console.log(selection.focusNode.parentNode.parentNode)
129
+ // console.log(selection.anchorOffset)
130
+ // console.log(selection.focusOffset)
131
+
132
+ // let startParent: any = selection.anchorNode.parentNode
133
+
134
+ // let startNodeIndex =
135
+ // startParent == field.current
136
+ // ? nodes.length
137
+ // : // : parseInt(startParent.dataset.childIndex)
138
+ // getChildIndex(startParent)
139
+
140
+ // let startPrecedingSum = nodes
141
+ // .slice(0, startNodeIndex)
142
+ // .map((span: any) => span.textContent.length)
143
+ // .reduce((a, b) => a + b, 0)
144
+
145
+ let startPrecedingSum = getSelectionPrecedingSum("anchorNode")
146
+
147
+ // let endParent: any = selection.focusNode.parentNode
148
+ // let endNodeIndex =
149
+ // endParent == field.current
150
+ // ? nodes.length
151
+ // : // : parseInt(endParent.dataset.childIndex)
152
+ // getChildIndex(endParent)
153
+
154
+ // let endPrecedingSum = nodes
155
+ // .slice(0, endNodeIndex)
156
+ // .map((span: any) => span.textContent.length)
157
+ // .reduce((a, b) => a + b, 0)
158
+
159
+ let endPrecedingSum = getSelectionPrecedingSum("focusNode")
160
+
161
+ console.log([startPrecedingSum, endPrecedingSum])
94
162
  let result = [
95
163
  startPrecedingSum + selection.anchorOffset,
96
164
  endPrecedingSum + selection.focusOffset,
@@ -528,11 +596,11 @@ export const FloatingComment: React.FunctionComponent<ComponentTypes> = (
528
596
  })
529
597
  changes.push(succeeding)
530
598
 
531
- console.log(`Addition (${length})`)
532
- console.log([start, end])
533
- if (stylings.length > 0) {
534
- console.log([stylings[0].start, stylings[0].finish])
535
- }
599
+ // console.log(`Addition (${length})`)
600
+ // console.log([start, end])
601
+ // if (stylings.length > 0) {
602
+ // console.log([stylings[0].start, stylings[0].finish])
603
+ // }
536
604
 
537
605
  // console.log(succeeding)
538
606
 
@@ -561,12 +629,12 @@ export const FloatingComment: React.FunctionComponent<ComponentTypes> = (
561
629
 
562
630
  changes.push(encapsulated)
563
631
 
564
- console.log(length)
632
+ // console.log(length)
565
633
 
566
- console.log(`Encapsulated: ${encapsulated.length}`)
634
+ // console.log(`Encapsulated: ${encapsulated.length}`)
567
635
 
568
- console.log(`Succeeding: ${succeeding.length}`)
569
- console.log(`Preceding: ${preceding.length}`)
636
+ // console.log(`Succeeding: ${succeeding.length}`)
637
+ // console.log(`Preceding: ${preceding.length}`)
570
638
  // console.log(preceding)
571
639
 
572
640
  changes.flat().map(({ index, start, finish }) => {
@@ -581,13 +649,12 @@ export const FloatingComment: React.FunctionComponent<ComponentTypes> = (
581
649
  }
582
650
 
583
651
  const handlePaste = (stylings, difference, start, end) => {
584
- console.log(`Paste:`)
585
- console.log([start, end])
586
- console.log(stylings)
652
+ // console.log(`Paste:`)
653
+ // console.log([start, end])
654
+ // console.log(stylings)
587
655
  let changes = []
588
656
 
589
657
  // Get stylings being encapsulated by pasting range
590
-
591
658
  let encapsulating = handleScenario(
592
659
  stylings,
593
660
  (_start, _end) => start <= _start && end >= _end // This is needed because of conflict with addition/deletion
@@ -659,10 +726,10 @@ export const FloatingComment: React.FunctionComponent<ComponentTypes> = (
659
726
 
660
727
  changes.push(right)
661
728
 
662
- console.log(`Encapsulating: ${encapsulating.length}`)
663
- console.log(`Encapsulated: ${encapsulated.length}`)
664
- console.log(`Left Intersecting: ${left.length}`)
665
- console.log(`Right Intersecting: ${right.length}`)
729
+ // console.log(`Encapsulating: ${encapsulating.length}`)
730
+ // console.log(`Encapsulated: ${encapsulated.length}`)
731
+ // console.log(`Left Intersecting: ${left.length}`)
732
+ // console.log(`Right Intersecting: ${right.length}`)
666
733
 
667
734
  changes = changes.flat()
668
735
  changes.map((styling) => {
@@ -683,15 +750,225 @@ export const FloatingComment: React.FunctionComponent<ComponentTypes> = (
683
750
  }
684
751
  })
685
752
 
686
- console.log(stylings)
753
+ // console.log(stylings)
687
754
 
688
755
  return stylings
689
756
  }
690
757
 
758
+ const handleDrop = (value) => {
759
+ let [dropStart, dropEnd] = getFieldSelection()
760
+
761
+ let stylings = _text.current.stylings.slice()
762
+ let dropped = _text.current.dropped.text
763
+
764
+ let changes = []
765
+
766
+ let [dragStart, dragEnd] = _text.current.dropped.previous
767
+
768
+ console.log(`Drag: ${[dragStart, dragEnd]}`)
769
+ console.log(`Drop: ${[dropStart, dropEnd]}`)
770
+ console.log(stylings)
771
+
772
+ // negative - right, positive - left
773
+ // let direction = dragStart - dropStart < 0 ? -1 : 1
774
+
775
+ // Possible refactorization:
776
+ // Since the drop invokes the onInput event twice, the first is the removal of dragged text, and the second is the addition of dropped text
777
+ // If the handling occurs at the second occurence of onInput, the first could be handled naturally by the handleDeletion function
778
+
779
+ // offset: length * direction
780
+
781
+ // To be offset in right conditions:
782
+ // (_start >= dg.end && _end > dg.end)
783
+ // (_end <= dp.start && _start < dp.start)
784
+
785
+ // To be offset in left conditions:
786
+ // (_end <= dg.start && _start < dg.start)
787
+ // (_start >= dp.end && _end > dp.end)
788
+
789
+ // Get all intersecting stylings
790
+ let dragStylings = stylings.filter(
791
+ ({ start, finish }) =>
792
+ !(
793
+ (start < dragStart && finish <= dragStart) ||
794
+ (start >= dragEnd && finish > dragEnd)
795
+ )
796
+ )
797
+
798
+ // Offset stylings between drag and drop locations
799
+ stylings
800
+ .filter(({ start, finish }) => start >= dragEnd && finish > dragEnd)
801
+ .map((styling) => {
802
+ changes.push({
803
+ original: styling,
804
+ changed: {
805
+ ...styling,
806
+ start: styling.start - dropped.length,
807
+ finish: styling.finish - dropped.length,
808
+ },
809
+ })
810
+ })
811
+
812
+ // Handle complete encapsulation over styling
813
+ stylings
814
+ .filter(({ start, finish }) => dragStart <= start && dragEnd >= finish)
815
+ .map((styling) => {
816
+ changes.push({
817
+ original: styling,
818
+ changed: { ...styling, start: styling.start, finish: styling.start },
819
+ })
820
+ })
821
+
822
+ // Handle drag range being encapsulated by styling
823
+ stylings
824
+ .filter(
825
+ ({ start, finish }) =>
826
+ (dragStart > start && dragEnd <= finish) ||
827
+ (dragStart >= start && dragEnd < finish)
828
+ )
829
+ .map((styling) => {
830
+ changes.push({
831
+ original: styling,
832
+ changed: {
833
+ ...styling,
834
+ start: styling.start,
835
+ finish: styling.finish - dropped.length,
836
+ },
837
+ })
838
+ })
839
+
840
+ // Handle resumptions
841
+ let resumptions = stylings.filter(
842
+ ({ start, finish }) =>
843
+ (finish > dragStart &&
844
+ start > dragStart &&
845
+ start < dragEnd &&
846
+ finish > dragEnd) ||
847
+ (start < dragEnd &&
848
+ finish < dragEnd &&
849
+ finish > dragStart &&
850
+ start < dragStart)
851
+ )
852
+
853
+ resumptions.map((styling) => {
854
+ let right =
855
+ styling.start < dragEnd &&
856
+ styling.finish < dragEnd &&
857
+ styling.finish > dragStart &&
858
+ styling.start < dragStart
859
+
860
+ changes.push({
861
+ original: styling,
862
+ changed: {
863
+ ...styling,
864
+ start: !right
865
+ ? getMaximum([dragEnd, styling.start]) - dropped.length // Only decrease if the drag
866
+ : styling.start,
867
+ finish: right
868
+ ? getMinimum([dragStart, styling.finish])
869
+ : styling.finish - dropped.length,
870
+ },
871
+ })
872
+ })
873
+
874
+ // Apply drag changes
875
+ changes.map(({ original, changed }) => {
876
+ let index = stylings.findIndex((styling) =>
877
+ compareStylings(original, styling)
878
+ )
879
+
880
+ stylings[index] = changed
881
+ })
882
+
883
+ changes = []
884
+
885
+ // Positive offset succeeding stylings
886
+ stylings
887
+ .filter(({ start, finish }) => start >= dropStart && finish > dropStart)
888
+ .map((styling) => {
889
+ changes.push({
890
+ original: styling,
891
+ changed: {
892
+ ...styling,
893
+ start: styling.start + dropped.length,
894
+ finish: styling.finish + dropped.length,
895
+ },
896
+ })
897
+ })
898
+
899
+ // Splice intersecting stylings
900
+ stylings
901
+ .filter(({ start, finish }) => start < dropStart && finish > dropStart)
902
+ .map((styling) => {
903
+ changes.push({
904
+ original: styling,
905
+ changed: {
906
+ ...styling,
907
+ start: styling.start,
908
+ finish: dropStart,
909
+ },
910
+ })
911
+
912
+ changes.push({
913
+ original: null,
914
+ changed: {
915
+ ...styling,
916
+ start: dropEnd,
917
+ finish: styling.finish + dropped.length,
918
+ },
919
+ })
920
+ })
921
+
922
+ // Apply drop changes
923
+ changes.map(({ original, changed }) => {
924
+ if (original == null) {
925
+ stylings.push(changed)
926
+ return
927
+ }
928
+
929
+ let index = stylings.findIndex((styling) =>
930
+ compareStylings(original, styling)
931
+ )
932
+
933
+ stylings[index] = changed
934
+ })
935
+
936
+ // Remove empty stylings
937
+ stylings = stylings.filter((styling) => styling.start != styling.finish)
938
+
939
+ // Clamp start and end values, negative offset by dragStart, and positive offset by dropStart
940
+ dragStylings = dragStylings.map((styling) => {
941
+ return {
942
+ ...styling,
943
+ start: getMaximum([styling.start, dragStart]) - dragStart + dropStart,
944
+ finish: getMinimum([styling.finish, dragEnd]) - dragStart + dropStart,
945
+ }
946
+ })
947
+
948
+ // Push to current stylings
949
+ stylings = stylings.concat(dragStylings)
950
+
951
+ setText({
952
+ ..._text.current,
953
+ content: value,
954
+ stylings: stylings,
955
+ revert: [dropStart, dropEnd],
956
+ pasted: { status: false, length: 0 },
957
+ dropped: { status: false, text: "", previous: [0, 0] },
958
+ })
959
+ }
960
+
691
961
  const onChange = (value) => {
692
962
  setTimeout(function () {
693
- let [selectionStart, selectionEnd] = getFieldSelection()
963
+ if (_text.current.dropped.status) {
964
+ // Drops from text in the content editable invoke the onChange function twice
965
+ if (value.length == _text.current.content.length) {
966
+ handleDrop(value)
967
+ }
968
+ return
969
+ }
694
970
 
971
+ let [selectionStart, selectionEnd] = getFieldSelection()
695
972
  let difference = value.length - _text.current.content.length
696
973
 
697
974
  let start = selectionStart - difference
@@ -773,6 +1050,7 @@ export const FloatingComment: React.FunctionComponent<ComponentTypes> = (
773
1050
  stylings: stylings,
774
1051
  revert: [selectionStart, selectionEnd],
775
1052
  pasted: { status: false, length: 0 },
1053
+ dropped: { status: false, text: "", previous: [0, 0] },
776
1054
  })
777
1055
  }, 0)
778
1056
  }
@@ -872,6 +1150,22 @@ export const FloatingComment: React.FunctionComponent<ComponentTypes> = (
872
1150
  }
873
1151
  }
874
1152
 
1153
+ const calculateTextWidth = (
1154
+ text: string,
1155
+ font: { size: number; family: string }
1156
+ ) => {
1157
+ let element = document.createElement("div")
1158
+ let { size, family } = font
1159
+
1160
+ element.className = `text-[${size}px] font-['${family}'] absolute float-left whitespace-nowrap invisible`
1161
+ element.innerHTML = text
1162
+
1163
+ document.body.appendChild(element)
1164
+
1165
+ let rect = element.getBoundingClientRect()
1166
+ return rect.width
1167
+ }
1168
+
875
1169
  return (
876
1170
  <div className="align-center box-border flex h-min w-[400px] flex-col items-center justify-center rounded shadow-md">
877
1171
  <div className={clsx("flex w-full flex-row justify-start p-2")}>
@@ -887,42 +1181,13 @@ export const FloatingComment: React.FunctionComponent<ComponentTypes> = (
887
1181
  />
888
1182
  )
889
1183
  })}
890
-
891
- {/* <Property
892
- name="B"
893
- onMouseDown={(event) => {
894
- event.preventDefault() // This does not take focus away from field which allows the function to retrieve the current selection data
895
- perform("bold")
896
- }}
897
- />
898
- <Property
899
- name="I"
900
- onMouseDown={(event) => {
901
- event.preventDefault()
902
- perform("italic")
903
- }}
904
- />
905
- <Property
906
- name="U"
907
- onMouseDown={(event) => {
908
- event.preventDefault()
909
- perform("under")
910
- }}
911
- />
912
- <Property
913
- name="S"
914
- onMouseDown={(event) => {
915
- event.preventDefault()
916
- perform("strike")
917
- }}
918
- /> */}
919
1184
  </div>
920
1185
  <div className="h-[1px] w-full bg-slate-600">&nbsp;</div>
921
- <div className="w-full">
1186
+ <div className="selection-ignore box-border w-full p-2">
922
1187
  <div
923
1188
  ref={field}
924
1189
  contentEditable="true"
925
- className="rtl h-[150px] w-full resize-none overflow-auto overflow-x-hidden border-none p-2 outline-none"
1190
+ className="selection-ignore rtl h-[150px] w-full resize-none overflow-auto overflow-x-hidden border-none font-['Arial'] text-[16px] outline-none"
926
1191
  style={{
927
1192
  direction: getTextDirection(),
928
1193
  }}
@@ -938,60 +1203,6 @@ export const FloatingComment: React.FunctionComponent<ComponentTypes> = (
938
1203
  ..._text.current,
939
1204
  pasted: { status: true, length: data.length },
940
1205
  })
941
-
942
- // let [start, end] = getFieldSelection()
943
-
944
- // console.log(index)
945
- // console.log(start)
946
-
947
- // let index = start - data.length
948
- // let stylings = text.stylings.slice()
949
-
950
- // FIXME:
951
- // stylings.push({
952
- // type: "bold",
953
- // start: 5,
954
- // finish: 7,
955
- // })
956
-
957
- // let copy = text.lastCopy
958
- // if (copy.length != 0) {
959
- // for (let styling of copy) {
960
- // stylings.push({
961
- // type: styling.type,
962
- // start: styling.start + start,
963
- // finish: styling.finish + start,
964
- // })
965
- // }
966
- // }
967
-
968
- // let content: any = text.content
969
- // let original = content.length
970
-
971
- // // If not collapsed, insert text
972
- // if (start == end) {
973
- // content = content.split("")
974
- // content.splice(start, 0, data)
975
- // content = content.join("")
976
- // } else {
977
- // console.log(content)
978
- // // Otherwise, replace substring
979
- // content =
980
- // content.substring(0, start) +
981
- // data +
982
- // content.substring(end, content.length)
983
- // }
984
-
985
- // let difference = content.length - original
986
-
987
- // stylings = handleDeletion()
988
-
989
- // setText({
990
- // ...text,
991
- // content: content,
992
- // stylings: stylings,
993
- // revert: [start + data.length, start + data.length],
994
- // })
995
1206
  }}
996
1207
  onInput={(event: any) => {
997
1208
  onChange(event.target.textContent)
@@ -1006,10 +1217,24 @@ export const FloatingComment: React.FunctionComponent<ComponentTypes> = (
1006
1217
  lastCopy: data,
1007
1218
  })
1008
1219
  }}
1009
- // onKeyDown={(event: any) => {
1010
- // event.preventDefault()
1011
- // console.log(event)
1012
- // }}
1220
+ onDrop={(event) => {
1221
+ let text = event.dataTransfer.getData("text")
1222
+
1223
+ if (text.trim() == "") return
1224
+
1225
+ // console.log(getSelectionPrecedingSum("anchorNode"))
1226
+ // console.log(getSelectionPrecedingSum("focusNode"))
1227
+ // console.log(window.getSelection())
1228
+
1229
+ setText({
1230
+ ..._text.current,
1231
+ dropped: {
1232
+ status: true,
1233
+ text: text,
1234
+ previous: getFieldSelection(),
1235
+ },
1236
+ })
1237
+ }}
1013
1238
  ></div>
1014
1239
  </div>
1015
1240
  <div className="h-[1px] w-full bg-slate-600">&nbsp;</div>
package/src/styles.css CHANGED
@@ -660,6 +660,9 @@ video {
660
660
  .z-50 {
661
661
  z-index: 50;
662
662
  }
663
+ .float-left {
664
+ float: left;
665
+ }
663
666
  .m-0 {
664
667
  margin: 0px;
665
668
  }
@@ -1787,6 +1790,12 @@ video {
1787
1790
  .align-middle {
1788
1791
  vertical-align: middle;
1789
1792
  }
1793
+ .font-\[\'\$\{family\}\'\] {
1794
+ font-family: '${family}';
1795
+ }
1796
+ .font-\[\'Arial\'\] {
1797
+ font-family: 'Arial';
1798
+ }
1790
1799
  .text-2xl {
1791
1800
  font-size: 1.5rem;
1792
1801
  line-height: 2rem;
@@ -1809,6 +1818,9 @@ video {
1809
1818
  .text-\[11px\] {
1810
1819
  font-size: 11px;
1811
1820
  }
1821
+ .text-\[16px\] {
1822
+ font-size: 16px;
1823
+ }
1812
1824
  .text-\[9px\] {
1813
1825
  font-size: 9px;
1814
1826
  }