@pihanga2/core 0.2.1 → 0.3.1

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.
@@ -1,6 +1,4 @@
1
- import React from "react"
2
1
  import ReactDOM from "react-dom/client"
3
- import { Provider } from "react-redux"
4
2
  import { Dispatch } from "react"
5
3
 
6
4
  import {
@@ -13,8 +11,11 @@ import {
13
11
  RegisterCardF,
14
12
  MetaCardMapperF,
15
13
  PiRegisterMetaCard,
14
+ PiMapProps,
15
+ WindowProps,
16
+ GenericCardParameterT,
16
17
  } from "./types"
17
- import { Card, registerCard, registerCardComponent, registerMetacard } from "./card"
18
+ import { createCardDeclaration, registerCard, registerCardComponent, registerMetacard, updateOrRegisterCard } from "./register_cards"
18
19
  import { createReducer } from "./reducer"
19
20
  import { ON_INIT_ACTION, currentRoute, init as routerInit } from "./router"
20
21
 
@@ -32,6 +33,7 @@ import {
32
33
  registerPOST,
33
34
  registerPUT,
34
35
  } from "./rest"
36
+ import { RootComponent } from "./root"
35
37
  const logger = getLogger("root")
36
38
 
37
39
  export type {
@@ -47,9 +49,11 @@ export type {
47
49
  StateMapper,
48
50
  PiReducer,
49
51
  PiRegisterMetaCard,
52
+ WindowProps,
50
53
  } from "./types"
51
54
  export { registerActions, actionTypesToEvents, createOnAction } from "./redux"
52
- export { Card, memo, createCardDeclaration, isCardRef } from "./card"
55
+ export { Card, usePiReducer } from "./card"
56
+ export { memo, createCardDeclaration, isCardRef } from "./register_cards"
53
57
  export { getLogger } from "./logger"
54
58
  export type { PiCardProps, PiCardRef } from "./types"
55
59
  export type { ErrorAction as RestErrorAction } from "./rest"
@@ -59,7 +63,12 @@ export { showPage, onInit, onShowPage, createShowPageAction, onNavigateToPage }
59
63
  export type { ShowPageEvent, NavigateToPageEvent } from "./router"
60
64
 
61
65
  export interface PiRegister {
66
+ //window(parameters: PiCardDef): PiCardRef
67
+
68
+ window<S extends ReduxState>(parameters: PiMapProps<WindowProps, S, {}>): PiCardRef
69
+
62
70
  card(name: string, parameters: PiCardDef): PiCardRef
71
+ updateCard(name: string, parameters: { [key: string]: GenericCardParameterT }): PiCardRef
63
72
 
64
73
  cardComponent(declaration: PiRegisterComponent): void
65
74
 
@@ -157,11 +166,23 @@ export function start<S extends Partial<ReduxState>>(
157
166
  }
158
167
  })
159
168
  })
169
+ // make pihanga's reducer interface available to cards
170
+ const anyStore: any = store
171
+ anyStore.piReducer = piReducer
172
+
160
173
  dispatchF = store.dispatch
161
174
 
162
175
  const card = registerCard(piReducer.register, dispatchF)
