@webalternatif/js-core 1.6.3 → 1.6.4

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.
Files changed (57) hide show
  1. package/README.md +3 -3
  2. package/dist/cjs/Mouse.js +6 -1
  3. package/dist/cjs/Translator.js +7 -1
  4. package/dist/cjs/array.js +5 -35
  5. package/dist/cjs/dom.js +817 -208
  6. package/dist/cjs/eventDispatcher.js +6 -1
  7. package/dist/cjs/index.js +6 -13
  8. package/dist/cjs/is.js +44 -1
  9. package/dist/cjs/math.js +56 -31
  10. package/dist/cjs/traversal.js +1 -2
  11. package/dist/cjs/utils.js +155 -38
  12. package/dist/esm/Mouse.js +7 -1
  13. package/dist/esm/Translator.js +7 -1
  14. package/dist/esm/array.js +4 -35
  15. package/dist/esm/dom.js +816 -206
  16. package/dist/esm/eventDispatcher.js +7 -1
  17. package/dist/esm/index.js +7 -8
  18. package/dist/esm/is.js +43 -0
  19. package/dist/esm/math.js +58 -32
  20. package/dist/esm/traversal.js +1 -2
  21. package/dist/esm/utils.js +156 -39
  22. package/dist/umd/Translator.umd.js +1 -0
  23. package/dist/umd/dom.umd.js +1 -0
  24. package/dist/umd/eventDispatcher.umd.js +1 -0
  25. package/dist/umd/mouse.umd.js +1 -0
  26. package/dist/umd/webf.umd.js +1 -0
  27. package/docs/array.md +41 -8
  28. package/docs/dom.md +1063 -269
  29. package/docs/is.md +244 -0
  30. package/docs/math.md +87 -7
  31. package/docs/mouse.md +43 -0
  32. package/docs/translator.md +14 -14
  33. package/docs/traversal.md +16 -16
  34. package/docs/utils.md +173 -20
  35. package/package.json +10 -4
  36. package/src/Mouse.js +73 -0
  37. package/src/Translator.js +148 -0
  38. package/src/array.js +136 -0
  39. package/src/dom.js +1553 -0
  40. package/src/eventDispatcher.js +118 -0
  41. package/src/index.js +106 -0
  42. package/src/is.js +201 -0
  43. package/src/math.js +113 -0
  44. package/src/onOff.js +313 -0
  45. package/src/random.js +38 -0
  46. package/src/string.js +662 -0
  47. package/src/stringPrototype.js +16 -0
  48. package/src/traversal.js +236 -0
  49. package/src/utils.js +242 -0
  50. package/types/Translator.d.ts +6 -5
  51. package/types/array.d.ts +0 -1
  52. package/types/dom.d.ts +763 -204
  53. package/types/index.d.ts +22 -21
  54. package/types/is.d.ts +3 -0
  55. package/types/math.d.ts +6 -5
  56. package/types/utils.d.ts +4 -4
  57. package/types/i18n.d.ts +0 -4
package/docs/utils.md CHANGED
@@ -14,23 +14,89 @@ import { utils } from '@webalternatif/js-core'
14
14
  <!-- GENERATED:utils -->
15
15
  <!-- Generated by documentation.js. Update this documentation by updating the source code. -->
16
16
 
17
+ ## equals
18
+
19
+ [src/utils.js:35-77][1]
20
+
21
+ Deeply compares two or more values for structural equality.
22
+
23
+ All provided values must be deeply equal for the function to return `true`.
24
+ Comparison is recursive and handles objects, arrays, and built-in types.
25
+
26
+ ### Parameters
27
+
28
+ * `values` **...any** Two or more values to compare.
29
+
30
+ ### Examples
31
+
32
+ ```javascript
33
+ equals(1, 1) // => true
34
+ equals(NaN, NaN) // => true
35
+ equals([1, { a: 2 }], [1, { a: 2 }]) // => true
36
+ equals({ a: 1 }, { a: 1, b: 2 }) // => false
37
+ equals(
38
+ { a: { b: 1 } },
39
+ { a: { b: 1 } },
40
+ { a: { b: 1 } },
41
+ ) // => true
42
+
43
+ const a = {}; a.self = a
44
+ const b = {}; b.self = b
45
+ equals(a, b) // => true
46
+
47
+ equals(new Date(1000), new Date(1000)) // => true
48
+ equals(new Date(1000), new Date(2000)) // => false
49
+
50
+ equals(/abc/gi, /abc/gi) // => true
51
+ equals(/abc/g, /abc/i) // => false
52
+ ```
53
+
54
+ Returns **[boolean][2]** `true` if all values are deeply equal, otherwise `false`.
55
+
56
+ ## noop
57
+
58
+ [src/utils.js:84-84][3]
59
+
60
+ No-operation function. Useful as a default callback or placeholder.
61
+
62
+ Returns **void**&#x20;
63
+
64
+ ## sizeOf
65
+
66
+ [src/utils.js:97-99][4]
67
+
68
+ Returns the size of a collection. Can be an object, an array or string.
69
+
70
+ ### Parameters
71
+
72
+ * `v` **([Object][5] | [Array][6] | [string][7])** The value to count
73
+
74
+ ### Examples
75
+
76
+ ```javascript
77
+ sizeOf([1, 2, 3]) // => 3
78
+ sizeOf("hello") // => 5
79
+ sizeOf({ a: 1, b: 2 }) // => 2
80
+ ```
81
+
82
+ Returns **[number][8]** The size of `v`
83
+
17
84
  ## flatten
