@formily-design/shared 1.0.0

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 (98) hide show
  1. package/LICENSE.md +20 -0
  2. package/README.md +1 -0
  3. package/dist/designable.shared.umd.production.js +1 -0
  4. package/dist/designable.shared.umd.production.min.js +2051 -0
  5. package/esm/animation.d.ts +2 -0
  6. package/esm/animation.js +33 -0
  7. package/esm/array.d.ts +37 -0
  8. package/esm/array.js +111 -0
  9. package/esm/clone.d.ts +4 -0
  10. package/esm/clone.js +97 -0
  11. package/esm/compose.d.ts +1 -0
  12. package/esm/compose.js +7 -0
  13. package/esm/coordinate.d.ts +106 -0
  14. package/esm/coordinate.js +475 -0
  15. package/esm/element.d.ts +6 -0
  16. package/esm/element.js +137 -0
  17. package/esm/event.d.ts +96 -0
  18. package/esm/event.js +213 -0
  19. package/esm/globalThisPolyfill.d.ts +1 -0
  20. package/esm/globalThisPolyfill.js +22 -0
  21. package/esm/index.d.ts +17 -0
  22. package/esm/index.js +17 -0
  23. package/esm/instanceof.d.ts +1 -0
  24. package/esm/instanceof.js +11 -0
  25. package/esm/keycode.d.ts +147 -0
  26. package/esm/keycode.js +150 -0
  27. package/esm/lru.d.ts +73 -0
  28. package/esm/lru.js +293 -0
  29. package/esm/observer.d.ts +9 -0
  30. package/esm/observer.js +29 -0
  31. package/esm/request-idle.d.ts +10 -0
  32. package/esm/request-idle.js +8 -0
  33. package/esm/scroller.d.ts +10 -0
  34. package/esm/scroller.js +82 -0
  35. package/esm/subscribable.d.ts +14 -0
  36. package/esm/subscribable.js +48 -0
  37. package/esm/types.d.ts +13 -0
  38. package/esm/types.js +21 -0
  39. package/esm/uid.d.ts +1 -0
  40. package/esm/uid.js +9 -0
  41. package/lib/animation.d.ts +2 -0
  42. package/lib/animation.js +38 -0
  43. package/lib/array.d.ts +37 -0
  44. package/lib/array.js +125 -0
  45. package/lib/clone.d.ts +4 -0
  46. package/lib/clone.js +102 -0
  47. package/lib/compose.d.ts +1 -0
  48. package/lib/compose.js +11 -0
  49. package/lib/coordinate.d.ts +106 -0
  50. package/lib/coordinate.js +504 -0
  51. package/lib/element.d.ts +6 -0
  52. package/lib/element.js +145 -0
  53. package/lib/event.d.ts +96 -0
  54. package/lib/event.js +218 -0
  55. package/lib/globalThisPolyfill.d.ts +1 -0
  56. package/lib/globalThisPolyfill.js +25 -0
  57. package/lib/index.d.ts +17 -0
  58. package/lib/index.js +33 -0
  59. package/lib/instanceof.d.ts +1 -0
  60. package/lib/instanceof.js +15 -0
  61. package/lib/keycode.d.ts +147 -0
  62. package/lib/keycode.js +154 -0
  63. package/lib/lru.d.ts +73 -0
  64. package/lib/lru.js +297 -0
  65. package/lib/observer.d.ts +9 -0
  66. package/lib/observer.js +33 -0
  67. package/lib/request-idle.d.ts +10 -0
  68. package/lib/request-idle.js +13 -0
  69. package/lib/scroller.d.ts +10 -0
  70. package/lib/scroller.js +88 -0
  71. package/lib/subscribable.d.ts +14 -0
  72. package/lib/subscribable.js +52 -0
  73. package/lib/types.d.ts +13 -0
  74. package/lib/types.js +29 -0
  75. package/lib/uid.d.ts +1 -0
  76. package/lib/uid.js +12 -0
  77. package/package.json +32 -0
  78. package/rollup.config.js +3 -0
  79. package/src/animation.ts +38 -0
  80. package/src/array.ts +301 -0
  81. package/src/clone.ts +96 -0
  82. package/src/compose.ts +7 -0
  83. package/src/coordinate.ts +633 -0
  84. package/src/element.ts +144 -0
  85. package/src/event.ts +379 -0
  86. package/src/globalThisPolyfill.ts +19 -0
  87. package/src/index.ts +17 -0
  88. package/src/instanceof.ts +10 -0
  89. package/src/keycode.ts +150 -0
  90. package/src/lru.ts +322 -0
  91. package/src/observer.ts +38 -0
  92. package/src/request-idle.ts +22 -0
  93. package/src/scroller.ts +113 -0
  94. package/src/subscribable.ts +60 -0
  95. package/src/types.ts +27 -0
  96. package/src/uid.ts +10 -0
  97. package/tsconfig.build.json +10 -0
  98. package/tsconfig.json +5 -0
