@pyreon/ui-core 0.0.2

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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025-present Vit Bokisch
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,145 @@
1
+ # @pyreon/ui-core
2
+
3
+ Shared foundation for the Pyreon UI System ecosystem.
4
+
5
+ Provides utility functions, a styling engine bridge, theme context, and HTML tag definitions used across all `@pyreon` packages. No external utility dependencies — all implementations are built-in with prototype pollution protection where applicable.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ bun add @pyreon/ui-core
11
+ ```
12
+
13
+ ## API
14
+
15
+ ### Provider & Context
16
+
17
+ ```ts
18
+ import { Provider, context, config, init } from '@pyreon/ui-core'
19
+ ```
20
+
21
+ **Provider** wraps your app with a theme context. It bridges `@pyreon/styler`'s theming with the internal context system.
22
+
23
+ ```ts
24
+ import { Provider } from '@pyreon/ui-core'
25
+
26
+ Provider({
27
+ theme: { rootSize: 16, breakpoints: { xs: 0, md: 768 } },
28
+ children: [/* your app */],
29
+ })
30
+ ```
31
+
32
+ **config** — the styling engine singleton. Pyreon uses `@pyreon/styler` directly — no connector abstraction needed.
33
+
34
+ ```ts
35
+ import { config } from '@pyreon/ui-core'
36
+
37
+ // Access the engine from anywhere
38
+ const { styled, css, keyframes } = config
39
+ ```
40
+
41
+ ### Utilities
42
+
43
+ #### compose
44
+
45
+ Right-to-left function composition.
46
+
47
+ ```ts
48
+ import { compose } from '@pyreon/ui-core'
49
+
50
+ const transform = compose(toUpperCase, trim, normalize)
51
+ transform(' hello ') // => 'HELLO'
52
+ ```
53
+
54
+ #### render
55
+
56
+ Flexible element renderer. Handles components, elements, primitives, and arrays.
57
+
58
+ ```ts
59
+ import { render } from '@pyreon/ui-core'
60
+
61
+ render('hello') // => 'hello'
62
+ render(MyComponent) // => MyComponent({})
63
+ render(null) // => null
64
+ ```
65
+
66
+ #### isEmpty
67
+
68
+ Type-safe emptiness check. Returns `true` for `null`, `undefined`, `{}`, `[]`, and non-object primitives.
69
+
70
+ ```ts
71
+ import { isEmpty } from '@pyreon/ui-core'
72
+
73
+ isEmpty({}) // => true
74
+ isEmpty([]) // => true
75
+ isEmpty(null) // => true
76
+ isEmpty({ a: 1 }) // => false
77
+ ```
78
+
79
+ #### omit / pick
80
+
81
+ Create objects without or with only specified keys. Accept nullable inputs.
82
+
83
+ ```ts
84
+ import { omit, pick } from '@pyreon/ui-core'
85
+
86
+ omit({ a: 1, b: 2, c: 3 }, ['b']) // => { a: 1, c: 3 }
87
+ pick({ a: 1, b: 2, c: 3 }, ['a', 'b']) // => { a: 1, b: 2 }
88
+ ```
89
+
90
+ #### set / get
91
+
92
+ Nested property access and mutation by dot/bracket path. `set` has built-in prototype pollution protection — keys like `__proto__`, `constructor`, and `prototype` are blocked.
93
+
94
+ ```ts
95
+ import { set, get } from '@pyreon/ui-core'
96
+
97
+ const obj = {}
98
+ set(obj, 'a.b.c', 42) // => { a: { b: { c: 42 } } }
99
+ get(obj, 'a.b.c') // => 42
100
+ get(obj, 'a.x', 'default') // => 'default'
101
+ ```
102
+
103
+ #### merge
104
+
105
+ Deep merge objects left-to-right. Only plain objects are recursed into; arrays are replaced wholesale. Prototype pollution keys are blocked.
106
+
107
+ ```ts
108
+ import { merge } from '@pyreon/ui-core'
109
+
110
+ merge({ a: { x: 1 } }, { a: { y: 2 } }) // => { a: { x: 1, y: 2 } }
111
+ ```
112
+
113
+ #### throttle
114
+
115
+ Limits function execution to at most once per wait period. Returns a throttled function with a `.cancel()` method.
116
+
117
+ ```ts
118
+ import { throttle } from '@pyreon/ui-core'
119
+
120
+ const throttled = throttle(handleResize, 200)
121
+ window.addEventListener('resize', throttled)
122
+ // cleanup: throttled.cancel()
123
+ ```
124
+
125
+ ### HTML Constants
126
+
127
+ ```ts
128
+ import { HTML_TAGS, HTML_TEXT_TAGS } from '@pyreon/ui-core'
129
+ ```
130
+
131
+ - **HTML_TAGS** — array of 100+ valid HTML tag names
132
+ - **HTML_TEXT_TAGS** — array of text-content tags (h1–h6, p, span, strong, em, etc.)
133
+
134
+ Both have corresponding TypeScript union types: `HTMLTags` and `HTMLTextTags`.
135
+
136
+ ## Peer Dependencies
137
+
138
+ | Package | Version |
139
+ | ------- | ------- |
140
+ | @pyreon/core | >= 0.0.1 |
141
+ | @pyreon/styler | >= 0.0.1 |
142
+
143
+ ## License
144
+
145
+ MIT
package/lib/index.d.ts ADDED
@@ -0,0 +1,257 @@
1
+ import * as _pyreon_styler0 from "@pyreon/styler";
2
+ import { StyledFunction, css, keyframes, styled } from "@pyreon/styler";
3
+ import * as _pyreon_core0 from "@pyreon/core";
4
+ import { ComponentFn, VNodeChild } from "@pyreon/core";
5
+
6
+ //#region src/compose.d.ts
7
+ type ArityOneFn = (arg: any) => any;
8
+ type PickLastInTuple<T extends any[]> = T extends [...rest: infer _U, argn: infer L] ? L : any;
9
+ type FirstFnParameterType<T extends any[]> = Parameters<PickLastInTuple<T>>[any];
10
+ type LastFnReturnType<T extends any[]> = ReturnType<T[0]>;
11
+ declare const compose: <T extends ArityOneFn[]>(...fns: T) => (p: FirstFnParameterType<T>) => LastFnReturnType<T>;
12
+ //#endregion
13
+ //#region src/html/htmlElementAttrs.d.ts
14
+ type Base = HTMLElement;
15
+ interface HTMLElementAttrs {
16
+ a: HTMLAnchorElement;
17
+ abbr: Base;
18
+ address: Base;
19
+ area: HTMLAreaElement;
20
+ article: Base;
21
+ aside: Base;
22
+ audio: HTMLAudioElement;
23
+ b: Base;
24
+ bdi: Base;
25
+ bdo: Base;
26
+ big: Base;
27
+ blockquote: HTMLQuoteElement;
28
+ body: HTMLBodyElement;
29
+ br: HTMLBRElement;
30
+ button: HTMLButtonElement;
31
+ canvas: HTMLCanvasElement;
32
+ caption: Base;
33
+ cite: HTMLQuoteElement;
34
+ code: Base;
35
+ col: HTMLTableColElement;
36
+ colgroup: HTMLTableColElement;
37
+ data: HTMLDataElement;
38
+ datalist: HTMLDataListElement;
39
+ dd: Base;
40
+ del: HTMLModElement;
41
+ details: HTMLDetailsElement;
42
+ dfn: Base;
43
+ dialog: HTMLDialogElement;
44
+ div: HTMLDivElement;
45
+ dl: HTMLDListElement;
46
+ dt: Base;
47
+ em: Base;
48
+ embed: HTMLEmbedElement;
49
+ fieldset: HTMLFieldSetElement;
50
+ figcaption: Base;
51
+ figure: Base;
52
+ footer: Base;
53
+ form: HTMLFormElement;
54
+ h1: HTMLHeadingElement;
55
+ h2: HTMLHeadingElement;
56
+ h3: HTMLHeadingElement;
57
+ h4: HTMLHeadingElement;
58
+ h5: HTMLHeadingElement;
59
+ h6: HTMLHeadingElement;
60
+ header: Base;
61
+ hr: HTMLHRElement;
62
+ html: HTMLHtmlElement;
63
+ i: Base;
64
+ iframe: HTMLIFrameElement;
65
+ img: HTMLImageElement;
66
+ input: HTMLInputElement;
67
+ ins: HTMLModElement;
68
+ kbd: Base;
69
+ label: HTMLLabelElement;
70
+ legend: HTMLLegendElement;
71
+ li: HTMLLIElement;
72
+ main: Base;
73
+ map: HTMLMapElement;
74
+ mark: Base;
75
+ meter: HTMLMeterElement;
76
+ nav: Base;
77
+ object: HTMLObjectElement;
78
+ ol: HTMLOListElement;
79
+ optgroup: HTMLOptGroupElement;
80
+ option: HTMLOptionElement;
81
+ output: HTMLOutputElement;
82
+ p: HTMLParagraphElement;
83
+ picture: Base;
84
+ pre: HTMLPreElement;
85
+ progress: HTMLProgressElement;
86
+ q: HTMLQuoteElement;
87
+ rp: Base;
88
+ rt: Base;
89
+ ruby: Base;
90
+ s: Base;
91
+ samp: Base;
92
+ section: Base;
93
+ select: HTMLSelectElement;
94
+ small: Base;
95
+ source: HTMLSourceElement;
96
+ span: HTMLSpanElement;
97
+ strong: Base;
98
+ sub: Base;
99
+ summary: Base;
100
+ sup: Base;
101
+ svg: SVGSVGElement;
102
+ table: HTMLTableElement;
103
+ tbody: HTMLTableSectionElement;
104
+ td: HTMLTableCellElement;
105
+ template: HTMLTemplateElement;
106
+ textarea: HTMLTextAreaElement;
107
+ tfoot: HTMLTableSectionElement;
108
+ th: HTMLTableCellElement;
109
+ thead: HTMLTableSectionElement;
110
+ time: HTMLTimeElement;
111
+ tr: HTMLTableRowElement;
112
+ track: HTMLTrackElement;
113
+ u: Base;
114
+ ul: HTMLUListElement;
115
+ var: Base;
116
+ video: HTMLVideoElement;
117
+ wbr: Base;
118
+ }
119
+ //#endregion
120
+ //#region src/html/htmlTags.d.ts
121
+ declare const HTML_TAGS: readonly ["a", "abbr", "address", "area", "article", "aside", "audio", "b", "bdi", "bdo", "big", "blockquote", "body", "br", "button", "canvas", "caption", "cite", "code", "col", "colgroup", "data", "datalist", "dd", "del", "details", "dfn", "dialog", "div", "dl", "dt", "em", "embed", "fieldset", "figcaption", "figure", "footer", "form", "h1", "h2", "h3", "h4", "h5", "h6", "header", "hr", "html", "i", "iframe", "img", "input", "ins", "kbd", "label", "legend", "li", "main", "map", "mark", "meter", "nav", "object", "ol", "optgroup", "option", "output", "p", "picture", "pre", "progress", "q", "rp", "rt", "ruby", "s", "samp", "section", "select", "small", "source", "span", "strong", "sub", "summary", "sup", "svg", "table", "tbody", "td", "template", "textarea", "tfoot", "th", "thead", "time", "tr", "track", "u", "ul", "var", "video", "wbr"];
122
+ declare const HTML_TEXT_TAGS: readonly ["abbr", "b", "bdi", "bdo", "big", "blockquote", "cite", "code", "del", "div", "dl", "dt", "em", "figcaption", "h1", "h2", "h3", "h4", "h5", "h6", "i", "ins", "kbd", "label", "legend", "li", "p", "pre", "q", "rp", "rt", "s", "small", "span", "strong", "sub", "summary", "sup", "time", "u"];
123
+ type HTMLTags = (typeof HTML_TAGS)[number];
124
+ type HTMLTextTags = (typeof HTML_TEXT_TAGS)[number];
125
+ //#endregion
126
+ //#region src/html/index.d.ts
127
+ type HTMLTagAttrsByTag<T extends HTMLTags> = T extends HTMLTags ? HTMLElementAttrs[T] : Record<string, never>;
128
+ //#endregion
129
+ //#region src/config.d.ts
130
+ /**
131
+ * Describes the shape of the CSS-in-JS engine.
132
+ * Pyreon uses @pyreon/styler directly — no connector abstraction needed.
133
+ * This type is kept for API compatibility with downstream packages.
134
+ */
135
+ interface CSSEngineConnector {
136
+ css: typeof css;
137
+ styled: typeof styled;
138
+ keyframes: typeof keyframes;
139
+ }
140
+ interface PlatformConfig {
141
+ component: string | HTMLTags;
142
+ textComponent: string | HTMLTags;
143
+ createMediaQueries?: (props: {
144
+ breakpoints: Record<string, number>;
145
+ rootSize: number;
146
+ css: CSSEngineConnector["css"];
147
+ }) => Record<string, (...args: any[]) => any>;
148
+ }
149
+ type InitConfig = Partial<CSSEngineConnector & PlatformConfig>;
150
+ /**
151
+ * Configuration singleton that bridges the UI system with the CSS engine.
152
+ * All packages reference config.css, config.styled, etc.
153
+ *
154
+ * In Pyreon, the engine is @pyreon/styler and is available immediately —
155
+ * no lazy initialization or connector pattern needed.
156
+ */
157
+ declare class Configuration {
158
+ css: (strings: TemplateStringsArray, ...values: _pyreon_styler0.Interpolation[]) => _pyreon_styler0.CSSResult;
159
+ styled: StyledFunction;
160
+ keyframes: (strings: TemplateStringsArray, ...values: _pyreon_styler0.Interpolation[]) => {
161
+ readonly name: string;
162
+ toString(): string;
163
+ };
164
+ component: string | HTMLTags;
165
+ textComponent: string | HTMLTags;
166
+ createMediaQueries: PlatformConfig["createMediaQueries"];
167
+ init: (props: InitConfig) => void;
168
+ }
169
+ declare const config: Configuration;
170
+ declare const init: (props: InitConfig) => void;
171
+ //#endregion
172
+ //#region src/types.d.ts
173
+ interface Breakpoints {
174
+ [key: string]: number;
175
+ }
176
+ type BreakpointKeys = keyof Breakpoints;
177
+ //#endregion
178
+ //#region src/context.d.ts
179
+ /**
180
+ * Internal context shared across all @pyreon packages.
181
+ * Carries the theme object plus any extra provider props.
182
+ */
183
+ declare const context: _pyreon_core0.Context<any>;
184
+ type Theme = Partial<{
185
+ rootSize: number;
186
+ breakpoints: Breakpoints;
187
+ } & Record<string, any>>;
188
+ type ProviderType = Partial<{
189
+ theme: Theme;
190
+ children: VNodeChild;
191
+ } & Record<string, any>>;
192
+ /**
193
+ * Provider that feeds the internal Pyreon context with the theme.
194
+ * When no theme is supplied, renders children directly.
195
+ */
196
+ declare function Provider({
197
+ theme,
198
+ children,
199
+ ...props
200
+ }: ProviderType): VNodeChild;
201
+ //#endregion
202
+ //#region src/hoistNonReactStatics.d.ts
203
+ /**
204
+ * Copies non-framework static properties from a source component to a target.
205
+ *
206
+ * Pyreon equivalent of hoistNonReactStatics — simplified since Pyreon
207
+ * components are plain functions without React-specific statics like
208
+ * contextType, propTypes, getDerivedStateFromProps, etc.
209
+ */
210
+ declare const hoistNonReactStatics: <T, S>(target: T, source: S, excludeList?: Record<string, true>) => T;
211
+ //#endregion
212
+ //#region src/isEmpty.d.ts
213
+ type IsEmpty = <T extends Record<number | string, any> | any[] | null | undefined>(param: T) => T extends null | undefined ? true : keyof T extends never ? true : T extends T[] ? T[number] extends never ? true : false : false;
214
+ declare const isEmpty: IsEmpty;
215
+ //#endregion
216
+ //#region src/isEqual.d.ts
217
+ declare const isEqual: (a: unknown, b: unknown) => boolean;
218
+ //#endregion
219
+ //#region src/render.d.ts
220
+ type RenderProps<T extends Record<string, unknown> | undefined> = (props: Partial<T>) => VNodeChild;
221
+ /**
222
+ * Flexible element renderer that handles multiple content types:
223
+ * - Primitives (string, number) — returned as-is
224
+ * - Arrays — returned as-is
225
+ * - Component functions — created via h()
226
+ * - VNode objects — returned as-is (with props merged if provided)
227
+ * - Render props (functions) — called with attachProps
228
+ * - Falsy values — return null
229
+ */
230
+ type Render = <T extends Record<string, any> | undefined>(content?: ComponentFn | string | VNodeChild | VNodeChild[] | RenderProps<T>, attachProps?: T) => VNodeChild;
231
+ declare const render: Render;
232
+ //#endregion
233
+ //#region src/useStableValue.d.ts
234
+ /**
235
+ * Returns a referentially stable version of `value`. The returned reference
236
+ * only changes when the value is no longer deeply equal to the previous one.
237
+ *
238
+ * Pyreon equivalent of the React useStableValue hook — uses a signal
239
+ * internally to hold the stable reference.
240
+ */
241
+ declare const useStableValue: <T>(value: T) => T;
242
+ //#endregion
243
+ //#region src/utils.d.ts
244
+ declare const omit: <T extends Record<string, any>>(obj: T | null | undefined, keys?: readonly (string | keyof T)[]) => Partial<T>;
245
+ declare const pick: <T extends Record<string, any>>(obj: T | null | undefined, keys?: readonly (string | keyof T)[]) => Partial<T>;
246
+ declare const get: (obj: any, path: string | string[], defaultValue?: any) => any;
247
+ declare const set: (obj: Record<string, any>, path: string | string[], value: any) => Record<string, any>;
248
+ declare const throttle: <T extends (...args: any[]) => any>(fn: T, wait?: number, options?: {
249
+ leading?: boolean;
250
+ trailing?: boolean;
251
+ }) => T & {
252
+ cancel: () => void;
253
+ };
254
+ declare const merge: <T extends Record<string, any>>(target: T, ...sources: Record<string, any>[]) => T;
255
+ //#endregion
256
+ export { type BreakpointKeys, type Breakpoints, type CSSEngineConnector, type HTMLElementAttrs, type HTMLTagAttrsByTag, type HTMLTags, type HTMLTextTags, HTML_TAGS, HTML_TEXT_TAGS, type IsEmpty, Provider, type Render, compose, config, context, get, hoistNonReactStatics, init, isEmpty, isEqual, merge, omit, pick, render, set, throttle, useStableValue };
257
+ //# sourceMappingURL=index2.d.ts.map
package/lib/index.js ADDED
@@ -0,0 +1,560 @@
1
+ import { css, keyframes, styled } from "@pyreon/styler";
2
+ import { createContext, h, onUnmount, popContext, pushContext } from "@pyreon/core";
3
+
4
+ //#region src/compose.ts
5
+ const compose = (...fns) => (p) => fns.reduceRight((acc, cur) => cur(acc), p);
6
+
7
+ //#endregion
8
+ //#region src/config.ts
9
+ /**
10
+ * Configuration singleton that bridges the UI system with the CSS engine.
11
+ * All packages reference config.css, config.styled, etc.
12
+ *
13
+ * In Pyreon, the engine is @pyreon/styler and is available immediately —
14
+ * no lazy initialization or connector pattern needed.
15
+ */
16
+ var Configuration = class {
17
+ css = css;
18
+ styled = styled;
19
+ keyframes = keyframes;
20
+ component = "div";
21
+ textComponent = "span";
22
+ createMediaQueries = void 0;
23
+ init = (props) => {
24
+ if (props.css) this.css = props.css;
25
+ if (props.styled) this.styled = props.styled;
26
+ if (props.keyframes) this.keyframes = props.keyframes;
27
+ if (props.component) this.component = props.component;
28
+ if (props.textComponent) this.textComponent = props.textComponent;
29
+ if (props.createMediaQueries) this.createMediaQueries = props.createMediaQueries;
30
+ };
31
+ };
32
+ const config = new Configuration();
33
+ const { init } = config;
34
+
35
+ //#endregion
36
+ //#region src/isEmpty.ts
37
+ const isEmpty = ((param) => {
38
+ if (!param) return true;
39
+ if (typeof param !== "object") return true;
40
+ if (Array.isArray(param)) return param.length === 0;
41
+ return Object.keys(param).length === 0;
42
+ });
43
+
44
+ //#endregion
45
+ //#region src/context.tsx
46
+ /**
47
+ * Internal context shared across all @pyreon packages.
48
+ * Carries the theme object plus any extra provider props.
49
+ */
50
+ const context = createContext({});
51
+ /**
52
+ * Provider that feeds the internal Pyreon context with the theme.
53
+ * When no theme is supplied, renders children directly.
54
+ */
55
+ function Provider({ theme, children, ...props }) {
56
+ if (isEmpty(theme) || !theme) return children ?? null;
57
+ const contextValue = {
58
+ theme,
59
+ ...props
60
+ };
61
+ pushContext(new Map([[context.id, contextValue]]));
62
+ onUnmount(() => popContext());
63
+ return children ?? null;
64
+ }
65
+
66
+ //#endregion
67
+ //#region src/hoistNonReactStatics.ts
68
+ const KNOWN_STATICS = {
69
+ name: true,
70
+ length: true,
71
+ prototype: true,
72
+ caller: true,
73
+ callee: true,
74
+ arguments: true,
75
+ arity: true
76
+ };
77
+ const COMPONENT_STATICS = {
78
+ displayName: true,
79
+ defaultProps: true
80
+ };
81
+ /**
82
+ * Copies non-framework static properties from a source component to a target.
83
+ *
84
+ * Pyreon equivalent of hoistNonReactStatics — simplified since Pyreon
85
+ * components are plain functions without React-specific statics like
86
+ * contextType, propTypes, getDerivedStateFromProps, etc.
87
+ */
88
+ const hoistNonReactStatics = (target, source, excludeList) => {
89
+ if (typeof source === "string") return target;
90
+ const proto = Object.getPrototypeOf(source);
91
+ if (proto && proto !== Object.prototype) hoistNonReactStatics(target, proto, excludeList);
92
+ const keys = [...Object.getOwnPropertyNames(source), ...Object.getOwnPropertySymbols(source)];
93
+ for (const key of keys) {
94
+ const k = key;
95
+ if (KNOWN_STATICS[k] || excludeList?.[k] || COMPONENT_STATICS[k]) continue;
96
+ const descriptor = Object.getOwnPropertyDescriptor(source, key);
97
+ if (descriptor) try {
98
+ Object.defineProperty(target, key, descriptor);
99
+ } catch {}
100
+ }
101
+ return target;
102
+ };
103
+
104
+ //#endregion
105
+ //#region src/html/htmlTags.ts
106
+ const HTML_TAGS = [
107
+ "a",
108
+ "abbr",
109
+ "address",
110
+ "area",
111
+ "article",
112
+ "aside",
113
+ "audio",
114
+ "b",
115
+ "bdi",
116
+ "bdo",
117
+ "big",
118
+ "blockquote",
119
+ "body",
120
+ "br",
121
+ "button",
122
+ "canvas",
123
+ "caption",
124
+ "cite",
125
+ "code",
126
+ "col",
127
+ "colgroup",
128
+ "data",
129
+ "datalist",
130
+ "dd",
131
+ "del",
132
+ "details",
133
+ "dfn",
134
+ "dialog",
135
+ "div",
136
+ "dl",
137
+ "dt",
138
+ "em",
139
+ "embed",
140
+ "fieldset",
141
+ "figcaption",
142
+ "figure",
143
+ "footer",
144
+ "form",
145
+ "h1",
146
+ "h2",
147
+ "h3",
148
+ "h4",
149
+ "h5",
150
+ "h6",
151
+ "header",
152
+ "hr",
153
+ "html",
154
+ "i",
155
+ "iframe",
156
+ "img",
157
+ "input",
158
+ "ins",
159
+ "kbd",
160
+ "label",
161
+ "legend",
162
+ "li",
163
+ "main",
164
+ "map",
165
+ "mark",
166
+ "meter",
167
+ "nav",
168
+ "object",
169
+ "ol",
170
+ "optgroup",
171
+ "option",
172
+ "output",
173
+ "p",
174
+ "picture",
175
+ "pre",
176
+ "progress",
177
+ "q",
178
+ "rp",
179
+ "rt",
180
+ "ruby",
181
+ "s",
182
+ "samp",
183
+ "section",
184
+ "select",
185
+ "small",
186
+ "source",
187
+ "span",
188
+ "strong",
189
+ "sub",
190
+ "summary",
191
+ "sup",
192
+ "svg",
193
+ "table",
194
+ "tbody",
195
+ "td",
196
+ "template",
197
+ "textarea",
198
+ "tfoot",
199
+ "th",
200
+ "thead",
201
+ "time",
202
+ "tr",
203
+ "track",
204
+ "u",
205
+ "ul",
206
+ "var",
207
+ "video",
208
+ "wbr"
209
+ ];
210
+ const HTML_TEXT_TAGS = [
211
+ "abbr",
212
+ "b",
213
+ "bdi",
214
+ "bdo",
215
+ "big",
216
+ "blockquote",
217
+ "cite",
218
+ "code",
219
+ "del",
220
+ "div",
221
+ "dl",
222
+ "dt",
223
+ "em",
224
+ "figcaption",
225
+ "h1",
226
+ "h2",
227
+ "h3",
228
+ "h4",
229
+ "h5",
230
+ "h6",
231
+ "i",
232
+ "ins",
233
+ "kbd",
234
+ "label",
235
+ "legend",
236
+ "li",
237
+ "p",
238
+ "pre",
239
+ "q",
240
+ "rp",
241
+ "rt",
242
+ "s",
243
+ "small",
244
+ "span",
245
+ "strong",
246
+ "sub",
247
+ "summary",
248
+ "sup",
249
+ "time",
250
+ "u"
251
+ ];
252
+
253
+ //#endregion
254
+ //#region src/isEqual.ts
255
+ const isArrayEqual = (a, b) => {
256
+ if (a.length !== b.length) return false;
257
+ for (let i = 0; i < a.length; i++) if (!isEqual(a[i], b[i])) return false;
258
+ return true;
259
+ };
260
+ const isObjectEqual = (a, b) => {
261
+ const aKeys = Object.keys(a);
262
+ if (aKeys.length !== Object.keys(b).length) return false;
263
+ for (const key of aKeys) {
264
+ if (!Object.hasOwn(b, key)) return false;
265
+ if (!isEqual(a[key], b[key])) return false;
266
+ }
267
+ return true;
268
+ };
269
+ const isEqual = (a, b) => {
270
+ if (Object.is(a, b)) return true;
271
+ if (typeof a !== typeof b || a == null || b == null || typeof a !== "object") return false;
272
+ if (Array.isArray(a)) return Array.isArray(b) && isArrayEqual(a, b);
273
+ if (Array.isArray(b)) return false;
274
+ return isObjectEqual(a, b);
275
+ };
276
+
277
+ //#endregion
278
+ //#region src/render.tsx
279
+ const render = (content, attachProps) => {
280
+ if (!content) return null;
281
+ const t = typeof content;
282
+ if (t === "string" || t === "number" || t === "boolean" || t === "bigint") return content;
283
+ if (Array.isArray(content)) return content;
284
+ if (typeof content === "function") return h(content, attachProps ?? {});
285
+ if (typeof content === "object" && content !== null) return content;
286
+ return content;
287
+ };
288
+
289
+ //#endregion
290
+ //#region ../../node_modules/.bun/@pyreon+reactivity@0.3.1/node_modules/@pyreon/reactivity/lib/index.js
291
+ let batchDepth = 0;
292
+ let setA = /* @__PURE__ */ new Set();
293
+ let pendingNotifications = setA;
294
+ function isBatching() {
295
+ return batchDepth > 0;
296
+ }
297
+ function enqueuePendingNotification(notify) {
298
+ pendingNotifications.add(notify);
299
+ }
300
+ let activeEffect = null;
301
+ const effectDeps = /* @__PURE__ */ new WeakMap();
302
+ let _depsCollector = null;
303
+ /**
304
+ * Register the active effect as a subscriber of the given reactive source.
305
+ * The subscriber Set is created lazily on the host — sources read only outside
306
+ * effects never allocate a Set.
307
+ */
308
+ function trackSubscriber(host) {
309
+ if (activeEffect) {
310
+ if (!host._s) host._s = /* @__PURE__ */ new Set();
311
+ const subscribers = host._s;
312
+ subscribers.add(activeEffect);
313
+ if (_depsCollector) _depsCollector.push(subscribers);
314
+ else {
315
+ let deps = effectDeps.get(activeEffect);
316
+ if (!deps) {
317
+ deps = /* @__PURE__ */ new Set();
318
+ effectDeps.set(activeEffect, deps);
319
+ }
320
+ deps.add(subscribers);
321
+ }
322
+ }
323
+ }
324
+ function notifySubscribers(subscribers) {
325
+ if (subscribers.size === 0) return;
326
+ if (subscribers.size === 1) {
327
+ const sub = subscribers.values().next().value;
328
+ if (isBatching()) enqueuePendingNotification(sub);
329
+ else sub();
330
+ return;
331
+ }
332
+ if (isBatching()) for (const sub of subscribers) enqueuePendingNotification(sub);
333
+ else {
334
+ const originalSize = subscribers.size;
335
+ let i = 0;
336
+ for (const sub of subscribers) {
337
+ if (i >= originalSize) break;
338
+ sub();
339
+ i++;
340
+ }
341
+ }
342
+ }
343
+ let _traceListeners = null;
344
+ /** @internal — called from signal.set() when tracing is active */
345
+ function _notifyTraceListeners(sig, prev, next) {
346
+ if (!_traceListeners) return;
347
+ const event = {
348
+ signal: sig,
349
+ name: sig.label,
350
+ prev,
351
+ next,
352
+ stack: (/* @__PURE__ */ new Error()).stack ?? "",
353
+ timestamp: performance.now()
354
+ };
355
+ for (const l of _traceListeners) l(event);
356
+ }
357
+ /** Check if any trace listeners are active (fast path for signal.set) */
358
+ function isTracing() {
359
+ return _traceListeners !== null;
360
+ }
361
+ function _peek() {
362
+ return this._v;
363
+ }
364
+ function _set(newValue) {
365
+ if (Object.is(this._v, newValue)) return;
366
+ const prev = this._v;
367
+ this._v = newValue;
368
+ if (isTracing()) _notifyTraceListeners(this, prev, newValue);
369
+ if (this._d) notifyDirect(this._d);
370
+ if (this._s) notifySubscribers(this._s);
371
+ }
372
+ function _update(fn) {
373
+ _set.call(this, fn(this._v));
374
+ }
375
+ function _subscribe(listener) {
376
+ if (!this._s) this._s = /* @__PURE__ */ new Set();
377
+ this._s.add(listener);
378
+ return () => this._s?.delete(listener);
379
+ }
380
+ /**
381
+ * Register a direct updater — lighter than subscribe().
382
+ * Uses a flat array instead of Set. Disposal nulls the slot (no Set.delete overhead).
383
+ * Used by compiler-emitted _bindText/_bindDirect for zero-overhead DOM bindings.
384
+ */
385
+ function _directFn(updater) {
386
+ if (!this._d) this._d = [];
387
+ const arr = this._d;
388
+ const idx = arr.length;
389
+ arr.push(updater);
390
+ return () => {
391
+ arr[idx] = null;
392
+ };
393
+ }
394
+ /**
395
+ * Notify direct updaters — flat array iteration, batch-aware.
396
+ * Null slots (from disposed updaters) are skipped.
397
+ */
398
+ function notifyDirect(updaters) {
399
+ if (isBatching()) for (let i = 0; i < updaters.length; i++) {
400
+ const fn = updaters[i];
401
+ if (fn) enqueuePendingNotification(fn);
402
+ }
403
+ else for (let i = 0; i < updaters.length; i++) updaters[i]?.();
404
+ }
405
+ function _debug() {
406
+ return {
407
+ name: this.label,
408
+ value: this._v,
409
+ subscriberCount: this._s?.size ?? 0
410
+ };
411
+ }
412
+ /**
413
+ * Create a reactive signal.
414
+ *
415
+ * Only 1 closure is allocated (the read function). State is stored as
416
+ * properties on the function object (_v, _s) and methods (peek, set,
417
+ * update, subscribe) are shared across all signals — not per-signal closures.
418
+ */
419
+ function signal(initialValue, options) {
420
+ const read = (() => {
421
+ trackSubscriber(read);
422
+ return read._v;
423
+ });
424
+ read._v = initialValue;
425
+ read._s = null;
426
+ read._d = null;
427
+ read.peek = _peek;
428
+ read.set = _set;
429
+ read.update = _update;
430
+ read.subscribe = _subscribe;
431
+ read.direct = _directFn;
432
+ read.debug = _debug;
433
+ read.label = options?.name;
434
+ return read;
435
+ }
436
+
437
+ //#endregion
438
+ //#region src/useStableValue.ts
439
+ /**
440
+ * Returns a referentially stable version of `value`. The returned reference
441
+ * only changes when the value is no longer deeply equal to the previous one.
442
+ *
443
+ * Pyreon equivalent of the React useStableValue hook — uses a signal
444
+ * internally to hold the stable reference.
445
+ */
446
+ const useStableValue = (value) => {
447
+ const ref = signal(value);
448
+ if (!isEqual(ref.peek(), value)) ref.set(value);
449
+ return ref.peek();
450
+ };
451
+
452
+ //#endregion
453
+ //#region src/utils.ts
454
+ const omit = (obj, keys) => {
455
+ if (obj == null) return {};
456
+ if (!keys || keys.length === 0) return { ...obj };
457
+ const result = {};
458
+ const keysSet = new Set(keys);
459
+ for (const key in obj) if (Object.hasOwn(obj, key) && !keysSet.has(key)) result[key] = obj[key];
460
+ return result;
461
+ };
462
+ const pick = (obj, keys) => {
463
+ if (obj == null) return {};
464
+ if (!keys || keys.length === 0) return { ...obj };
465
+ const result = {};
466
+ for (const key of keys) {
467
+ const k = key;
468
+ if (Object.hasOwn(obj, k)) result[k] = obj[k];
469
+ }
470
+ return result;
471
+ };
472
+ const PATH_RE = /[^.[\]]+/g;
473
+ const parsePath = (path) => {
474
+ if (Array.isArray(path)) return path;
475
+ return path.match(PATH_RE) ?? [];
476
+ };
477
+ const isUnsafeKey = (key) => key === "__proto__" || key === "prototype" || key === "constructor";
478
+ const get = (obj, path, defaultValue) => {
479
+ const keys = parsePath(path);
480
+ let result = obj;
481
+ for (const key of keys) {
482
+ if (result == null || isUnsafeKey(key)) return defaultValue;
483
+ result = result[key];
484
+ }
485
+ return result === void 0 ? defaultValue : result;
486
+ };
487
+ const set = (obj, path, value) => {
488
+ const keys = parsePath(path);
489
+ let current = obj;
490
+ for (let i = 0; i < keys.length - 1; i++) {
491
+ const key = keys[i];
492
+ if (isUnsafeKey(key)) return obj;
493
+ const nextKey = keys[i + 1];
494
+ if (isUnsafeKey(nextKey)) return obj;
495
+ if (current[key] == null) current[key] = /^\d+$/.test(nextKey) ? [] : {};
496
+ current = current[key];
497
+ }
498
+ const lastKey = keys[keys.length - 1];
499
+ if (lastKey != null && !isUnsafeKey(lastKey)) current[lastKey] = value;
500
+ return obj;
501
+ };
502
+ const throttle = (fn, wait = 0, options) => {
503
+ const leading = options?.leading !== false;
504
+ const trailing = options?.trailing !== false;
505
+ let lastCallTime;
506
+ let timeoutId;
507
+ let lastArgs;
508
+ const invoke = (args) => {
509
+ lastCallTime = Date.now();
510
+ fn(...args);
511
+ };
512
+ const startTrailingTimer = (args, delay) => {
513
+ lastArgs = args;
514
+ if (timeoutId !== void 0) return;
515
+ timeoutId = setTimeout(() => {
516
+ timeoutId = void 0;
517
+ if (lastArgs) {
518
+ invoke(lastArgs);
519
+ lastArgs = void 0;
520
+ }
521
+ }, delay);
522
+ };
523
+ const throttled = (...args) => {
524
+ const now = Date.now();
525
+ const elapsed = lastCallTime === void 0 ? wait : now - lastCallTime;
526
+ if (elapsed >= wait) if (leading) invoke(args);
527
+ else {
528
+ lastCallTime = now;
529
+ if (trailing) startTrailingTimer(args, wait);
530
+ }
531
+ else if (trailing) startTrailingTimer(args, wait - elapsed);
532
+ };
533
+ throttled.cancel = () => {
534
+ if (timeoutId !== void 0) {
535
+ clearTimeout(timeoutId);
536
+ timeoutId = void 0;
537
+ }
538
+ lastArgs = void 0;
539
+ lastCallTime = void 0;
540
+ };
541
+ return throttled;
542
+ };
543
+ const isPlainObject = (value) => value !== null && typeof value === "object" && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype;
544
+ const merge = (target, ...sources) => {
545
+ for (const source of sources) {
546
+ if (source == null) continue;
547
+ for (const key of Object.keys(source)) {
548
+ if (key === "__proto__" || key === "constructor" || key === "prototype") continue;
549
+ const targetVal = target[key];
550
+ const sourceVal = source[key];
551
+ if (isPlainObject(targetVal) && isPlainObject(sourceVal)) target[key] = merge({ ...targetVal }, sourceVal);
552
+ else target[key] = sourceVal;
553
+ }
554
+ }
555
+ return target;
556
+ };
557
+
558
+ //#endregion
559
+ export { HTML_TAGS, HTML_TEXT_TAGS, Provider, compose, config, context, get, hoistNonReactStatics, init, isEmpty, isEqual, merge, omit, pick, render, set, throttle, useStableValue };
560
+ //# sourceMappingURL=index.js.map
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@pyreon/ui-core",
3
+ "version": "0.0.2",
4
+ "description": "Core utilities, config, and context for Pyreon UI System",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "sideEffects": false,
8
+ "exports": {
9
+ "source": "./src/index.ts",
10
+ "import": "./lib/index.js",
11
+ "types": "./lib/index.d.ts"
12
+ },
13
+ "types": "./lib/index.d.ts",
14
+ "main": "./lib/index.js",
15
+ "files": [
16
+ "lib",
17
+ "!lib/**/*.map",
18
+ "!lib/analysis",
19
+ "README.md",
20
+ "LICENSE"
21
+ ],
22
+ "engines": {
23
+ "node": ">= 18"
24
+ },
25
+ "publishConfig": {
26
+ "access": "public"
27
+ },
28
+ "scripts": {
29
+ "build": "bun run vl_rolldown_build",
30
+ "lint": "biome check src/",
31
+ "test": "vitest run",
32
+ "typecheck": "tsc --noEmit"
33
+ },
34
+ "peerDependencies": {
35
+ "@pyreon/core": ">=0.3.0",
36
+ "@pyreon/styler": "^0.0.2"
37
+ },
38
+ "devDependencies": {
39
+ "@vitus-labs/tools-rolldown": "^1.15.0",
40
+ "@vitus-labs/tools-typescript": "^1.15.0"
41
+ }
42
+ }