@pocketprep/ui-kit 3.5.26 → 3.5.28

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.
@@ -11,6 +11,13 @@
11
11
  class="uikit-errors__icon"
12
12
  type="warning"
13
13
  />
14
+ <div
15
+ v-if="errorHeader"
16
+ class="uikit-errors__error-header"
17
+ v-dark="isDarkMode"
18
+ >
19
+ {{ errorHeader }}
20
+ </div>
14
21
  <slot name="errors">
15
22
  <div
16
23
  v-for="error in filteredErrors"
@@ -42,6 +49,7 @@ import { dark } from '../../directives'
42
49
  export default class Errors extends Vue {
43
50
  @Prop({ default: () => ([]) }) errors!: string[]
44
51
  @Prop({ default: false }) isDarkMode!: boolean
52
+ @Prop() errorHeader!: string
45
53
 
46
54
  get filteredErrors () {
47
55
  return [ ...new Set(this.errors) ]
@@ -92,7 +100,8 @@ export default class Errors extends Vue {
92
100
  }
93
101
  }
94
102
 
95
- &__error {
103
+ &__error,
104
+ &__error-header {
96
105
  color: $brand-black;
97
106
  font-size: 14px;
98
107
  line-height: 19px;
package/lib/utils.ts CHANGED
@@ -94,17 +94,66 @@ export const highlightKeywordsInText = (params: {
94
94
  return params.text
95
95
  }
96
96
 
97
- const keywords = params.keywordDefinitions.map(k => k.keyword)
97
+ // Sort keywords by length (longest first) to handle overlapping keywords correctly
98
+ const sortedKeywords = [ ...params.keywordDefinitions ].map(k => k.keyword).sort((a, b) => b.length - a.length)
98
99
 
99
- return keywords.reduce((acc, word) => {
100
- const regex = new RegExp(`(\\W)(${escapeRegex(word)})(\\W)`, 'i')
101
- return acc.replace(
102
- regex,
103
- `$1<span class="keyword-highlight${params.isDarkMode
104
- ? ' keyword-highlight--dark' : ''}" data-location="${
105
- params.location}" role="button" tabindex="0" aria-label="${
106
- word}, Instruction. Click for definition" aria-haspopup="dialog" aria-expanded="false">
107
- <span style="pointer-events: none;">$2</span></span>$3`
100
+ // Create an array for all matching keywords and substrings with their start and end indexes
101
+ const allMatches: { start: number; end: number; keyword: string; keywordMatch: string }[] = []
102
+
103
+ sortedKeywords.forEach((keyword) => {
104
+ const escapedKeyword = escapeRegex(keyword)
105
+ const regex = new RegExp(`\\b${escapedKeyword}\\b`, 'gi')
106
+
107
+ // Use matchAll to get all keyword matches
108
+ // This will include keyword matches that are substrings of other keywords
109
+ // ie: keyword 'stakeholder' will match in a keyword string 'key stakeholder'
110
+ const matches = Array.from(params.text.matchAll(regex))
111
+ matches.forEach((match) => {
112
+ allMatches.push({
113
+ start: match.index,
114
+ end: match.index + match[0].length,
115
+ keyword,
116
+ keywordMatch: match[0],
117
+ })
118
+ })
119
+ })
120
+
121
+ // Sort all matches by start index
122
+ allMatches.sort((a, b) => a.start - b.start)
123
+
124
+ // Create an array to track which indexes are already highlighted
125
+ const highlightedRanges: { start: number; end: number }[] = []
126
+
127
+ // Create an array to track non-overlapping keyword matches
128
+ const nonOverlappingMatches: { start: number; end: number; keyword: string; keywordMatch: string }[] = []
129
+ // Do not include any matches that are substrings of other matches
130
+ allMatches.forEach((match) => {
131
+ const overlaps = highlightedRanges.some(range =>
132
+ (match.start < range.end && match.end > range.start)
108
133
  )
109
- }, params.text)
134
+
135
+ if (!overlaps) {
136
+ nonOverlappingMatches.push(match)
137
+ highlightedRanges.push({ start: match.start, end: match.end })
138
+ }
139
+ })
140
+
141
+ // Sort by start position again and process in reverse order to maintain positions
142
+ nonOverlappingMatches.sort((a, b) => b.start - a.start)
143
+
144
+ let result = params.text
145
+
146
+ nonOverlappingMatches.forEach((match) => {
147
+ const beforeMatch = result.substring(0, match.start)
148
+ const afterMatch = result.substring(match.end)
149
+ const highlightedMatch = `<span class="keyword-highlight${params.isDarkMode
150
+ ? ' keyword-highlight--dark' : ''}" data-location="${
151
+ params.location}" role="button" tabindex="0" aria-label="${
152
+ match.keywordMatch}, Instruction. Click for definition" aria-haspopup="dialog" aria-expanded="false">
153
+ <span style="pointer-events: none;">${match.keywordMatch}</span></span>`
154
+ result = beforeMatch + highlightedMatch + afterMatch
155
+ })
156
+
157
+ return result
110
158
  }
159
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pocketprep/ui-kit",
3
- "version": "3.5.26",
3
+ "version": "3.5.28",
4
4
  "description": "Pocket Prep UI Kit",
5
5
  "author": "pocketprep",
6
6
  "scripts": {