18
85
 
19
- [src/utils.js:53-59][1]
86
+ [src/utils.js:115-121][9]
20
87
 
21
- Recursively flattens an object or array into a single-level array.
88
+ Recursively flattens values of an object or array into a single-level array.
22
89
 
23
90
  ### Parameters
24
91
 
25
- * `o` **([Object][2] | [Array][3])** The object or array to flatten.
92
+ * `o` **([Object][5] | [Array][6])** The object or array to flatten.
26
93
 
27
94
  ### Examples
28
95
 
29
96
  Flatten an array of arrays
30
97
 
31
98
  ```javascript
32
- const result = flatten([1, [2, [3, 4]], 5]);
33
- console.log(result);
99
+ const result = flatten([1, [2, [3, 4]], 5])
34
100
  // Output: [1, 2, 3, 4, 5]
35
101
  ```
36
102
 
@@ -38,38 +104,125 @@ Flatten an object
38
104
 
39
105
  ```javascript
40
106
  const result = flatten({ a: 1, b: [2, { c: 3 }], d: 4 });
41
- console.log(result);
42
107
  // Output: [1, 2, 3, 4]
43
108
  ```
44
109
 
45
- Returns **[Array][3]** A flattened array containing all the values from the input object or array.
110
+ Returns **[Array][6]** A flattened array containing all the values from the input object or array.
111
+
112
+ ## strParseFloat
113
+
114
+ [src/utils.js:134-140][10]
115
+
116
+ Parses a number from a string.
117
+
118
+ ### Parameters
119
+
120
+ * `val` &#x20;
121
+
122
+ ### Examples
123
+
124
+ ```javascript
125
+ strParseFloat("12.34") // => 12.34
126
+ strParseFloat("12,34") // => 12.34
127
+ strParseFloat(" 1 234,56 ") // => 1234.56
128
+ ```
129
+
130
+ Returns **[number][8]**&#x20;
131
+
132
+ ## throttle
133
+
134
+ [src/utils.js:165-188][11]
135
+
136
+ Creates a throttled version of a function that only executes at most once
137
+ every `wait` milliseconds.
138
+
139
+ ### Parameters
140
+
141
+ * `func` **[Function][12]** The function to throttle.
142
+ * `wait` **[number][8]** The number of milliseconds before execution of `func`.
143
+ * `leading` **[boolean][2]** if `true` run `func` a first time immediately. (optional, default `true`)
144
+ * `trailing` **[boolean][2]** if `true` run `func` one last time after timeout. (optional, default `true`)
145
+ * `context` **any** Optional `this` context to bind when invoking `func`. (optional, default `null`)
146
+
147
+ ### Examples
148
+
149
+ ```javascript
150
+ const throttled = throttle(() => console.log('tick'), 1000)
151
+ window.addEventListener('scroll', throttled)
152
+ ```
153
+
154
+ ```javascript
155
+ // runs at the end
156
+ const throttled = throttle(doSomething, 500, false, true)
157
+ ```
158
+
159
+ ```javascript
160
+ // runs immediately
161
+ const throttled = throttle(doSomething, 500, true, false)
162
+ ```
163
+
164
+ Returns **[Function][12]** A throotled function
46
165
 
47
166
  ## debounce
48
167
 
49
- [src/utils.js:105-129][4]
168
+ [src/utils.js:218-242][13]
50
169
 