package/src/keycode.ts ADDED
@@ -0,0 +1,150 @@
1
+ export enum KeyCode {
2
+ Backspace = 'Backspace',
3
+ Tab = 'Tab',
4
+ Enter = 'Enter',
5
+ Shift = 'Shift',
6
+ Control = 'Control',
7
+ Alt = 'Alt',
8
+ CapsLock = 'CapsLock',
9
+ Escape = 'Escape',
10
+ Space = ' ',
11
+ PageUp = 'PageUp',
12
+ PageDown = 'PageDown',
13
+ End = 'End',
14
+ Home = 'Home',
15
+ ArrowLeft = 'ArrowLeft',
16
+ ArrowUp = 'ArrowUp',
17
+ ArrowRight = 'ArrowRight',
18
+ ArrowDown = 'ArrowDown',
19
+ Left = 'Left',
20
+ Up = 'Up',
21
+ Right = 'Right',
22
+ Down = 'Down',
23
+ Insert = 'Insert',
24
+ Delete = 'Delete',
25
+ Zero = '0',
26
+ ClosedParen = ')',
27
+ One = '1',
28
+ ExclamationMark = '!',
29
+ Two = '2',
30
+ AtSign = '@',
31
+ Three = '3',
32
+ PoundSign = '£',
33
+ Hash = '#',
34
+ Four = '4',
35
+ DollarSign = '$',
36
+ Five = '5',
37
+ PercentSign = '%',
38
+ Six = '6',
39
+ Caret = '^',
40
+ Hat = '^',
41
+ Seven = '7',
42
+ Ampersand = '&',
43
+ Eight = '8',
44
+ Star = '*',
45
+ Asterisk = '*',
46
+ Nine = '9',
47
+ OpenParen = '(',
48
+ a = 'a',
49
+ b = 'b',
50
+ c = 'c',
51
+ d = 'd',
52
+ e = 'e',
53
+ f = 'f',
54
+ g = 'g',
55
+ h = 'h',
56
+ i = 'i',
57
+ j = 'j',
58
+ k = 'k',
59
+ l = 'l',
60
+ m = 'm',
61
+ n = 'n',
62
+ o = 'o',
63
+ p = 'p',
64
+ q = 'q',
65
+ r = 'r',
66
+ s = 's',
67
+ t = 't',
68
+ u = 'u',
69
+ v = 'v',
70
+ w = 'w',
71
+ x = 'x',
72
+ y = 'y',
73
+ z = 'z',
74
+ A = 'A',
75
+ B = 'B',
76
+ C = 'C',
77
+ D = 'D',
78
+ E = 'E',
79
+ F = 'F',
80
+ G = 'G',
81
+ H = 'H',
82
+ I = 'I',
83
+ J = 'J',
84
+ K = 'K',
85
+ L = 'L',
86
+ M = 'M',
87
+ N = 'N',
88
+ O = 'O',
89
+ P = 'P',
90
+ Q = 'Q',
91
+ R = 'R',
92
+ S = 'S',
93
+ T = 'T',
94
+ U = 'U',
95
+ V = 'V',
96
+ W = 'W',
97
+ X = 'X',
98
+ Y = 'Y',
99
+ Z = 'Z',
100
+ Meta = 'Meta',
101
+ LeftWindowKey = 'Meta',
102
+ RightWindowKey = 'Meta',
103
+ Numpad0 = '0',
104
+ Numpad1 = '1',
105
+ Numpad2 = '2',
106
+ Numpad3 = '3',
107
+ Numpad4 = '4',
108
+ Numpad5 = '5',
109
+ Numpad6 = '6',
110
+ Numpad7 = '7',
111
+ Numpad8 = '8',
112
+ Numpad9 = '9',
113
+ Multiply = '*',
114
+ Add = '+',
115
+ Subtract = '-',
116
+ DecimalPoint = '.',
117
+ MSDecimalPoint = 'Decimal',
118
+ Divide = '/',
119
+ F1 = 'F1',
120
+ F2 = 'F2',
121
+ F3 = 'F3',
122
+ F4 = 'F4',
123
+ F5 = 'F5',
124
+ F6 = 'F6',
125
+ F7 = 'F7',
126
+ F8 = 'F8',
127
+ F9 = 'F9',
128
+ F10 = 'F10',
129
+ F11 = 'F11',
130
+ F12 = 'F12',
131
+ NumLock = 'NumLock',
132
+ ScrollLock = 'ScrollLock',
133
+ SemiColon = ';',
134
+ Equals = '=',
135
+ Comma = ',',
136
+ Dash = '-',
137
+ Period = '.',
138
+ UnderScore = '_',
139
+ PlusSign = '+',
140
+ ForwardSlash = '/',
141
+ Tilde = '~',
142
+ GraveAccent = '`',
143
+ OpenBracket = '[',
144
+ ClosedBracket = ']',
145
+ Quote = "'",
146
+ }
147
+
148
+ export const getKeyCodeFromEvent = (event: KeyboardEvent): KeyCode => {
149
+ return event.key as any
150
+ }
package/src/lru.ts ADDED
@@ -0,0 +1,322 @@
1
+ /**
2
+ * A doubly linked list-based Least Recently Used (LRU) cache. Will keep most
3
+ * recently used items while discarding least recently used items when its limit
4
+ * is reached.
5
+ *
6
+ * Licensed under MIT. Copyright (c) 2010 Rasmus Andersson <http://hunch.se/>
7
+ * See README.md for details.
8
+ *
9
+ * Illustration of the design:
10
+ *
11
+ * entry entry entry entry
12
+ * ______ ______ ______ ______
13
+ * | head |.newer => | |.newer => | |.newer => | tail |
14
+ * | A | | B | | C | | D |
15
+ * |______| <= older.|______| <= older.|______| <= older.|______|
16
+ *
17
+ * removed <-- <-- <-- <-- <-- <-- <-- <-- <-- <-- <-- added
18
+ */
19
+ /* eslint-disable */
20
+
21
+ const NEWER = Symbol('newer')
22
+ const OLDER = Symbol('older')
23
+
24
+ function Entry(key: any, value: any) {
25
+ this.key = key
26
+ this.value = value
27
+ this[NEWER] = undefined
28
+ this[OLDER] = undefined
29
+ }
30
+
31
+ export class LRUMap<K, V> {
32
+ size: number
33
+ limit: number
34
+ oldest: any
35
+ newest: any
36
+ _keymap: Map<K, { key: K; value: V }>
37
+ constructor(limit: number, entries?: any) {
38
+ if (typeof limit !== 'number') {
39
+ // called as (entries)
40
+ entries = limit
41
+ limit = 0
42
+ }
43
+
44
+ this.size = 0
45
+ this.limit = limit
46
+ this.oldest = this.newest = undefined
47
+ this._keymap = new Map()
48
+
49
+ if (entries) {
50
+ this.assign(entries)
51
+ if (limit < 1) {
52
+ this.limit = this.size
53
+ }
54
+ }
55
+ }
56
+
57
+ private _markEntryAsUsed(entry: any) {
58
+ if (entry === this.newest) {
59
+ // Already the most recenlty used entry, so no need to update the list
60
+ return
61
+ }
62
+ // HEAD--------------TAIL
63
+ // <.older .newer>
64
+ // <--- add direction --
65
+ // A B C <D> E
66
+ if (entry[NEWER]) {
67
+ if (entry === this.oldest) {
68
+ this.oldest = entry[NEWER]
69
+ }
70
+ entry[NEWER][OLDER] = entry[OLDER] // C <-- E.
71
+ }
72
+ if (entry[OLDER]) {
73
+ entry[OLDER][NEWER] = entry[NEWER] // C. --> E
74
+ }
75
+ entry[NEWER] = undefined // D --x
76
+ entry[OLDER] = this.newest // D. --> E
77
+ if (this.newest) {
78
+ this.newest[NEWER] = entry // E. <-- D
79
+ }
80
+ this.newest = entry
81
+ }
82
+
83
+ assign(entries: any) {
84
+ let entry: any
85
+ let limit = this.limit || Number.MAX_VALUE
86
+ this._keymap.clear()
87
+ const it = entries[Symbol.iterator]()
88
+ for (let itv = it.next(); !itv.done; itv = it.next()) {
89
+ const e = new Entry(itv.value[0], itv.value[1])
90
+ this._keymap.set(e.key, e)
91
+ if (!entry) {
92
+ this.oldest = e
93
+ } else {
94
+ entry[NEWER] = e
95
+ e[OLDER] = entry
96
+ }
97
+ entry = e
98
+ if (limit-- === 0) {
99
+ throw new Error('overflow')
100
+ }
101
+ }
102
+ this.newest = entry
103
+ this.size = this._keymap.size
104
+ }
105
+
106
+ get(key: K) {
107
+ // First, find our cache entry
108
+ const entry = this._keymap.get(key)
109
+ if (!entry) {
110
+ return
111
+ } // Not cached. Sorry.
112
+ // As <key> was found in the cache, register it as being requested recently
113
+ this._markEntryAsUsed(entry)
114
+ return entry.value
115
+ }
116
+
117
+ set(key: K, value: V) {
118
+ let entry = this._keymap.get(key)
119
+
120
+ if (entry) {
121
+ // update existing
122
+ entry.value = value
123
+ this._markEntryAsUsed(entry)
124
+ return this
125
+ }
126
+
127
+ // new entry
128
+ this._keymap.set(key, (entry = new Entry(key, value)))
129
+
130
+ if (this.newest) {
131
+ // link previous tail to the new tail (entry)
132
+ this.newest[NEWER] = entry
133
+ entry[OLDER] = this.newest
134
+ } else {
135
+ // we're first in -- yay
136
+ this.oldest = entry
137
+ }
138
+
139
+ // add new entry to the end of the linked list -- it's now the freshest entry.
140
+ this.newest = entry
141
+ ++this.size
142
+ if (this.size > this.limit) {
143
+ // we hit the limit -- remove the head
144
+ this.shift()
145
+ }
146
+
147
+ return this
148
+ }
149
+
150
+ shift() {
151
+ // todo: handle special case when limit == 1
152
+ const entry = this.oldest
153
+ if (entry) {
154
+ if (this.oldest[NEWER]) {
155
+ // advance the list
156
+ this.oldest = this.oldest[NEWER]
157
+ this.oldest[OLDER] = undefined
158
+ } else {
159
+ // the cache is exhausted
160
+ this.oldest = undefined
161
+ this.newest = undefined
162
+ }
163
+ // Remove last strong reference to <entry> and remove links from the purged
164
+ // entry being returned:
165
+ entry[NEWER] = entry[OLDER] = undefined
166
+ this._keymap.delete(entry.key)
167
+ --this.size
168
+ return [entry.key, entry.value]
169
+ }
170
+ }
171
+
172
+ find(key: K) {
173
+ const e = this._keymap.get(key)
174
+ return e ? e.value : undefined
175
+ }
176
+
177
+ has(key: K) {
178
+ return this._keymap.has(key)
179
+ }
180
+
181
+ delete(key: any) {
182
+ const entry = this._keymap.get(key)
183
+ if (!entry) {
184
+ return
185
+ }
186
+ this._keymap.delete(entry.key)
187
+ if (entry[NEWER] && entry[OLDER]) {
188
+ // relink the older entry with the newer entry
189
+ entry[OLDER][NEWER] = entry[NEWER]
190
+ entry[NEWER][OLDER] = entry[OLDER]
191
+ } else if (entry[NEWER]) {
192
+ // remove the link to us
193
+ entry[NEWER][OLDER] = undefined
194
+ // link the newer entry to head
195
+ this.oldest = entry[NEWER]
196
+ } else if (entry[OLDER]) {
197
+ // remove the link to us
198
+ entry[OLDER][NEWER] = undefined
199
+ // link the newer entry to head
200
+ this.newest = entry[OLDER]
201
+ } else {
202
+ // if(entry[OLDER] === undefined && entry.newer === undefined) {
203
+ this.oldest = this.newest = undefined
204
+ }
205
+
206
+ this.size--
207
+ return entry.value
208
+ }
209
+
210
+ clear() {
211
+ // Not clearing links should be safe, as we don't expose live links to user
212
+ this.oldest = this.newest = undefined
213
+ this.size = 0
214
+ this._keymap.clear()
215
+ }
216
+
217
+ keys() {
218
+ return new KeyIterator(this.oldest)
219
+ }
220
+
221
+ values() {
222
+ return new ValueIterator(this.oldest)
223
+ }
224
+
225
+ entries() {}
226
+
227
+ forEach(fun: (value: any, key: any, ctx: object) => void, thisObj: any) {
228
+ if (typeof thisObj !== 'object') {
229
+ thisObj = this
230
+ }
231
+ let entry = this.oldest
232
+ while (entry) {
233
+ fun.call(thisObj, entry.value, entry.key, this)
234
+ entry = entry[NEWER]
235
+ }
236
+ }
237
+
238
+ toJSON() {
239
+ const s = new Array(this.size)
240
+ let i = 0
241
+ let entry = this.oldest
242
+ while (entry) {
243
+ s[i++] = { key: entry.key, value: entry.value }
244
+ entry = entry[NEWER]
245
+ }
246
+ return s
247
+ }
248
+
249
+ toString() {
250
+ let s = ''
251
+ let entry = this.oldest
252
+ while (entry) {
253
+ s += String(entry.key) + ':' + entry.value
254
+ entry = entry[NEWER]
255
+ if (entry) {
256
+ s += ' < '
257
+ }
258
+ }
259
+ return s
260
+ }
261
+
262
+ [Symbol.iterator]() {
263
+ return new EntryIterator(this.oldest)
264
+ }
265
+ }
266
+
267
+ class EntryIterator {
268
+ entry: any
269
+ constructor(oldestEntry: any) {
270
+ this.entry = oldestEntry
271
+ }
272
+ [Symbol.iterator]() {
273
+ return this
274
+ }
275
+ next() {
276
+ const ent = this.entry
277
+ if (ent) {
278
+ this.entry = ent[NEWER]
279
+ return { done: false, value: [ent.key, ent.value] }
280
+ } else {
281
+ return { done: true, value: undefined }
282
+ }
283
+ }
284
+ }
285
+
286
+ class KeyIterator {
287
+ entry: any
288
+ constructor(oldestEntry: any) {
289
+ this.entry = oldestEntry
290
+ }
291
+ [Symbol.iterator]() {
292
+ return this
293
+ }
294
+ next() {
295
+ const ent = this.entry
296
+ if (ent) {
297
+ this.entry = ent[NEWER]
298
+ return { done: false, value: ent.key }
299
+ } else {
300
+ return { done: true, value: undefined }
301
+ }
302
+ }
303
+ }
304
+
305
+ class ValueIterator {
306
+ entry: any
307
+ constructor(oldestEntry: any) {
308
+ this.entry = oldestEntry
309
+ }
310
+ [Symbol.iterator]() {
311
+ return this
312
+ }
313
+ next() {
314
+ const ent = this.entry
315
+ if (ent) {
316
+ this.entry = ent[NEWER]
317
+ return { done: false, value: ent.value }
318
+ } else {
319
+ return { done: true, value: undefined }
320
+ }
321
+ }
322
+ }
@@ -0,0 +1,38 @@
1
+ export class LayoutObserver {
2
+ private resizeObserver: ResizeObserver
3
+
4
+ private performanceObserver: PerformanceObserver
5
+
6
+ private mutationObserver: MutationObserver
7
+
8
+ private connected = false
9
+
10
+ constructor(observer: () => void = () => {}) {
11
+ this.resizeObserver = new ResizeObserver(() => observer())
12
+ this.performanceObserver = new PerformanceObserver(() => {
13
+ observer()
14
+ })
15
+ this.mutationObserver = new MutationObserver(() => observer())
16
+ }
17
+
18
+ observe = (target: HTMLElement | Element) => {
19
+ this.resizeObserver.observe(target)
20
+ this.performanceObserver.observe({
21
+ entryTypes: ['paint', 'element', 'layout-shift', 'event'],
22
+ })
23
+ this.mutationObserver.observe(target, {
24
+ attributeFilter: ['style'],
25
+ attributes: true,
26
+ })
27
+ this.connected = true
28
+ }
29
+
30
+ disconnect = () => {
31
+ if (this.connected) {
32
+ this.resizeObserver.disconnect()
33
+ this.performanceObserver.disconnect()
34
+ this.mutationObserver.disconnect()
35
+ }
36
+ this.connected = false
37
+ }
38
+ }
@@ -0,0 +1,22 @@
1
+ import 'requestidlecallback'
2
+ import { globalThisPolyfill } from './globalThisPolyfill'
3
+
4
+ export interface IIdleDeadline {
5
+ didTimeout: boolean
6
+ timeRemaining: () => DOMHighResTimeStamp
7
+ }
8
+
9
+ export interface IdleCallbackOptions {
10
+ timeout?: number
11
+ }
12
+
13
+ export const requestIdle = (
14
+ callback: (params: IIdleDeadline) => void,
15
+ options?: IdleCallbackOptions
16
+ ): number => {
17
+ return globalThisPolyfill['requestIdleCallback'](callback, options)
18
+ }
19
+
20
+ export const cancelIdle = (id: number) => {
21
+ globalThisPolyfill['cancelIdleCallback'](id)
22
+ }
@@ -0,0 +1,113 @@
1
+ import { IPoint } from './coordinate'
2
+ import { createUniformSpeedAnimation, calcSpeedFactor } from './animation'
3
+ import { isFn, isWindow } from './types'
4
+
5
+ const MAX_SPEED = 80 // px/s
6
+
7
+ export type ScrollDirection = 'begin' | 'end'
8
+
9
+ export interface IAutoScrollBasicInfo {
10
+ direction: ScrollDirection
11
+ speedFactor: number
12
+ speed: number
13
+ }
14
+
15
+ export const calcAutoScrollBasicInfo = (
16
+ point: IPoint,
17
+ axis: 'x' | 'y',
18
+ viewport: DOMRect,
19
+ maxSpeed = MAX_SPEED
20
+ ): IAutoScrollBasicInfo | null => {
21
+ const { left, right, top, bottom } = viewport
22
+ const { x, y } = point
23
+
24
+ let begin: number
25
+ let end: number
26
+ let pos: number
27
+ let speedFactor: number
28
+ if (axis === 'x') {
29
+ begin = left
30
+ end = right
31
+ pos = x
32
+ } else {
33
+ begin = top
34
+ end = bottom
35
+ pos = y
36
+ }
37
+
38
+ const scrollerSize = end - begin
39
+
40
+ const moveDistance = scrollerSize > 400 ? 100 : scrollerSize / 3
41
+ if (end - pos < moveDistance) {
42
+ return {
43
+ direction: 'end',
44
+ speedFactor,
45
+ speed: maxSpeed * calcSpeedFactor(end - pos, moveDistance),
46
+ }
47
+ } else if (pos - begin < moveDistance) {
48
+ return {
49
+ direction: 'begin',
50
+ speedFactor,
51
+ speed: maxSpeed * calcSpeedFactor(pos - begin, moveDistance),
52
+ }
53
+ }
54
+
55
+ return null
56
+ }
57
+
58
+ export const updateScrollValue = (
59
+ element: HTMLElement | Window,
60
+ axis: 'x' | 'y',
61
+ value: number,
62
+ callback?: (scrollValue: number) => void
63
+ ) => {
64
+ if (element) {
65
+ if (!isWindow(element)) {
66
+ if (axis === 'x') {
67
+ if (element.scrollLeft + value > element.scrollWidth) return
68
+ element.scrollLeft += value
69
+ if (isFn(callback)) {
70
+ callback(element.scrollLeft)
71
+ }
72
+ } else {
73
+ if (element.scrollTop + value > element.scrollHeight) return
74
+ element.scrollTop += value
75
+ if (isFn(callback)) {
76
+ callback(element.scrollTop)
77
+ }
78
+ }
79
+ } else {
80
+ if (axis === 'x') {
81
+ element.scrollBy({
82
+ left: value,
83
+ behavior: 'smooth',
84
+ })
85
+ } else {
86
+ element.scrollBy({
87
+ top: value,
88
+ behavior: 'smooth',
89
+ })
90
+ }
91
+ if (isFn(callback)) {
92
+ callback(value)
93
+ }
94
+ }
95
+ }
96
+ }
97
+
98
+ export const scrollAnimate = (
99
+ element: HTMLElement | Window,
100
+ axis: 'x' | 'y',
101
+ direction: 'begin' | 'end',
102
+ speed: number,
103
+ callback?: (scrollValue: number) => void
104
+ ) => {
105
+ return createUniformSpeedAnimation(speed, (delta) => {
106
+ updateScrollValue(
107
+ element,
108
+ axis,
109
+ direction === 'begin' ? 0 - delta : delta,
110
+ callback
111
+ )
112
+ })
113
+ }
@@ -0,0 +1,60 @@
1
+ import { isFn } from './types'
2
+
3
+ const UNSUBSCRIBE_ID_SYMBOL = Symbol('UNSUBSCRIBE_ID_SYMBOL')
4
+
5
+ export interface ISubscriber<Payload = any> {
6
+ (payload: Payload): void | boolean
7
+ }
8
+
9
+ export class Subscribable<ExtendsType = any> {
10
+ private subscribers: {
11
+ index?: number
12
+ [key: number]: ISubscriber
13
+ } = {
14
+ index: 0,
15
+ }
16
+
17
+ dispatch<T extends ExtendsType = any>(event: T, context?: any) {
18
+ let interrupted = false
19
+ for (const key in this.subscribers) {
20
+ if (isFn(this.subscribers[key])) {
21
+ event['context'] = context
22
+ if (this.subscribers[key](event) === false) {
23
+ interrupted = true
24
+ }
25
+ }
26
+ }
27
+ return interrupted ? false : true
28
+ }
29
+
30
+ subscribe(subscriber: ISubscriber) {
31
+ let id: number
32
+ if (isFn(subscriber)) {
33
+ id = this.subscribers.index + 1
34
+ this.subscribers[id] = subscriber
35
+ this.subscribers.index++
36
+ }
37
+
38
+ const unsubscribe = () => {
39
+ this.unsubscribe(id)
40
+ }
41
+
42
+ unsubscribe[UNSUBSCRIBE_ID_SYMBOL] = id
43
+
44
+ return unsubscribe
45
+ }
46
+
47
+ unsubscribe = (id?: number | string | (() => void)) => {
48
+ if (id === undefined || id === null) {
49
+ for (const key in this.subscribers) {
50
+ this.unsubscribe(key)
51
+ }
52
+ return
53
+ }
54
+ if (!isFn(id)) {
55
+ delete this.subscribers[id]
56
+ } else {
57
+ delete this.subscribers[id[UNSUBSCRIBE_ID_SYMBOL]]
58
+ }
59
+ }
60
+ }