176
+ const updateCard = updateOrRegisterCard(piReducer.register, dispatchF)
177
+ const window = <S extends ReduxState>(p: PiMapProps<WindowProps, S, {}>): PiCardRef => {
178
+ return card("_window", { cardType: "framework", ...p })
179
+ }
180
+
181
+
163
182
  const register: PiRegister = {
183
+ window,
164
184
  card,
185
+ updateCard,
165
186
  cardComponent: registerCardComponent,
166
187
  metaCard: registerMetacard(card),
167
188
  reducer: piReducer,
@@ -181,13 +202,3 @@ export function start<S extends Partial<ReduxState>>(
181
202
 
182
203
  return register
183
204
  }
184
-
185
- function RootComponent(store: any) {
186
- return (
187
- <React.StrictMode>
188
- <Provider store={store}>
189
- <Card cardName="page" parentCard="" />
190
- </Provider>
191
- </React.StrictMode>
192
- )
193
- }
package/src/reducer.ts CHANGED
@@ -2,6 +2,7 @@ import { Action, Reducer } from "@reduxjs/toolkit"
2
2
  import {
3
3
  DispatchF,
4
4
  PiReducer,
5
+ PiReducerCancelF,
5
6
  PiRegisterOneShotReducerF,
6
7
  PiRegisterReducerF,
7
8
  ReduceF,
@@ -14,6 +15,7 @@ import { RegisterCardState, UPDATE_STATE_ACTION } from "./card"
14
15
  import StackTrace from "stacktrace-js"
15
16
  import { getLogger } from "./logger"
16
17
  import { Dispatch } from "react"
18
+ import { currentRoute } from "./router"
17
19
 
18
20
  const logger = getLogger("reducer")
19
21
 
@@ -21,6 +23,7 @@ type ReducerDef<S extends ReduxState, A extends ReduxAction> = {
21
23
  mapperMany?: ReduceF<S, A>
22
24
  mapperOnce?: ReduceOnceF<S, A>
23
25
  priority?: number
26
+ key?: string
24
27
  definedIn?: StackTrace.StackFrame
25
28
  }
26
29
 
@@ -40,34 +43,21 @@ export function createReducer(
40
43
  ): ReduxState => {
41
44
  const s = state || initialState
42
45
  const ra = mappings[action.type]
43
- if (!ra || ra.length === 0) {
46
+ const rany = mappings["*"]
47
+ if ((!ra || ra.length === 0) && (!rany || rany.length === 0)) {
44
48
  return s
45
49
  }
46
50
 
47
51
  const nextState = produce<ReduxState, ReduxState>(s, (draft) => {
48
- const rout: ReducerDef<ReduxState, Action<any>>[] = []
49
- const outDraft = ra.reduce((d, m) => {
50
- try {
51
- if (m.mapperMany) {
52
- const s = m.mapperMany(d, action, delayedDispatcher)
53
- rout.push(m)
54
- return s
55
- } else if (m.mapperOnce) {
56
- const [s, flag] = m.mapperOnce(d, action, delayedDispatcher)
57
- if (!flag) {
58
- rout.push(m)
59
- }
60
- return s
61
- } else {
62
- return d
63
- }
64
- } catch (err: any) {
65
- logger.error(err.message, m.definedIn)
66
- return d
67
- }
68
- }, draft)
69
- mappings[action.type] = rout
70
- return outDraft
52
+ if (ra) {
53
+ const rout = _reduce(ra, draft, action, delayedDispatcher)
54
+ mappings[action.type] = rout
55
+ }
56
+ if (rany) {
57
+ const rout2 = _reduce(rany, draft, action, delayedDispatcher)
58
+ mappings["*"] = rout2
59
+ }
60
+ return
71
61
  })
72
62
  return nextState
73
63
  }
@@ -79,8 +69,9 @@ export function createReducer(
79
69
  eventType: string,
80
70
  mapper: ReduceF<S, A>,
81
71
  priority: number = 0,
82
- ): void => {
83
- addReducer(eventType, { mapperMany: mapper, priority })
72
+ key: string | undefined = undefined
73
+ ): PiReducerCancelF => {
74
+ return addReducer(eventType, { mapperMany: mapper, priority, key })
84
75
  }
85
76
 
86
77
  const registerOneShot: PiRegisterOneShotReducerF = <
@@ -88,18 +79,22 @@ export function createReducer(
88
79
  A extends ReduxAction,
89
80
  >(
90
81
  eventType: string,
91
- mapper: (state: S, action: A, dispatch: DispatchF) => [S, boolean],
82
+ mapper: (state: S, action: A, dispatch: DispatchF) => boolean,
92
83
  priority: number = 0,
93
- ): void => {
94
- addReducer(eventType, { mapperOnce: mapper, priority })
84
+ key: string | undefined = undefined
85
+ ): PiReducerCancelF => {
86
+ return addReducer(eventType, { mapperOnce: mapper, priority, key })
95
87
  }
96
88
 
89
+ const nonCancelF = () => {}
90
+
97
91
  function addReducer<S extends ReduxState, A extends ReduxAction>(
98
92
  eventType: string,
99
93
  reducerDef: ReducerDef<S, A>,
100
- // mappings: { [k: string]: ReducerDef<ReduxState, Action>[] }
101
- ) {
102
- const m = mappings[eventType] || []
94
+ ): PiReducerCancelF {
95
+ let m = mappings[eventType] || []
96
+ const key = reducerDef.key
97
+ m = removeReducer(key, m)
103
98
  m.push(reducerDef as any as ReducerDef<ReduxState, Action<any>>) // keep typing happy
104
99
  m.sort((a, b) => (b.priority || 0) - (a.priority || 0))
105
100
  mappings[eventType] = m
@@ -114,6 +109,10 @@ export function createReducer(
114
109
  logger.warn(err.message)
115
110
  }
116
111
  StackTrace.get().then(callback).catch(errback)
112
+ return key ? () => {
113
+ let m = mappings[eventType] || []
114
+ mappings[eventType] = removeReducer(key, m)
115
+ } : nonCancelF
117
116
  }
118
117
 
119
118
  const piReducer: PiReducer = {
@@ -125,3 +124,39 @@ export function createReducer(
125
124
 
126
125
  return [reducer, piReducer]
127
126
  }
127
+
128
+ function removeReducer(
129
+ key: string | undefined,
130
+ m: ReducerDef<ReduxState, Action>[]
131
+ ) {
132
+ if (key) {
133
+ return m.filter((r) => r.key !== key)
134
+ } else {
135
+ return m
136
+ }
137
+ }
138
+
139
+ function _reduce(
140
+ ra: ReducerDef<ReduxState, Action>[],
141
+ draft: ReduxState,
142
+ action: Action,
143
+ delayedDispatcher: (a: any) => void,
144
+ ): ReducerDef<ReduxState, Action<any>>[] {
145
+ const rout: ReducerDef<ReduxState, Action<any>>[] = []
146
+ ra.forEach((m) => {
147
+ try {
148
+ if (m.mapperMany) {
149
+ m.mapperMany(draft, action, delayedDispatcher)
150
+ rout.push(m)
151
+ } else if (m.mapperOnce) {
152
+ const flag = m.mapperOnce(draft, action, delayedDispatcher)
153
+ if (!flag) {
154
+ rout.push(m)
155
+ }
156
+ }
157
+ } catch (err: any) {
158
+ logger.error(err.message, m.definedIn)
159
+ }
160
+ })
161
+ return rout
162
+ }
@@ -0,0 +1,308 @@
1
+ import equal from "deep-equal"
2
+ import { getLogger } from "./logger"
3
+ import {
4
+ CSSModuleClasses,
5
+ CardAction,
6
+ CardProp,
7
+ DispatchF,
8
+ GenericCardParameterT,
9
+ MetaCardMapperF,
10
+ PiCardDef,
11
+ PiCardRef,
12
+ PiMapProps,
13
+ PiRegisterComponent,
14
+ PiRegisterMetaCard,
15
+ PiRegisterReducerF,
16
+ ReduxState,
17
+ RegisterCardF,
18
+ StateMapper,
19
+ StateMapperContext,
20
+ } from "./types"
21
+ import { Action, AnyAction, Dispatch } from "@reduxjs/toolkit"
22
+
23
+ const logger = getLogger("card-register")
24
+
25
+ export function isCardRef(p: any): boolean {
26
+ return (typeof p === "object" && p.cardType !== undefined)
27
+ }
28
+
29
+ export type Mapping = {
30
+ cardType: string
31
+ props: { [k: string]: unknown }
32
+ eventMappers: { [k: string]: (ev: Action) => Action | null }
33
+ cardEvents: { [key: string]: string }
34
+ parameters: PiCardDef // original
35
+ }
36
+
37
+ export type MetaCard = {
38
+ type: string
39
+ registerCard: RegisterCardF
40
+ mapper: MetaCardMapperF
41
+ events?: { [key: string]: string }
42
+ }
43
+
44
+ export const cardTypes: { [k: string]: PiRegisterComponent } = {}
45
+ export const metacardTypes: { [k: string]: MetaCard } = {}
46
+
47
+ export let framework: string // name of active UI framework
48
+ export const cardMappings: { [k: string]: Mapping } = {}
49
+ export const dispatch2registerReducer: [React.Dispatch<any>, PiRegisterReducerF][] = []
50
+
51
+
52
+ export function registerCardComponent(card: PiRegisterComponent): void {
53
+ if (cardTypes[card.name]) {
54
+ logger.warn(`Overwriting definition for card type "${card.name}"`)
55
+ }
56
+ logger.info(`Register card type "${card.name}"`)
57
+ if (!framework) {
58
+ // set default framework
59
+ const na = card.name.split("/")
60
+ if (na.length >= 2) {
61
+ framework = na[0]
62
+ logger.info(`Setting UI framework to "${framework}"`)
63
+ }
64
+ }
65
+ cardTypes[card.name] = card
66
+ }
67
+
68
+ export function registerMetacard(registerCard: RegisterCardF) {
69
+ function f<C>(declaration: PiRegisterMetaCard) {
70
+ const { type, mapper, events } = declaration
71
+ if (metacardTypes[type]) {
72
+ logger.warn(`Overwriting definition for meta card type "${type}"`)
73
+ }
74
+ logger.info(`Register meta card type "${type}"`)
75
+ metacardTypes[type] = { type, registerCard, mapper, events }
76
+ }
77
+ return f
78
+ }
79
+
80
+
81
+ export function registerCard(
82
+ registerReducer: PiRegisterReducerF,
83
+ dispatchF: React.Dispatch<any>,
84
+ ) {
85
+ // to be used by dynamically registered cards
86
+ dispatch2registerReducer.push([dispatchF, registerReducer])
87
+ return (name: string, parameters: PiCardDef): PiCardRef => {
88
+ return _registerCard(name, parameters, registerReducer)
89
+ }
90
+ }
91
+
92
+ export function updateOrRegisterCard(
93
+ registerReducer: PiRegisterReducerF,
94
+ dispatchF: React.Dispatch<any>,
95
+ ) {
96
+ // to be used by dynamically registered cards
97
+ dispatch2registerReducer.push([dispatchF, registerReducer])
98
+ return (name: string, parameters: { [key: string]: GenericCardParameterT }): PiCardRef => {
99
+ return _updateCard(name, parameters, registerReducer)
100
+ }
101
+ }
102
+
103
+ export function _registerCard(
104
+ name: string,
105
+ parameters: PiCardDef,
106
+ registerReducer: PiRegisterReducerF,
107
+ overrideEvents?: { [key: string]: string },
108
+ ): PiCardRef {
109
+ if (cardMappings[name]) {
110
+ logger.warn(`Overwriting definition for card "${name}"`)
111
+ }
112
+ let cardType = cardTypes[parameters.cardType]
113
+ if (!cardType) {
114
+ if (framework) {
115
+ cardType = cardTypes[`${framework}/${parameters.cardType}`]
116
+ }
117
+ if (!cardType) {
118
+ // maybe it's a metadata card
119
+ if (_registerMetadataCard(name, parameters, registerReducer)) {
120
+ return name
121
+ }
122
+ logger.warn("unknown card type", parameters.cardType)
123
+ return name
124
+ }
125
+ }
126
+
127
+ const events = overrideEvents || cardType.events || {}
128
+ _createCardMapping(name, parameters, registerReducer, events)
129
+ return name
130
+ }
131
+
132
+ export function _updateCard(
133
+ name: string,
134
+ parameters: { [key: string]: GenericCardParameterT },
135
+ registerReducer: PiRegisterReducerF,
136
+ overrideEvents?: { [key: string]: string },
137
+ ): PiCardRef {
138
+ const mappings = cardMappings[name]
139
+ if (!mappings) {
140
+ // first time
141
+ if (!parameters.cardType) {
142
+ logger.warn("missing 'cardType'", name)
143
+ return name
144
+ }
145
+ const p: any = parameters
146
+ return _registerCard(name, p, registerReducer, overrideEvents)
147
+ }
148
+
149
+ const p = { ...mappings.parameters, ...parameters }
150
+ _createCardMapping(name, p, registerReducer, mappings.cardEvents)
151
+ return name
152
+
153
+ }
154
+
155
+ export function _createCardMapping(
156
+ name: string,
157
+ parameters: PiCardDef,
158
+ registerReducer: PiRegisterReducerF,
159
+ cardEvents: { [key: string]: string },
160
+ ) {
161
+ const props = {} as { [k: string]: unknown }
162
+ const eventMappers = {} as { [k: string]: (ev: Action) => Action }
163
+
164
+ Object.entries(parameters).forEach(([k, v]) => {
165
+ if (k === "cardType") return
166
+ if (typeof v === "object") {
167
+ const cd = v as PiCardDef // speculative
168
+ if (cd.cardType) {
169
+ const cardName = `${name}/${k}`
170
+ v = _registerCard(cardName, cd, registerReducer)
171
+ }
172
+ }
173
+ if (
174
+ k.startsWith("on") &&
175
+ processEventParameter(k, v, cardEvents, eventMappers, registerReducer, name)
176
+ ) {
177
+ return
178
+ }
179
+ props[k] = v
180
+ })
181
+ cardMappings[name] = { cardType: parameters.cardType, props, eventMappers, cardEvents, parameters }
182
+ }
183
+
184
+ export function _updateCardMapping(
185
+ name: string,
186
+ parameters: PiCardDef,
187
+ registerReducer: PiRegisterReducerF,
188
+ mappings: Mapping,
189
+ ) {
190
+ return _createCardMapping(name, parameters, registerReducer, mappings.cardEvents)
191
+ }
192
+
193
+ function _registerMetadataCard(
194
+ metaName: string,
195
+ parameters: PiCardDef,
196
+ registerReducer: PiRegisterReducerF,
197
+ ): boolean {
198
+ let mc = metacardTypes[parameters.cardType]
199
+ if (!mc) {
200
+ if (framework) {
201
+ mc = metacardTypes[`${framework}/${parameters.cardType}`]
202
+ }
203
+ if (!mc) {
204
+ return false
205
+ }
206
+ }
207
+ function registerCard(name: string, parameters: PiCardDef): PiCardRef {
208
+ const n = `${metaName}/${name}`
209
+ return mc.registerCard(n, parameters)
210
+ }
211
+ const top = mc.mapper(metaName, parameters, registerCard)
212
+ _registerCard(metaName, top, registerReducer, mc.events)
213
+ return true
214
+ }
215
+
216
+ // NOT IMPLEMENTED YET
217
+ // function _updateMetadataCard(
218
+ // metaName: string,
219
+ // parameters: PiCardDef,
220
+ // registerReducer: PiRegisterReducerF,
221
+ // ): boolean {
222
+ // let mc = metacardTypes[parameters.cardType]
223
+ // if (!mc) {
224
+ // if (framework) {
225
+ // mc = metacardTypes[`${framework}/${parameters.cardType}`]
226
+ // }
227
+ // if (!mc) {
228
+ // return false
229
+ // }
230
+ // }
231
+ // function updateCard(name: string, parameters: PiCardDef): PiCardRef {
232
+ // const n = `${metaName}/${name}`
233
+ // return mc.updateCard(n, parameters)
234
+ // }
235
+ // const top = mc.mapper(metaName, parameters, updateCard)
236
+ // _updateCard(metaName, top, registerReducer, mc.events)
237
+ // return true
238
+ // }
239
+
240
+ export function createCardDeclaration<Props = {}, Events = {}>(
241
+ cardType: string,
242
+ ): <S extends ReduxState>(p: PiMapProps<Props, S, Events>) => PiCardDef {
243
+ return (p) => ({
244
+ ...p,
245
+ cardType,
246
+ })
247
+ }
248
+
249
+ function processEventParameter(
250
+ propName: string,
251
+ value: unknown,
252
+ events: { [key: string]: string },
253
+ eventMappers: { [k: string]: (ev: Action) => Action },
254
+ registerReducer: PiRegisterReducerF,
255
+ cardName: string,
256
+ ): boolean {
257
+ const eva = Object.entries(events).find(([n, _]) => {
258
+ return propName === n || propName === `${n}Mapper`
259
+ })
260
+ if (!eva) {
261
+ logger.warn(
262
+ `encountered property '${propName}' for card '${cardName}' which looks like an even but is not defined`,
263
+ )
264
+ return false
265
+ }
266
+
267
+ const [evName, actionType] = eva
268
+ if (propName === evName) {
269
+ const r = value as (state: ReduxState, action: CardAction, dispatch: DispatchF) => ReduxState
270
+ registerReducer(actionType, (s, a, d) => {
271
+ const ca = a as CardAction
272
+ if (ca.cardID === cardName) {
273
+ s = r(s, ca, d)
274
+ }
275
+ return s
276
+ }, 0, `${cardName}|${propName}`)
277
+ }
278
+ if (propName === `${evName}Mapper`) {
279
+ logger.debug("processEventParameter", cardName)
280
+
281
+ const m = value as (ev: Action) => Action
282
+ eventMappers[evName] = m
283
+ }
284
+ return true
285
+ }
286
+
287
+ export function memo<P, T, S extends ReduxState, C>(
288
+ filterF: (state: S, context: StateMapperContext<C>) => P,
289
+ mapperF: (partial: P, context: StateMapperContext<C>, state: S) => T,
290
+ ): (state: S, context: StateMapperContext<C>) => T {
291
+ const lastFilter: { [k: string]: P } = {}
292
+ const lastValue: { [k: string]: T } = {}
293
+ const isNotFirst: { [k: string]: boolean } = {}
294
+
295
+ return (state: S, context: StateMapperContext<C>): T => {
296
+ const k = context.cardKey || "-"
297
+ const fv = filterF(state, context)
298
+ if (isNotFirst[k] && equal(fv, lastFilter[k])) {
299
+ // nothing changed
300
+ return lastValue[k]
301
+ }
302
+ lastFilter[k] = fv
303
+ const v = mapperF(fv, context, state)
304
+ lastValue[k] = v
305
+ isNotFirst[k] = true
306
+ return v
307
+ }
308
+ }
package/src/rest/types.ts CHANGED
@@ -57,7 +57,7 @@ export type RegisterGenericProps<
57
57
  reply: R,
58
58
  dispatcher: DispatchF,
59
59
  result: ResultAction<A>,
60
- ) => S
60
+ ) => void
61
61
  error?: (
62
62
  state: S,
63
63
  error: ErrorAction<A>,
package/src/root.tsx ADDED
@@ -0,0 +1,14 @@
1
+ import React from "react"
2
+ import { Provider } from "react-redux"
3
+
4
+ import { Card } from "./card"
5
+
6
+ export function RootComponent(store: any) {
7
+ return (
8
+ <React.StrictMode>
9
+ <Provider store={store}>
10
+ <Card cardName="_window" parentCard="" />
11
+ </Provider>
12
+ </React.StrictMode>
13
+ )
14
+ }
package/src/types.ts CHANGED
@@ -31,13 +31,13 @@ export type ReduceF<S extends ReduxState, A extends ReduxAction> = (
31
31
  state: S,
32
32
  action: A,
33
33
  dispatch: DispatchF,
34
- ) => S
34
+ ) => void // S
35
35
 
36
36
  export type ReduceOnceF<S extends ReduxState, A extends ReduxAction> = (
37
37
  state: S,
38
38
  action: A,
39
39
  dispatch: DispatchF,
40
- ) => [S, boolean]
40
+ ) => boolean // [S, boolean]
41
41
 
42
42
  export type DispatchF = <T extends ReduxAction>(a: T) => void
43
43
 
@@ -52,7 +52,10 @@ export type PiRegisterReducerF = <S extends ReduxState, A extends ReduxAction>(
52
52
  eventType: string,
53
53
  mapper: ReduceF<S, A>, // (state: S, action: A, dispatch: DispatchF) => S,
54
54
  priority?: number,
55
- ) => void
55
+ key?: string,
56
+ ) => PiReducerCancelF
57
+
58
+ export type PiReducerCancelF = () => void
56
59
 
57
60
  export type PiRegisterOneShotReducerF = <
58
61
  S extends ReduxState,
@@ -76,6 +79,12 @@ export type CardProp = {
76
79
  parentCard: string
77
80
  } & PiDefCtxtProps
78
81
 
82
+ // props for the 'root' of all cards
83
+ export type WindowProps = {
84
+ page: PiCardRef
85
+ framework?: string // select framework to render window
86
+ theme?: any // depends on framework
87
+ }
79
88
 
80
89
  // type which needs to be implemented by card components
81
90
  export type PiCardProps<P, E = {}> = P & {
@@ -84,8 +93,8 @@ export type PiCardProps<P, E = {}> = P & {
84
93
  _cls: (elName: string | string[], styles?: CSSModuleClasses) => string
85
94
  _dispatch: DispatchF
86
95
  } & {
87
- [Key in keyof E]: (ev: E[Key]) => void
88
- }
96
+ [Key in keyof E]: (ev: E[Key]) => void
97
+ }
89
98
 
90
99
  export type CSSModuleClasses = { readonly [key: string]: string }
91
100