51
170
  Creates a debounced function that delays the invocation of `func` until after `wait` milliseconds
52
171
  have elapsed since the last time the debounced function was invoked.
53
172
 
54
173
  ### Parameters
55
174
 
56
- * `func` **[Function][5]** The function to debounce.
57
- * `wait` **[number][6]** The number of milliseconds to delay.
58
- * `immediate` **[boolean][7]** If true, execute `func` on the leading edge instead of the trailing. (optional, default `false`)
59
- * `context` **[Object][2]** The context to bind to `func`. (optional, default `null`)
175
+ * `func` **[Function][12]** The function to debounce.
176
+ * `wait` **[number][8]** The number of milliseconds to delay.
177
+ * `immediate` **[boolean][2]** If true, execute `func` on the leading edge instead of the trailing. (optional, default `false`)
178
+ * `context` **any** The context to bind to `func`. (optional, default `null`)
179
+
180
+ ### Examples
181
+
182
+ ```javascript
183
+ // Search input: only trigger search after user stops typing
184
+ const debouncedSearch = debounce(query => {
185
+ api.search(query)
186
+ }, 300)
187
+
188
+ input.addEventListener('input', ev => {
189
+ debouncedSearch(ev.target.value)
190
+ })
191
+ ```
192
+
193
+ ```javascript
194
+ // Resize handler : run layout update only after resizing stops
195
+ const debouncedResize = debounce(() => {
196
+ updateLayout()
197
+ }, 500)
198
+
199
+ window.addEventListener('resize', debouncedResize)
200
+ ```
201
+
202
+ Returns **[Function][12]** The debounced function.
203
+
204
+ [1]: https://github.com/webalternatif/js-core/blob/c59faf2628616256e5ee2499f03adb5e9a32460d/src/utils.js#L35-L77 "Source code on GitHub"
205
+
206
+ [2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
207
+
208
+ [3]: https://github.com/webalternatif/js-core/blob/c59faf2628616256e5ee2499f03adb5e9a32460d/src/utils.js#L84-L84 "Source code on GitHub"
209
+
210
+ [4]: https://github.com/webalternatif/js-core/blob/c59faf2628616256e5ee2499f03adb5e9a32460d/src/utils.js#L97-L99 "Source code on GitHub"
211
+
212
+ [5]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
60
213
 
61
- Returns **[Function][5]** The debounced function.
214
+ [6]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array
62
215
 
63
- [1]: https://github.com/webalternatif/js-core/blob/c9158e27ed191c37c696ced37950f978a21eecba/src/utils.js#L53-L59 "Source code on GitHub"
216
+ [7]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
64
217
 
65
- [2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
218
+ [8]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
66
219
 
67
- [3]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array
220
+ [9]: https://github.com/webalternatif/js-core/blob/c59faf2628616256e5ee2499f03adb5e9a32460d/src/utils.js#L115-L121 "Source code on GitHub"
68
221
 
69
- [4]: https://github.com/webalternatif/js-core/blob/c9158e27ed191c37c696ced37950f978a21eecba/src/utils.js#L105-L129 "Source code on GitHub"
222
+ [10]: https://github.com/webalternatif/js-core/blob/c59faf2628616256e5ee2499f03adb5e9a32460d/src/utils.js#L134-L140 "Source code on GitHub"
70
223
 
71
- [5]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function
224
+ [11]: https://github.com/webalternatif/js-core/blob/c59faf2628616256e5ee2499f03adb5e9a32460d/src/utils.js#L165-L188 "Source code on GitHub"
72
225
 
73
- [6]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
226
+ [12]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function
74
227
 
75
- [7]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
228
+ [13]: https://github.com/webalternatif/js-core/blob/c59faf2628616256e5ee2499f03adb5e9a32460d/src/utils.js#L218-L242 "Source code on GitHub"
package/package.json CHANGED
@@ -12,11 +12,13 @@
12
12
  "name": "Ludovic Tarit",
13
13
  "email": "ludovic.tarit@webalternatif.com"
14
14
  },
15
- "version": "1.6.3",
15
+ "version": "1.6.4",
16
16
  "type": "module",
17
17
  "types": "types/index.d.ts",
18
18
  "main": "dist/cjs/index.js",
19
19
  "module": "dist/esm/index.js",
20
+ "unpkg": "dist/umd/webf.umd.js",
21
+ "jsdelivr": "dist/umd/webf.umd.js",
20
22
  "exports": {
21
23
  ".": {
22
24
  "types": "./types/index.d.ts",
@@ -34,8 +36,9 @@
34
36
  "test:watch": "jest --watch",
35
37
  "build:esm": "babel src --out-dir dist/esm --extensions .js",
36
38
  "build:cjs": "cross-env BABEL_ENV=cjs babel src --out-dir dist/cjs --extensions .js",
37
- "types": "tsc",
38
- "build": "rm -rf dist && yarn build:esm && yarn build:cjs && yarn types",
39
+ "build:umd": "webpack --config webpack.config.cjs",
40
+ "build": "rm -rf dist && yarn build:esm && yarn build:cjs && yarn build:umd && yarn types",
41
+ "types": "rm -rf types && tsc",
39
42
  "docs": "node scripts/docs.js",
40
43
  "prepublishOnly": "yarn build",
41
44
  "prepare": "husky install"
@@ -63,10 +66,13 @@
63
66
  "lint-staged": "^16.2.7",
64
67
  "nodemon": "^3.1.9",
65
68
  "prettier": "^3.8.1",
66
- "typescript": "^5.7.2"
69
+ "typescript": "^5.7.2",
70
+ "webpack": "^5.105.3",
71
+ "webpack-cli": "^6.0.1"
67
72
  },
68
73
  "files": [
69
74
  "dist",
75
+ "src",
70
76
  "types",
71
77
  "README.md",
72
78
  "docs",
package/src/Mouse.js ADDED
@@ -0,0 +1,73 @@
1
+ import { isTouchDevice } from './is.js'
2
+
3
+ class Mouse {
4
+ /**
5
+ * @param {Event} ev
6
+ * @param {Element} element
7
+ * @returns {{x: number, y: number}}
8
+ */
9
+ static getPosition(ev, element) {
10
+ ev = this.#getEvent(ev)
11
+ let rect = { left: 0, top: 0 }
12
+
13
+ if (element instanceof Element) {
14
+ const r = element.getBoundingClientRect()
15
+
16
+ rect = {
17
+ left: window.scrollX + r.left,
18
+ top: window.scrollY + r.top,
19
+ }
20
+ }
21
+
22
+ return {
23
+ x: ev.pageX - rect.left,
24
+ y: ev.pageY - rect.top,
25
+ }
26
+ }
27
+
28
+ /**
29
+ * @param {Event} ev
30
+ * @returns {{x: number, y: number}}
31
+ */
32
+ static getViewportPosition(ev) {
33
+ ev = this.#getEvent(ev)
34
+
35
+ return {
36
+ x: ev.clientX,
37
+ y: ev.clientY,
38
+ }
39
+ }
40
+
41
+ static getElement(ev) {
42
+ ev = this.#getEvent(ev)
43
+
44
+ return window.document.elementFromPoint(ev.clientX, ev.clientY)
45
+ }
46
+
47
+ /**
48
+ * @param {Event|{originalEvent?: Event}} ev
49
+ * @returns {Event}
50
+ */
51
+ static #getEvent(ev) {
52
+ ev = ev.originalEvent ?? ev
53
+
54
+ if (isTouchDevice()) {
55
+ const touch = ev.changedTouches?.[0] || ev.touches?.[0]
56
+
57
+ ev.clientX = touch.clientX
58
+ ev.clientY = touch.clientY
59
+ ev.pageX = touch.pageX
60
+ ev.pageY = touch.pageY
61
+ }
62
+
63
+ return ev
64
+ }
65
+ }
66
+
67
+ export default Mouse
68
+
69
+ /* istanbul ignore else */
70
+ if (typeof window !== 'undefined') {
71
+ window.webf = window.webf || {}
72
+ window.webf.mouse = Mouse
73
+ }
@@ -0,0 +1,148 @@
1
+ import { isFunction, isPlainObject } from './is.js'
2
+ import { each, extend } from './traversal.js'
3
+
4
+ /**
5
+ * @typedef {string|(()=>string)} TranslatorValue
6
+ */
7
+
8
+ /**
9
+ * @typedef {Record<string,Record<string,Record<string,TranslatorValue>>>} TranslatorNsMapping
10
+ */
11
+
12
+ /**
13
+ * @typedef {Record<string,Record<string,TranslatorValue>>} TranslatorCoreMapping
14
+ */
15
+
16
+ /**
17
+ * @typedef {TranslatorNsMapping | TranslatorCoreMapping} TranslatorMapping
18
+ */
19
+
20
+ class Translator {
21
+ /** @type string */
22
+ #lang
23
+
24
+ /** @type TranslatorMapping */
25
+ #mapping
26
+
27
+ /**
28
+ * @param {TranslatorMapping} mapping
29
+ * @param {string} [defaultLang]
30
+ */
31
+ constructor(mapping, defaultLang) {
32
+ this.#setMapping(mapping)
33
+ this.setLang(defaultLang)
34
+ }
35
+
36
+ #setMapping(mapping) {
37
+ let nsMapping = {},
38
+ coreMapping = {}
39
+
40
+ each(mapping, (ns, langs) => {
41
+ each(langs, (lang, trads) => {
42
+ if (isPlainObject(trads)) {
43
+ if (undefined === nsMapping[ns]) nsMapping[ns] = {}
44
+ nsMapping[ns][lang] = trads
45
+ } else {
46
+ if (undefined === coreMapping[ns]) coreMapping[ns] = {}
47
+ coreMapping[ns][lang] = trads
48
+ }
49
+ })
50
+ })
51
+
52
+ this.#mapping = extend(true, nsMapping, {
53
+ core: extend({}, nsMapping.core || {}, coreMapping),
54
+ })
55
+ }
56
+
57
+ /**
58
+ * @param {string} label
59
+ * @param {string} [lang]
60
+ * @param {string} [namespace='core']
61
+ * @returns {string}
62
+ */
63
+ translate(label, lang, namespace = 'core') {
64
+ lang = undefined === lang ? this.getLang() : lang
65
+
66
+ const translationExists =
67
+ undefined !== this.#mapping[namespace] &&
68
+ undefined !== this.#mapping[namespace][lang] &&
69
+ undefined !== this.#mapping[namespace][lang][label]
70
+
71
+ if (translationExists) {
72
+ const entry = this.#mapping[namespace][lang][label]
73
+ return this.#resolve(entry)
74
+ }
75
+
76
+ return 'en' !== lang ? this.translate(label, 'en', namespace) : label
77
+ }
78
+
79
+ /**
80
+ * @param {string} label
81
+ * @param {string} from - Language from.
82
+ * @param {string} to - Language to.
83
+ * @param {string} [namespace='core']
84
+ */
85
+ translateFrom(label, from, to, namespace = 'core') {
86
+ if (!label) return label
87
+
88
+ const mapNs = this.#mapping?.[namespace]
89
+ if (!mapNs) return label
90
+
91
+ const mapFrom = mapNs?.[from]
92
+ const mapTo = mapNs?.[to]
93
+ if (!mapFrom || !mapTo) return label
94
+
95
+ const key = this.#findKeyByValue(mapFrom, label)
96
+ if (!key) return label
97
+
98
+ const entryTo = mapTo[key]
99
+ const resolvedTo = this.#resolve(entryTo)
100
+
101
+ return resolvedTo ?? label
102
+ }
103
+
104
+ _(...args) {
105
+ return this.translate(...args)
106
+ }
107
+
108
+ getLang() {
109
+ return this.#lang
110
+ }
111
+
112
+ setLang(lang) {
113
+ if (!lang) {
114
+ if (typeof navigator !== 'undefined' && navigator.language) {
115
+ lang = navigator.language
116
+ } else if (typeof process !== 'undefined' && process.env) {
117
+ lang = process.env.LANG || process.env.LC_ALL || process.env.LC_MESSAGES
118
+ }
119
+ }
120
+
121
+ this.#lang = (lang || 'en').trim().toLowerCase().slice(0, 2)
122
+ }
123
+
124
+ #findKeyByValue(entries, label) {
125
+ for (const key in entries) {
126
+ const resolved = this.#resolve(entries[key])
127
+ if (resolved === label) return key
128
+ }
129
+
130
+ return null
131
+ }
132
+
133
+ #resolve(entry) {
134
+ if (isFunction(entry)) {
135
+ return entry()
136
+ }
137
+
138
+ return entry
139
+ }
140
+ }
141
+
142
+ export default Translator
143
+
144
+ /* istanbul ignore next */
145
+ if (typeof window !== 'undefined') {
146
+ window.webf = window.webf || {}
147
+ window.webf.Translator = Translator
148
+ }
package/src/array.js ADDED
@@ -0,0 +1,136 @@
1
+ import { each, map } from './traversal.js'
2
+ import { isArray, isInteger, isObject, isString, isUndefined } from './is.js'
3
+ import { round } from './math.js'
4
+ import { equals } from './utils.js'
5
+
6
+ /**
7
+ * Checks if a value exists in an array or an object
8
+ *
9
+ * @param {any} value the searched value
10
+ * @param {Object|Array} arr the array
11
+ * @param {number} [index=0] if provided, search from this index
12
+ * @param {boolean} [strict=false] if true, search is done with strict equality
13
+ * @returns {boolean}
14
+ *
15
+ * @example
16
+ * inArray(2, [1, 2, 3]) => true
17
+ *
18
+ * @example
19
+ * inArray({a: 1}, {a: 1, b: 2}) // => true
20
+ *
21
+ * @example
22
+ * inArray(5, [1, 2, 3]) // => false
23
+ */
24
+ export const inArray = function (value, arr, index = 0, strict = false) {
25
+ let ret = false
26
+
27
+ each(arr, (i, val) => {
28
+ if (i >= index) {
29
+ if (strict) {
30
+ if (val === value) {
31
+ ret = true
32
+ return false
33
+ }
34
+ } else {
35
+ if (isObject(value) && isObject(val)) {
36
+ ret = equals(val, value)
37
+ return false
38
+ } else if (isArray(value) && isObject(val)) {
39
+ ret = equals(val, value)
40
+ return false
41
+ // eslint-disable-next-line eqeqeq
42
+ } else if (val == value) {
43
+ ret = true
44
+ return false
45
+ }
46
+ }
47
+ }
48
+ })
49
+
50
+ return ret
51
+ }
52
+
53
+ /**
54
+ * Returns the first index at which a given element can be found in an array or a string.
55
+ * or -1 if it is not present.
56
+ *
57
+ * @param {Array<any>|string} arr - The array to search in
58
+ * @param {any} elt - The element to search for
59
+ * @param {number} [from] - The index to start the search from. Can be negative.
60
+ * @returns {number} - The index of the element, or -1 if not found
61
+ */
62
+ export const indexOf = function (arr, elt, from = 0) {
63
+ const a = isString(arr) ? map(arr, (_, a) => a) : arr
64
+
65
+ from = from < 0 ? Math.ceil(from) + a.length : Math.floor(from)
66
+
67
+ for (; from < a.length; from++) {
68
+ if (from in a && a[from] === elt) {
69
+ return from
70
+ }
71
+ }
72
+
73
+ return -1
74
+ }
75
+
76
+ /**
77
+ * Returns the last index at which a given element can be found in an array or a string.
78
+ * or -1 if it is not present.
79
+ *
80
+ * @param {Array<any>|string} arr - The array to search in
81
+ * @param {any} elt - The element to search for
82
+ * @param {number} [from] - The index to start the search from. Can be negative.
83
+ * @returns {number} - The index of the element, or -1 if not found
84
+ */
85
+ export const lastIndexOf = function (arr, elt, from = -1) {
86
+ const a = isString(arr) ? map(arr, (_, a) => a) : arr
87
+
88
+ from = from < 0 ? a.length + Math.ceil(from) : Math.floor(from)
89
+
90
+ for (; from >= 0; from--) {
91
+ if (from in a && a[from] === elt) {
92
+ return from
93
+ }
94
+ }
95
+
96
+ return -1
97
+ }
98
+
99
+ export const arrayUnique = function (arr) {
100
+ return arr.filter((el, index, arr) => index === indexOf(arr, el))
101
+ }
102
+
103
+ export const array_unique = arrayUnique
104
+
105
+ export const arrayDiff = (array1, array2, strict = false) => {
106
+ return array1.filter((item) => !inArray(item, array2, 0, strict))
107
+ }
108
+
109
+ export const array_diff = arrayDiff
110
+
111
+ export const range = function (size, startAt = 0, step = 1) {
112
+ size = round(size)
113
+ step = round(step)
114
+
115
+ const rng = []
116
+
117
+ if (isUndefined(startAt) || size < 1 || step === 0 || size < Math.abs(step)) {
118
+ return rng
119
+ }
120
+
121
+ const end = size * step
122
+
123
+ if (isString(startAt)) {
124
+ startAt = startAt.charCodeAt(0)
125
+
126
+ for (let i = 0; step > 0 ? i < end : i > end; i += step) {
127
+ rng.push(String.fromCharCode(startAt + i))
128
+ }
129
+ } else if (isInteger(startAt)) {
130
+ for (let i = 0; step > 0 ? i < end : i > end; i += step) {
131
+ rng.push(startAt + i)
132
+ }
133
+ }
134
+
135
+ return rng
136
+ }