@remix-run/ui 0.0.0 → 0.1.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.
- package/LICENSE +21 -0
- package/README.md +195 -2
- package/dist/animation/animate-layout-mixin.d.ts +12 -0
- package/dist/animation/animate-layout-mixin.js +214 -0
- package/dist/animation/animate-layout-mixin.js.map +1 -0
- package/dist/animation/animate-mixins.d.ts +29 -0
- package/dist/animation/animate-mixins.js +235 -0
- package/dist/animation/animate-mixins.js.map +1 -0
- package/dist/animation/index.d.ts +6 -0
- package/dist/animation/index.js +5 -0
- package/dist/animation/index.js.map +1 -0
- package/dist/animation/spring.d.ts +75 -0
- package/dist/animation/spring.js +183 -0
- package/dist/animation/spring.js.map +1 -0
- package/dist/animation/tween.d.ts +70 -0
- package/dist/animation/tween.js +93 -0
- package/dist/animation/tween.js.map +1 -0
- package/dist/components/accordion/accordion.d.ts +92 -0
- package/dist/components/accordion/accordion.js +337 -0
- package/dist/components/accordion/accordion.js.map +1 -0
- package/dist/components/anchor/anchor.d.ts +13 -0
- package/dist/components/anchor/anchor.js +558 -0
- package/dist/components/anchor/anchor.js.map +1 -0
- package/dist/components/breadcrumbs/breadcrumbs.d.ts +11 -0
- package/dist/components/breadcrumbs/breadcrumbs.js +78 -0
- package/dist/components/breadcrumbs/breadcrumbs.js.map +1 -0
- package/dist/components/button/button.d.ts +23 -0
- package/dist/components/button/button.js +147 -0
- package/dist/components/button/button.js.map +1 -0
- package/dist/components/combobox/combobox.d.ts +101 -0
- package/dist/components/combobox/combobox.js +708 -0
- package/dist/components/combobox/combobox.js.map +1 -0
- package/dist/components/glyph/glyph.d.ts +14 -0
- package/dist/components/glyph/glyph.js +65 -0
- package/dist/components/glyph/glyph.js.map +1 -0
- package/dist/components/listbox/listbox.d.ts +67 -0
- package/dist/components/listbox/listbox.js +340 -0
- package/dist/components/listbox/listbox.js.map +1 -0
- package/dist/components/menu/hover-aim.d.ts +5 -0
- package/dist/components/menu/hover-aim.js +308 -0
- package/dist/components/menu/hover-aim.js.map +1 -0
- package/dist/components/menu/menu.d.ts +164 -0
- package/dist/components/menu/menu.js +1106 -0
- package/dist/components/menu/menu.js.map +1 -0
- package/dist/components/popover/popover.d.ts +35 -0
- package/dist/components/popover/popover.js +138 -0
- package/dist/components/popover/popover.js.map +1 -0
- package/dist/components/select/select.d.ts +67 -0
- package/dist/components/select/select.js +397 -0
- package/dist/components/select/select.js.map +1 -0
- package/dist/components/separator/separator.d.ts +2 -0
- package/dist/components/separator/separator.js +15 -0
- package/dist/components/separator/separator.js.map +1 -0
- package/dist/components/tabs/tabs.d.ts +78 -0
- package/dist/components/tabs/tabs.js +341 -0
- package/dist/components/tabs/tabs.js.map +1 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/interactions/keydown/keydown.d.ts +1 -0
- package/dist/interactions/keydown/keydown.js +8 -0
- package/dist/interactions/keydown/keydown.js.map +1 -0
- package/dist/interactions/outside-click/outside-click-mixin.d.ts +2 -0
- package/dist/interactions/outside-click/outside-click-mixin.js +29 -0
- package/dist/interactions/outside-click/outside-click-mixin.js.map +1 -0
- package/dist/interactions/typeahead/typeahead-mixin.d.ts +8 -0
- package/dist/interactions/typeahead/typeahead-mixin.js +59 -0
- package/dist/interactions/typeahead/typeahead-mixin.js.map +1 -0
- package/dist/jsx-runtime.d.ts +2 -0
- package/dist/jsx-runtime.js +3 -0
- package/dist/jsx-runtime.js.map +1 -0
- package/dist/runtime/client-entries.d.ts +91 -0
- package/dist/runtime/client-entries.js +40 -0
- package/dist/runtime/client-entries.js.map +1 -0
- package/dist/runtime/component.d.ts +223 -0
- package/dist/runtime/component.js +152 -0
- package/dist/runtime/component.js.map +1 -0
- package/dist/runtime/create-element.d.ts +10 -0
- package/dist/runtime/create-element.js +29 -0
- package/dist/runtime/create-element.js.map +1 -0
- package/dist/runtime/diff-dom.d.ts +2 -0
- package/dist/runtime/diff-dom.js +364 -0
- package/dist/runtime/diff-dom.js.map +1 -0
- package/dist/runtime/diff-props.d.ts +9 -0
- package/dist/runtime/diff-props.js +195 -0
- package/dist/runtime/diff-props.js.map +1 -0
- package/dist/runtime/document-state.d.ts +11 -0
- package/dist/runtime/document-state.js +106 -0
- package/dist/runtime/document-state.js.map +1 -0
- package/dist/runtime/dom.d.ts +3189 -0
- package/dist/runtime/dom.js +2 -0
- package/dist/runtime/dom.js.map +1 -0
- package/dist/runtime/error-event.d.ts +20 -0
- package/dist/runtime/error-event.js +19 -0
- package/dist/runtime/error-event.js.map +1 -0
- package/dist/runtime/event-listeners.d.ts +50 -0
- package/dist/runtime/event-listeners.js +31 -0
- package/dist/runtime/event-listeners.js.map +1 -0
- package/dist/runtime/frame.d.ts +119 -0
- package/dist/runtime/frame.js +920 -0
- package/dist/runtime/frame.js.map +1 -0
- package/dist/runtime/invariant.d.ts +23 -0
- package/dist/runtime/invariant.js +33 -0
- package/dist/runtime/invariant.js.map +1 -0
- package/dist/runtime/jsx.d.ts +320 -0
- package/dist/runtime/jsx.js +32 -0
- package/dist/runtime/jsx.js.map +1 -0
- package/dist/runtime/mixins/attrs-mixin.d.ts +9 -0
- package/dist/runtime/mixins/attrs-mixin.js +23 -0
- package/dist/runtime/mixins/attrs-mixin.js.map +1 -0
- package/dist/runtime/mixins/link-mixin.d.ts +17 -0
- package/dist/runtime/mixins/link-mixin.js +101 -0
- package/dist/runtime/mixins/link-mixin.js.map +1 -0
- package/dist/runtime/mixins/mixin.d.ts +145 -0
- package/dist/runtime/mixins/mixin.js +570 -0
- package/dist/runtime/mixins/mixin.js.map +1 -0
- package/dist/runtime/mixins/on-mixin.d.ts +16 -0
- package/dist/runtime/mixins/on-mixin.js +49 -0
- package/dist/runtime/mixins/on-mixin.js.map +1 -0
- package/dist/runtime/mixins/ref-mixin.d.ts +9 -0
- package/dist/runtime/mixins/ref-mixin.js +21 -0
- package/dist/runtime/mixins/ref-mixin.js.map +1 -0
- package/dist/runtime/navigation.d.ts +28 -0
- package/dist/runtime/navigation.js +107 -0
- package/dist/runtime/navigation.js.map +1 -0
- package/dist/runtime/reconcile.d.ts +11 -0
- package/dist/runtime/reconcile.js +1262 -0
- package/dist/runtime/reconcile.js.map +1 -0
- package/dist/runtime/render.d.ts +54 -0
- package/dist/runtime/render.js +50 -0
- package/dist/runtime/render.js.map +1 -0
- package/dist/runtime/run.d.ts +45 -0
- package/dist/runtime/run.js +68 -0
- package/dist/runtime/run.js.map +1 -0
- package/dist/runtime/scheduler.d.ts +30 -0
- package/dist/runtime/scheduler.js +195 -0
- package/dist/runtime/scheduler.js.map +1 -0
- package/dist/runtime/svg-attributes.d.ts +5 -0
- package/dist/runtime/svg-attributes.js +124 -0
- package/dist/runtime/svg-attributes.js.map +1 -0
- package/dist/runtime/to-vnode.d.ts +3 -0
- package/dist/runtime/to-vnode.js +46 -0
- package/dist/runtime/to-vnode.js.map +1 -0
- package/dist/runtime/typed-event-target.d.ts +50 -0
- package/dist/runtime/typed-event-target.js +6 -0
- package/dist/runtime/typed-event-target.js.map +1 -0
- package/dist/runtime/vdom.d.ts +55 -0
- package/dist/runtime/vdom.js +198 -0
- package/dist/runtime/vdom.js.map +1 -0
- package/dist/runtime/vnode.d.ts +79 -0
- package/dist/runtime/vnode.js +38 -0
- package/dist/runtime/vnode.js.map +1 -0
- package/dist/server/stream.d.ts +57 -0
- package/dist/server/stream.js +1011 -0
- package/dist/server/stream.js.map +1 -0
- package/dist/style/css-mixin.d.ts +8 -0
- package/dist/style/css-mixin.js +54 -0
- package/dist/style/css-mixin.js.map +1 -0
- package/dist/style/index.d.ts +5 -0
- package/dist/style/index.js +4 -0
- package/dist/style/index.js.map +1 -0
- package/dist/style/style.d.ts +24 -0
- package/dist/style/style.js +269 -0
- package/dist/style/style.js.map +1 -0
- package/dist/style/stylesheet.d.ts +11 -0
- package/dist/style/stylesheet.js +174 -0
- package/dist/style/stylesheet.js.map +1 -0
- package/dist/test.d.ts +2 -0
- package/dist/test.js +2 -0
- package/dist/test.js.map +1 -0
- package/dist/theme/contract.d.ts +259 -0
- package/dist/theme/contract.js +131 -0
- package/dist/theme/contract.js.map +1 -0
- package/dist/theme/glyph-contract.d.ts +13 -0
- package/dist/theme/glyph-contract.js +34 -0
- package/dist/theme/glyph-contract.js.map +1 -0
- package/dist/theme/presets/rmx-01/glyphs.d.ts +2 -0
- package/dist/theme/presets/rmx-01/glyphs.js +252 -0
- package/dist/theme/presets/rmx-01/glyphs.js.map +1 -0
- package/dist/theme/presets/rmx-01/index.d.ts +2 -0
- package/dist/theme/presets/rmx-01/index.js +119 -0
- package/dist/theme/presets/rmx-01/index.js.map +1 -0
- package/dist/theme/runtime.d.ts +2 -0
- package/dist/theme/runtime.js +81 -0
- package/dist/theme/runtime.js.map +1 -0
- package/dist/theme/theme.d.ts +6 -0
- package/dist/theme/theme.js +5 -0
- package/dist/theme/theme.js.map +1 -0
- package/dist/utils/flash-attribute.d.ts +1 -0
- package/dist/utils/flash-attribute.js +11 -0
- package/dist/utils/flash-attribute.js.map +1 -0
- package/dist/utils/scroll-lock.d.ts +3 -0
- package/dist/utils/scroll-lock.js +69 -0
- package/dist/utils/scroll-lock.js.map +1 -0
- package/dist/utils/wait-for-css-transition.d.ts +1 -0
- package/dist/utils/wait-for-css-transition.js +58 -0
- package/dist/utils/wait-for-css-transition.js.map +1 -0
- package/dist/utils/wait.d.ts +1 -0
- package/dist/utils/wait.js +6 -0
- package/dist/utils/wait.js.map +1 -0
- package/package.json +124 -5
- package/src/animation/animate-layout-mixin.ts +266 -0
- package/src/animation/animate-mixins.ts +292 -0
- package/src/animation/index.ts +6 -0
- package/src/animation/spring.ts +299 -0
- package/src/animation/tween.ts +134 -0
- package/src/components/accordion/accordion.tsx +565 -0
- package/src/components/anchor/anchor.ts +904 -0
- package/src/components/breadcrumbs/breadcrumbs.tsx +124 -0
- package/src/components/button/README.md +44 -0
- package/src/components/button/button.tsx +188 -0
- package/src/components/combobox/README.md +145 -0
- package/src/components/combobox/combobox.tsx +1014 -0
- package/src/components/glyph/glyph.tsx +110 -0
- package/src/components/listbox/listbox.ts +447 -0
- package/src/components/menu/hover-aim.ts +428 -0
- package/src/components/menu/menu.tsx +1547 -0
- package/src/components/popover/README.md +122 -0
- package/src/components/popover/popover.ts +201 -0
- package/src/components/select/select.tsx +585 -0
- package/src/components/separator/separator.ts +19 -0
- package/src/components/tabs/README.md +105 -0
- package/src/components/tabs/tabs.tsx +499 -0
- package/src/index.ts +72 -0
- package/src/interactions/keydown/keydown.ts +14 -0
- package/src/interactions/outside-click/outside-click-mixin.ts +55 -0
- package/src/interactions/typeahead/typeahead-mixin.ts +89 -0
- package/src/jsx-runtime.ts +2 -0
- package/src/runtime/client-entries.ts +137 -0
- package/src/runtime/component.ts +402 -0
- package/src/runtime/create-element.ts +38 -0
- package/src/runtime/diff-dom.ts +404 -0
- package/src/runtime/diff-props.ts +209 -0
- package/src/runtime/document-state.ts +130 -0
- package/src/runtime/dom.ts +3971 -0
- package/src/runtime/error-event.ts +26 -0
- package/src/runtime/event-listeners.ts +171 -0
- package/src/runtime/frame.ts +1218 -0
- package/src/runtime/invariant.ts +31 -0
- package/src/runtime/jsx.ts +397 -0
- package/src/runtime/mixins/attrs-mixin.ts +35 -0
- package/src/runtime/mixins/link-mixin.ts +131 -0
- package/src/runtime/mixins/mixin.ts +908 -0
- package/src/runtime/mixins/on-mixin.ts +89 -0
- package/src/runtime/mixins/ref-mixin.ts +32 -0
- package/src/runtime/navigation.ts +148 -0
- package/src/runtime/reconcile.ts +1757 -0
- package/src/runtime/render.ts +101 -0
- package/src/runtime/run.ts +100 -0
- package/src/runtime/scheduler.ts +269 -0
- package/src/runtime/svg-attributes.ts +133 -0
- package/src/runtime/to-vnode.ts +51 -0
- package/src/runtime/typed-event-target.ts +67 -0
- package/src/runtime/vdom.ts +295 -0
- package/src/runtime/vnode.ts +137 -0
- package/src/server/stream.ts +1347 -0
- package/src/style/css-mixin.ts +82 -0
- package/src/style/index.ts +10 -0
- package/src/style/style.ts +312 -0
- package/src/style/stylesheet.ts +209 -0
- package/src/test/utils.ts +45 -0
- package/src/test.ts +2 -0
- package/src/theme/contract.ts +183 -0
- package/src/theme/glyph-contract.ts +57 -0
- package/src/theme/presets/rmx-01/glyphs.tsx +308 -0
- package/src/theme/presets/rmx-01/index.ts +121 -0
- package/src/theme/runtime.ts +116 -0
- package/src/theme/theme.ts +15 -0
- package/src/utils/flash-attribute.ts +11 -0
- package/src/utils/scroll-lock.ts +97 -0
- package/src/utils/wait-for-css-transition.ts +66 -0
- package/src/utils/wait.ts +5 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { createMixin, renderMixinElement } from '../runtime/mixins/mixin.ts'
|
|
2
|
+
import type { MixinDescriptor } from '../runtime/mixins/mixin.ts'
|
|
3
|
+
import type { ElementProps } from '../runtime/jsx.ts'
|
|
4
|
+
import { invariant } from '../runtime/invariant.ts'
|
|
5
|
+
import { processStyleClass } from '../style/index.ts'
|
|
6
|
+
import type { CSSProps } from '../style/style.ts'
|
|
7
|
+
|
|
8
|
+
type StyleEntry = { selector: string; css: string }
|
|
9
|
+
type StyleCache = Map<string, StyleEntry>
|
|
10
|
+
type StyleManagerLike = {
|
|
11
|
+
insert(className: string, rule: string): void
|
|
12
|
+
remove(className: string): void
|
|
13
|
+
getGeneration?(): number
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type CSSMixinDescriptor = MixinDescriptor<Element, [styles: CSSProps], ElementProps>
|
|
17
|
+
|
|
18
|
+
const clientStyleCache: StyleCache = new Map()
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Applies generated class names for CSS object styles.
|
|
22
|
+
*/
|
|
23
|
+
export const css = createMixin<Element, [styles: CSSProps], ElementProps>((handle) => {
|
|
24
|
+
let activeSelector = ''
|
|
25
|
+
let activeGeneration = -1
|
|
26
|
+
let currentStyles: CSSProps = {}
|
|
27
|
+
|
|
28
|
+
handle.addEventListener('remove', () => {
|
|
29
|
+
if (!activeSelector) return
|
|
30
|
+
let runtime = handle.frame.$runtime as {
|
|
31
|
+
styleCache?: StyleCache
|
|
32
|
+
styleManager?: StyleManagerLike
|
|
33
|
+
}
|
|
34
|
+
invariant(runtime, 'css mixin requires frame runtime')
|
|
35
|
+
let styleTarget = resolveStyleTarget(runtime)
|
|
36
|
+
styleTarget.styleManager?.remove(activeSelector)
|
|
37
|
+
activeSelector = ''
|
|
38
|
+
activeGeneration = -1
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
return (styles, props) => {
|
|
42
|
+
currentStyles = styles
|
|
43
|
+
let runtime = handle.frame.$runtime as {
|
|
44
|
+
styleCache?: StyleCache
|
|
45
|
+
styleManager?: StyleManagerLike
|
|
46
|
+
}
|
|
47
|
+
invariant(runtime, 'css mixin requires frame runtime')
|
|
48
|
+
let styleTarget = resolveStyleTarget(runtime)
|
|
49
|
+
let { selector, css: cssText } = processStyleClass(currentStyles, styleTarget.styleCache)
|
|
50
|
+
let styleGeneration = styleTarget.styleManager?.getGeneration?.() ?? 0
|
|
51
|
+
|
|
52
|
+
if (styleTarget.styleManager) {
|
|
53
|
+
if (activeSelector && activeSelector !== selector) {
|
|
54
|
+
styleTarget.styleManager.remove(activeSelector)
|
|
55
|
+
}
|
|
56
|
+
if (selector && (activeSelector !== selector || activeGeneration !== styleGeneration)) {
|
|
57
|
+
styleTarget.styleManager.insert(selector, cssText)
|
|
58
|
+
}
|
|
59
|
+
activeSelector = selector
|
|
60
|
+
activeGeneration = selector ? styleGeneration : -1
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (!selector) {
|
|
64
|
+
return handle.element
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return renderMixinElement(handle.element, {
|
|
68
|
+
...(props ?? {}),
|
|
69
|
+
className: props?.className ? `${props.className} ${selector}` : selector,
|
|
70
|
+
})
|
|
71
|
+
}
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
function resolveStyleTarget(runtime: {
|
|
75
|
+
styleCache?: StyleCache
|
|
76
|
+
styleManager?: StyleManagerLike
|
|
77
|
+
}): { styleCache: StyleCache; styleManager?: StyleManagerLike } {
|
|
78
|
+
return {
|
|
79
|
+
styleCache: runtime.styleCache ?? clientStyleCache,
|
|
80
|
+
styleManager: runtime.styleManager,
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { createStyleManager } from './stylesheet.ts'
|
|
2
|
+
|
|
3
|
+
export type {
|
|
4
|
+
CSSProps as EnhancedStyleProperties,
|
|
5
|
+
DOMStyleProperties as StyleProperties,
|
|
6
|
+
} from './style.ts'
|
|
7
|
+
export { processStyleClass, normalizeCssValue } from './style.ts'
|
|
8
|
+
export { createStyleManager }
|
|
9
|
+
|
|
10
|
+
export type StyleManager = ReturnType<typeof createStyleManager>
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
export type DOMStyleProperties = {
|
|
2
|
+
[key in keyof Omit<
|
|
3
|
+
CSSStyleDeclaration,
|
|
4
|
+
'item' | 'setProperty' | 'removeProperty' | 'getPropertyValue' | 'getPropertyPriority'
|
|
5
|
+
>]?: string | number | null | undefined
|
|
6
|
+
}
|
|
7
|
+
export type AllStyleProperties = {
|
|
8
|
+
[key: string]: string | number | null | undefined
|
|
9
|
+
}
|
|
10
|
+
export interface StyleProps extends AllStyleProperties, DOMStyleProperties {
|
|
11
|
+
cssText?: string | null
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// allow nesting
|
|
15
|
+
export interface CSSProps extends DOMStyleProperties {
|
|
16
|
+
[key: string]: CSSProps | string | number | null | undefined
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
type StyleObject = Record<string, unknown>
|
|
20
|
+
|
|
21
|
+
// Convert camelCase CSS properties to kebab-case
|
|
22
|
+
function camelToKebab(str: string): string {
|
|
23
|
+
return str.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Properties that should remain unitless (numeric values without px)
|
|
27
|
+
const NUMERIC_CSS_PROPS = new Set([
|
|
28
|
+
'aspect-ratio',
|
|
29
|
+
'z-index',
|
|
30
|
+
'opacity',
|
|
31
|
+
'flex-grow',
|
|
32
|
+
'flex-shrink',
|
|
33
|
+
'flex-order',
|
|
34
|
+
'grid-area',
|
|
35
|
+
'grid-row',
|
|
36
|
+
'grid-column',
|
|
37
|
+
'font-weight',
|
|
38
|
+
'line-height',
|
|
39
|
+
'order',
|
|
40
|
+
'orphans',
|
|
41
|
+
'widows',
|
|
42
|
+
'zoom',
|
|
43
|
+
'columns',
|
|
44
|
+
'column-count',
|
|
45
|
+
])
|
|
46
|
+
|
|
47
|
+
// Normalize numeric CSS values: append 'px' for properties that need units
|
|
48
|
+
// In Standards Mode, browsers drop numeric values without units when multiple properties
|
|
49
|
+
// are present in insertRule(). We must normalize them ourselves.
|
|
50
|
+
export function normalizeCssValue(key: string, value: unknown): string {
|
|
51
|
+
if (value == null) return String(value)
|
|
52
|
+
if (typeof value === 'number' && value !== 0) {
|
|
53
|
+
let cssKey = camelToKebab(key)
|
|
54
|
+
if (!NUMERIC_CSS_PROPS.has(cssKey) && !cssKey.startsWith('--')) {
|
|
55
|
+
return `${value}px`
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return String(value)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Check if a style property is a nested selector or media query
|
|
62
|
+
function isComplexSelector(key: string): boolean {
|
|
63
|
+
return (
|
|
64
|
+
key.startsWith('&') ||
|
|
65
|
+
key.startsWith('@') ||
|
|
66
|
+
key.startsWith(':') ||
|
|
67
|
+
key.startsWith('[') ||
|
|
68
|
+
key.startsWith('.')
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Detect @keyframes (including vendor-prefixed variants)
|
|
73
|
+
function isKeyframesAtRule(key: string): boolean {
|
|
74
|
+
if (!key.startsWith('@')) return false
|
|
75
|
+
let lower = key.toLowerCase()
|
|
76
|
+
return (
|
|
77
|
+
lower.startsWith('@keyframes') ||
|
|
78
|
+
lower.startsWith('@-webkit-keyframes') ||
|
|
79
|
+
lower.startsWith('@-moz-keyframes') ||
|
|
80
|
+
lower.startsWith('@-o-keyframes')
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Generate a hash for style objects to create unique class names
|
|
85
|
+
function hashStyle(obj: any): string {
|
|
86
|
+
// Sort keys to ensure consistent hashing, but include values in the string
|
|
87
|
+
let sortedEntries = Object.entries(obj).sort(([a], [b]) => a.localeCompare(b))
|
|
88
|
+
let str = JSON.stringify(sortedEntries)
|
|
89
|
+
let hash = 0
|
|
90
|
+
for (let i = 0; i < str.length; i++) {
|
|
91
|
+
let char = str.charCodeAt(i)
|
|
92
|
+
hash = (hash << 5) - hash + char
|
|
93
|
+
hash = hash & hash // Convert to 32-bit integer
|
|
94
|
+
}
|
|
95
|
+
return Math.abs(hash).toString(36)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Convert style object to CSS text
|
|
99
|
+
function styleToCss(styles: StyleObject, selector: string = ''): string {
|
|
100
|
+
let baseDeclarations: string[] = []
|
|
101
|
+
let nestedBlocks: string[] = []
|
|
102
|
+
let atRules: string[] = []
|
|
103
|
+
let preludeAtRules: string[] = []
|
|
104
|
+
|
|
105
|
+
for (let [key, value] of Object.entries(styles)) {
|
|
106
|
+
if (isComplexSelector(key)) {
|
|
107
|
+
if (key.startsWith('@')) {
|
|
108
|
+
// Allow at-rules to be conditionally disabled.
|
|
109
|
+
// e.g. { '@media (min-width: 600px)': condition ? undefined : { ... } }
|
|
110
|
+
let record = toRecord(value)
|
|
111
|
+
if (!record) continue
|
|
112
|
+
|
|
113
|
+
// Some at-rules (e.g., @media) scope declarations to the selector.
|
|
114
|
+
// Others (e.g., @function) must NOT include the selector in their body.
|
|
115
|
+
if (key.startsWith('@function')) {
|
|
116
|
+
let body = atRuleBodyToCss(record)
|
|
117
|
+
if (body.trim().length > 0) {
|
|
118
|
+
preludeAtRules.push(`${key} {\n${indent(body, 2)}\n}`)
|
|
119
|
+
} else {
|
|
120
|
+
preludeAtRules.push(`${key} {\n}`)
|
|
121
|
+
}
|
|
122
|
+
} else if (isKeyframesAtRule(key)) {
|
|
123
|
+
// Keyframes definitions must not be wrapped with the element selector.
|
|
124
|
+
// Emit them before the class rule so animations can be referenced.
|
|
125
|
+
let body = keyframesBodyToCss(record)
|
|
126
|
+
if (body.trim().length > 0) {
|
|
127
|
+
preludeAtRules.push(`${key} {\n${indent(body, 2)}\n}`)
|
|
128
|
+
} else {
|
|
129
|
+
preludeAtRules.push(`${key} {\n}`)
|
|
130
|
+
}
|
|
131
|
+
} else {
|
|
132
|
+
// Default: keep at-rules nested with the element selector
|
|
133
|
+
let inner = styleToCss(record, selector)
|
|
134
|
+
if (inner.trim().length > 0) {
|
|
135
|
+
atRules.push(`${key} {\n${indent(inner, 2)}\n}`)
|
|
136
|
+
} else {
|
|
137
|
+
// Empty at-rule body with selector block
|
|
138
|
+
atRules.push(`${key} {\n ${selector} {\n }\n}`)
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
continue
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// For nested selectors, keep them wholesale inside the base block
|
|
145
|
+
// Allow nested selectors to be conditionally disabled.
|
|
146
|
+
// e.g. { '&:hover': condition ? undefined : { ... } }
|
|
147
|
+
let record = toRecord(value)
|
|
148
|
+
if (!record) continue
|
|
149
|
+
|
|
150
|
+
let nestedContent = ''
|
|
151
|
+
for (let [prop, propValue] of Object.entries(record)) {
|
|
152
|
+
if (propValue != null) {
|
|
153
|
+
let normalizedValue = normalizeCssValue(prop, propValue)
|
|
154
|
+
nestedContent += ` ${camelToKebab(prop)}: ${normalizedValue};\n`
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
if (nestedContent) {
|
|
158
|
+
// Preserve key verbatim (e.g., '&[aria-selected], &[rmx-focus]')
|
|
159
|
+
nestedBlocks.push(` ${key} {\n${nestedContent} }`)
|
|
160
|
+
}
|
|
161
|
+
} else {
|
|
162
|
+
// Base declaration
|
|
163
|
+
if (value != null) {
|
|
164
|
+
let normalizedValue = normalizeCssValue(key, value)
|
|
165
|
+
baseDeclarations.push(` ${camelToKebab(key)}: ${normalizedValue};`)
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
let css = ''
|
|
171
|
+
if (preludeAtRules.length > 0) {
|
|
172
|
+
css += preludeAtRules.join('\n')
|
|
173
|
+
}
|
|
174
|
+
if (selector && (baseDeclarations.length > 0 || nestedBlocks.length > 0)) {
|
|
175
|
+
css += (css ? '\n' : '') + `${selector} {\n`
|
|
176
|
+
if (baseDeclarations.length > 0) {
|
|
177
|
+
css += baseDeclarations.join('\n') + '\n'
|
|
178
|
+
}
|
|
179
|
+
if (nestedBlocks.length > 0) {
|
|
180
|
+
css += nestedBlocks.join('\n') + '\n'
|
|
181
|
+
}
|
|
182
|
+
css += '}'
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (atRules.length > 0) {
|
|
186
|
+
css += (css ? '\n' : '') + atRules.join('\n')
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return css
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function indent(text: string, spaces: number): string {
|
|
193
|
+
let pad = ' '.repeat(spaces)
|
|
194
|
+
return text
|
|
195
|
+
.split('\n')
|
|
196
|
+
.map((line) => (line.length ? pad + line : line))
|
|
197
|
+
.join('\n')
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Narrow unknown values to plain record objects
|
|
201
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
202
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function toRecord(value: unknown): Record<string, unknown> | null {
|
|
206
|
+
return isRecord(value) ? value : null
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Build the body of a @keyframes rule (without wrapping selector)
|
|
210
|
+
function keyframesBodyToCss(frames: StyleObject): string {
|
|
211
|
+
let blocks: string[] = []
|
|
212
|
+
|
|
213
|
+
for (let [frameSelector, frameValue] of Object.entries(frames)) {
|
|
214
|
+
if (!isRecord(frameValue)) {
|
|
215
|
+
// Skip non-object frame definitions
|
|
216
|
+
continue
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
let declarations: string[] = []
|
|
220
|
+
for (let [prop, propValue] of Object.entries(frameValue)) {
|
|
221
|
+
if (propValue == null) continue
|
|
222
|
+
// Ignore nested selectors/at-rules inside keyframe steps
|
|
223
|
+
if (isComplexSelector(prop)) continue
|
|
224
|
+
let normalizedValue = normalizeCssValue(prop, propValue)
|
|
225
|
+
declarations.push(` ${camelToKebab(prop)}: ${normalizedValue};`)
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (declarations.length > 0) {
|
|
229
|
+
blocks.push(`${frameSelector} {\n${declarations.join('\n')}\n}`)
|
|
230
|
+
} else {
|
|
231
|
+
blocks.push(`${frameSelector} {\n}`)
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return blocks.join('\n')
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Build the body for at-rules that should not include a selector wrapper (e.g., @function)
|
|
239
|
+
function atRuleBodyToCss(styles: StyleObject): string {
|
|
240
|
+
let declarations: string[] = []
|
|
241
|
+
let nested: string[] = []
|
|
242
|
+
|
|
243
|
+
for (let [key, value] of Object.entries(styles)) {
|
|
244
|
+
if (isComplexSelector(key)) {
|
|
245
|
+
if (key.startsWith('@')) {
|
|
246
|
+
// Nested at-rules inside definition blocks; render their bodies recursively without selectors
|
|
247
|
+
let record = toRecord(value)
|
|
248
|
+
if (!record) continue
|
|
249
|
+
let inner = atRuleBodyToCss(record)
|
|
250
|
+
if (inner.trim().length > 0) {
|
|
251
|
+
nested.push(`${key} {\n${indent(inner, 2)}\n}`)
|
|
252
|
+
} else {
|
|
253
|
+
nested.push(`${key} {\n}`)
|
|
254
|
+
}
|
|
255
|
+
} else {
|
|
256
|
+
// Ignore nested selectors (&, :, ., [) inside definition-style at-rules
|
|
257
|
+
// They are not meaningful within e.g. @function bodies
|
|
258
|
+
continue
|
|
259
|
+
}
|
|
260
|
+
} else {
|
|
261
|
+
if (value != null) {
|
|
262
|
+
let normalizedValue = normalizeCssValue(key, value)
|
|
263
|
+
declarations.push(` ${camelToKebab(key)}: ${normalizedValue};`)
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
let body = ''
|
|
269
|
+
if (declarations.length > 0) {
|
|
270
|
+
body += declarations.join('\n')
|
|
271
|
+
}
|
|
272
|
+
if (nested.length > 0) {
|
|
273
|
+
body += (body ? '\n' : '') + nested.join('\n')
|
|
274
|
+
}
|
|
275
|
+
return body
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export function processStyleClass(
|
|
279
|
+
styleObj: CSSProps,
|
|
280
|
+
styleCache: Map<string, { selector: string; css: string }>,
|
|
281
|
+
): {
|
|
282
|
+
selector: string
|
|
283
|
+
css: string
|
|
284
|
+
} {
|
|
285
|
+
// Check if the object is empty
|
|
286
|
+
if (Object.keys(styleObj).length === 0) {
|
|
287
|
+
return { selector: '', css: '' }
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Generate a hash for the style object
|
|
291
|
+
let hash = hashStyle(styleObj)
|
|
292
|
+
let selector = `rmxc-${hash}`
|
|
293
|
+
|
|
294
|
+
// Check cache first
|
|
295
|
+
let cached = styleCache.get(hash)
|
|
296
|
+
if (cached) {
|
|
297
|
+
return cached
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
let css = styleToCss(styleObj, `.${selector}`)
|
|
301
|
+
let result = { selector, css }
|
|
302
|
+
|
|
303
|
+
// Store in cache
|
|
304
|
+
styleCache.set(hash, result)
|
|
305
|
+
|
|
306
|
+
return result
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Clear style cache (useful for testing)
|
|
310
|
+
export function clearStyleCache(styleCache: Map<string, { selector: string; css: string }>): void {
|
|
311
|
+
styleCache.clear()
|
|
312
|
+
}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
type RuleEntry = { count: number; index: number }
|
|
2
|
+
type ServerStyleSource = ParentNode | Iterable<Node>
|
|
3
|
+
|
|
4
|
+
const SERVER_STYLE_SELECTOR = 'style[data-rmx]'
|
|
5
|
+
const DEFAULT_STYLE_LAYER = 'rmx'
|
|
6
|
+
|
|
7
|
+
function getStyleLayerName(className: string, layer: string = DEFAULT_STYLE_LAYER): string {
|
|
8
|
+
return `${layer}.${className}`
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function compareNodesInDocumentOrder(a: Node, b: Node): number {
|
|
12
|
+
if (a === b) return 0
|
|
13
|
+
|
|
14
|
+
let position = a.compareDocumentPosition(b)
|
|
15
|
+
if (position & Node.DOCUMENT_POSITION_FOLLOWING) return -1
|
|
16
|
+
if (position & Node.DOCUMENT_POSITION_PRECEDING) return 1
|
|
17
|
+
return 0
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function isParentNode(value: ServerStyleSource): value is ParentNode {
|
|
21
|
+
return 'querySelectorAll' in value
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function collectServerStyleTagsFromNode(node: Node, into: Set<HTMLStyleElement>): void {
|
|
25
|
+
if (isHtmlStyleElement(node) && node.matches(SERVER_STYLE_SELECTOR)) {
|
|
26
|
+
into.add(node)
|
|
27
|
+
return
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (
|
|
31
|
+
!(node instanceof Element) &&
|
|
32
|
+
!(node instanceof Document) &&
|
|
33
|
+
!(node instanceof DocumentFragment)
|
|
34
|
+
) {
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
let nested = node.querySelectorAll?.(SERVER_STYLE_SELECTOR) ?? []
|
|
39
|
+
for (let i = 0; i < nested.length; i++) {
|
|
40
|
+
let el = nested[i]
|
|
41
|
+
if (isHtmlStyleElement(el)) {
|
|
42
|
+
into.add(el)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function collectServerStyleTags(source: ServerStyleSource): HTMLStyleElement[] {
|
|
48
|
+
let styles = new Set<HTMLStyleElement>()
|
|
49
|
+
|
|
50
|
+
if (isParentNode(source)) {
|
|
51
|
+
collectServerStyleTagsFromNode(source as unknown as Node, styles)
|
|
52
|
+
} else {
|
|
53
|
+
for (let node of source) {
|
|
54
|
+
collectServerStyleTagsFromNode(node, styles)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return Array.from(styles).sort(compareNodesInDocumentOrder)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function isHtmlStyleElement(node: unknown): node is HTMLStyleElement {
|
|
62
|
+
return typeof node === 'object' && node !== null && node instanceof HTMLStyleElement
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function getStyleSelector(styleEl: HTMLStyleElement): string | null {
|
|
66
|
+
let selector = styleEl.getAttribute('data-rmx')?.trim()
|
|
67
|
+
return selector ? selector : null
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function createStyleManager(layer: string = 'rmx') {
|
|
71
|
+
let stylesheet: CSSStyleSheet | null = null
|
|
72
|
+
let generation = 0
|
|
73
|
+
|
|
74
|
+
// Track usage count and rule index per className
|
|
75
|
+
// Using an object to track both count and index together
|
|
76
|
+
let ruleMap = new Map<string, RuleEntry>()
|
|
77
|
+
|
|
78
|
+
function getStylesheet(): CSSStyleSheet {
|
|
79
|
+
if (!stylesheet) {
|
|
80
|
+
stylesheet = new CSSStyleSheet()
|
|
81
|
+
document.adoptedStyleSheets.push(stylesheet)
|
|
82
|
+
}
|
|
83
|
+
return stylesheet
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function removeStylesheet() {
|
|
87
|
+
if (!stylesheet) return
|
|
88
|
+
document.adoptedStyleSheets = Array.from(document.adoptedStyleSheets).filter(
|
|
89
|
+
(s) => s !== stylesheet,
|
|
90
|
+
)
|
|
91
|
+
stylesheet = null
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function clearStylesheet() {
|
|
95
|
+
if (!stylesheet) return
|
|
96
|
+
for (let i = stylesheet.cssRules.length - 1; i >= 0; i--) {
|
|
97
|
+
stylesheet.deleteRule(i)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function adoptServerStyleTag(styleEl: HTMLStyleElement) {
|
|
102
|
+
let selector = getStyleSelector(styleEl)
|
|
103
|
+
if (!selector) return
|
|
104
|
+
|
|
105
|
+
if (ruleMap.has(selector)) {
|
|
106
|
+
styleEl.remove()
|
|
107
|
+
return
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
let cssText = styleEl.textContent?.trim() ?? ''
|
|
111
|
+
if (cssText.length === 0) {
|
|
112
|
+
styleEl.remove()
|
|
113
|
+
return
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
let sheet = getStylesheet()
|
|
118
|
+
let index = sheet.cssRules.length
|
|
119
|
+
sheet.insertRule(cssText, index)
|
|
120
|
+
ruleMap.set(selector, { count: 1, index })
|
|
121
|
+
styleEl.remove()
|
|
122
|
+
} catch {
|
|
123
|
+
// If adoption fails, keep the <style> tag in the DOM so styles still apply.
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function has(className: string) {
|
|
128
|
+
let entry = ruleMap.get(className)
|
|
129
|
+
return entry !== undefined && entry.count > 0
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function getGeneration() {
|
|
133
|
+
return generation
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function insert(className: string, rule: string) {
|
|
137
|
+
let entry = ruleMap.get(className)
|
|
138
|
+
|
|
139
|
+
if (entry) {
|
|
140
|
+
// Already exists, just increment count
|
|
141
|
+
entry.count++
|
|
142
|
+
return
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// New rule - insert and track
|
|
146
|
+
let sheet = getStylesheet()
|
|
147
|
+
let index = sheet.cssRules.length
|
|
148
|
+
// This may throw for invalid CSS. If it does, we intentionally let it
|
|
149
|
+
// bubble so the rule is not tracked unless insertion actually succeeds.
|
|
150
|
+
sheet.insertRule(`@layer ${getStyleLayerName(className, layer)} { ${rule} }`, index)
|
|
151
|
+
ruleMap.set(className, { count: 1, index })
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function remove(className: string) {
|
|
155
|
+
let entry = ruleMap.get(className)
|
|
156
|
+
|
|
157
|
+
if (!entry) return
|
|
158
|
+
|
|
159
|
+
// Decrement count
|
|
160
|
+
entry.count--
|
|
161
|
+
|
|
162
|
+
if (entry.count > 0) {
|
|
163
|
+
// Still in use, keep the rule
|
|
164
|
+
return
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Count reached zero, remove the rule
|
|
168
|
+
let indexToDelete = entry.index
|
|
169
|
+
|
|
170
|
+
// Remove from tracking
|
|
171
|
+
ruleMap.delete(className)
|
|
172
|
+
|
|
173
|
+
// TODO: just search and remove, stop re-indexing
|
|
174
|
+
if (!stylesheet) return
|
|
175
|
+
stylesheet.deleteRule(indexToDelete)
|
|
176
|
+
|
|
177
|
+
// Update indices for all rules that came after the deleted one
|
|
178
|
+
// They all shift down by 1
|
|
179
|
+
for (let [, data] of ruleMap.entries()) {
|
|
180
|
+
if (data.index > indexToDelete) {
|
|
181
|
+
data.index--
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function reset() {
|
|
187
|
+
clearStylesheet()
|
|
188
|
+
ruleMap.clear()
|
|
189
|
+
removeStylesheet()
|
|
190
|
+
generation++
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function adoptServerStyles(source: ServerStyleSource) {
|
|
194
|
+
let styles = collectServerStyleTags(source)
|
|
195
|
+
|
|
196
|
+
for (let styleEl of styles) {
|
|
197
|
+
adoptServerStyleTag(styleEl)
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function dispose() {
|
|
202
|
+
removeStylesheet()
|
|
203
|
+
// Clear internal state
|
|
204
|
+
ruleMap.clear()
|
|
205
|
+
generation++
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return { insert, remove, has, getGeneration, reset, adoptServerStyles, dispose }
|
|
209
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export type Assert<T extends true> = T
|
|
2
|
+
|
|
3
|
+
export type Equal<X, Y> =
|
|
4
|
+
(<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y ? 1 : 2 ? true : false
|
|
5
|
+
|
|
6
|
+
export async function drain(stream: ReadableStream<Uint8Array>): Promise<string> {
|
|
7
|
+
let reader = stream.getReader()
|
|
8
|
+
let decoder = new TextDecoder()
|
|
9
|
+
let html = ''
|
|
10
|
+
|
|
11
|
+
while (true) {
|
|
12
|
+
let { done, value } = await reader.read()
|
|
13
|
+
if (done) break
|
|
14
|
+
html += decoder.decode(value)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return html
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function readChunks(stream: ReadableStream<Uint8Array>): AsyncGenerator<string, void, void> {
|
|
21
|
+
let reader = stream.getReader()
|
|
22
|
+
let decoder = new TextDecoder()
|
|
23
|
+
|
|
24
|
+
return (async function* () {
|
|
25
|
+
while (true) {
|
|
26
|
+
let { done, value } = await reader.read()
|
|
27
|
+
if (done) break
|
|
28
|
+
yield decoder.decode(value)
|
|
29
|
+
}
|
|
30
|
+
})()
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function withResolvers<T = unknown>(): [
|
|
34
|
+
Promise<T>,
|
|
35
|
+
(value: T) => void,
|
|
36
|
+
(error: unknown) => void,
|
|
37
|
+
] {
|
|
38
|
+
let resolve!: (value: T) => void
|
|
39
|
+
let reject!: (error: unknown) => void
|
|
40
|
+
let promise = new Promise<T>((res, rej) => {
|
|
41
|
+
resolve = res
|
|
42
|
+
reject = rej
|
|
43
|
+
})
|
|
44
|
+
return [promise, resolve, reject]
|
|
45
|
+
}
|
package/src/test.ts
ADDED