@rb-pulse/core 1.3.0 → 1.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.
package/index.d.ts ADDED
@@ -0,0 +1,65 @@
1
+ /**
2
+ * @rb-pulse/core — TypeScript declarations for the Pulse Roblox scripting framework.
3
+ *
4
+ * Add `"types": ["@rb-pulse/core"]` to your tsconfig `compilerOptions` and every
5
+ * Pulse global (`signal`, `defineComponent`, `on.*`, widget factories, `Pulse`, …)
6
+ * plus the full Roblox API (`game`, `workspace`, `task`, all instance types, …)
7
+ * become available in every `.ts` file without any imports.
8
+ *
9
+ * Package layout:
10
+ * types/signals.d.ts — PulseSignal<T>, signal(), computed()
11
+ * types/events.d.ts — HandlerOpts, on.* subscriptions
12
+ * types/widgets.d.ts — widget interfaces + factory functions
13
+ * types/components.d.ts — defineComponent, definePage, groupbox, LayoutConfig
14
+ * types/runtime.d.ts — Pulse namespace, _PulseGet* character helpers
15
+ *
16
+ * Roblox API types are provided by @rbxts/types (auto-included — no extra config needed).
17
+ *
18
+ * @example tsconfig.json
19
+ * {
20
+ * "compilerOptions": {
21
+ * "strict": true,
22
+ * "noEmit": true,
23
+ * "skipLibCheck": true,
24
+ * "target": "ESNext",
25
+ * "moduleResolution": "bundler",
26
+ * "types": ["@rb-pulse/core"]
27
+ * }
28
+ * }
29
+ */
30
+
31
+ // Pulse framework globals
32
+ /// <reference path="./types/globals.d.ts" />
33
+ /// <reference path="./types/signals.d.ts" />
34
+ /// <reference path="./types/events.d.ts" />
35
+ /// <reference path="./types/widgets.d.ts" />
36
+ /// <reference path="./types/components.d.ts" />
37
+ /// <reference path="./types/runtime.d.ts" />
38
+
39
+ // ── Named re-exports ───────────────────────────────────────────────────────────
40
+ // Globals are already available without imports (via declare global above).
41
+ // These re-exports let you use explicit `import type` when you want visible types
42
+ // in function signatures, generics, or cross-file type sharing.
43
+ //
44
+ // @example
45
+ // import type { PulseSignal, WidgetDef, LayoutConfig } from '@rb-pulse/core'
46
+
47
+ export type { PulseSignal } from './types/signals'
48
+ export type { HandlerOpts } from './types/events'
49
+ export type {
50
+ ToggleDef,
51
+ SliderDef,
52
+ DropdownDef,
53
+ MultiDropdownDef,
54
+ ButtonDef,
55
+ KeybindDef,
56
+ LabelDef,
57
+ SeparatorDef,
58
+ WidgetDef,
59
+ } from './types/widgets'
60
+ export type {
61
+ ComponentRef,
62
+ GroupboxDef,
63
+ PageDef,
64
+ LayoutConfig,
65
+ } from './types/components'
package/package.json CHANGED
@@ -1,18 +1,18 @@
1
1
  {
2
2
  "name": "@rb-pulse/core",
3
- "version": "1.3.0",
4
- "description": "Pulse framework — component, signal, and event TypeScript API",
5
- "main": "./index.ts",
6
- "types": "./index.ts",
3
+ "version": "1.3.1",
4
+ "description": "Pulse framework — TypeScript type declarations for components, signals, widgets, and the Roblox API",
5
+ "types": "./index.d.ts",
7
6
  "files": [
8
- "index.ts",
9
- "*.d.ts"
7
+ "index.d.ts",
8
+ "types/"
10
9
  ],
11
10
  "keywords": [
12
11
  "roblox",
13
12
  "pulse",
14
13
  "lua",
15
- "typescript-to-lua"
14
+ "typescript-to-lua",
15
+ "types"
16
16
  ],
17
17
  "license": "MIT"
18
18
  }
@@ -0,0 +1,162 @@
1
+ /**
2
+ * Component system, page layout, and window configuration.
3
+ *
4
+ * - `defineComponent` — SolidJS-style reactive component setup
5
+ * - `definePage` — Next.js-style file-based page tabs
6
+ * - `groupbox` — column layout declaration inside a page
7
+ * - `LayoutConfig` — export default from `src/layout.ts`
8
+ */
9
+
10
+ /// <reference path="./widgets.d.ts" />
11
+
12
+ declare global {
13
+
14
+ // ── Component ──────────────────────────────────────────────────────────────────
15
+
16
+ /**
17
+ * Opaque handle returned by `defineComponent`.
18
+ * Pass to `groupbox({ mount: 'ComponentName' })` to render its widgets.
19
+ */
20
+ interface ComponentRef {
21
+ readonly _name: string
22
+ readonly _ui: WidgetDef[]
23
+ }
24
+
25
+ /**
26
+ * Register a reactive component.
27
+ *
28
+ * The `setup` function runs once on init. Declare signals, register `on.*`
29
+ * event handlers, then return an array of widget definitions. All event
30
+ * connections are auto-disconnected when the script re-executes or the
31
+ * component is destroyed (re-execution guard handles this automatically).
32
+ *
33
+ * Components mirror SolidJS function components: pure setup, reactive state,
34
+ * declarative UI return.
35
+ *
36
+ * @example
37
+ * defineComponent('SpeedHack', (): WidgetDef[] => {
38
+ * const enabled = signal<boolean>(false)
39
+ * const speed = signal<number>(16)
40
+ *
41
+ * on.heartbeat({ when: enabled }, (): void => {
42
+ * const h = _PulseGetHumanoid() as any
43
+ * if (h) h.WalkSpeed = speed()
44
+ * })
45
+ *
46
+ * return [
47
+ * toggle('Speed Hack').bind(enabled),
48
+ * slider('Walk Speed', { min: 16, max: 250 }).bind(speed),
49
+ * ]
50
+ * })
51
+ */
52
+ function defineComponent(name: string, setup: () => WidgetDef[]): ComponentRef
53
+
54
+ // ── Page layout ────────────────────────────────────────────────────────────────
55
+
56
+ /**
57
+ * Groupbox column inside a page layout.
58
+ * Use `mount` to attach a named component's widgets, or `widgets` for inline widgets.
59
+ */
60
+ interface GroupboxDef {
61
+ readonly type: 'groupbox'
62
+ readonly side: 'left' | 'right'
63
+ readonly title: string
64
+ readonly icon: string
65
+ readonly mount?: string
66
+ }
67
+
68
+ /** Page tab descriptor returned by `definePage`. */
69
+ interface PageDef {
70
+ readonly title: string
71
+ readonly icon: string
72
+ readonly layout: GroupboxDef[]
73
+ }
74
+
75
+ /**
76
+ * Declare a groupbox column inside a `definePage` factory.
77
+ *
78
+ * @param side Column position — `'left'` or `'right'`
79
+ * @param title Header text of the groupbox
80
+ * @param opts `icon` for a lucide icon name; `mount` to attach a component by name;
81
+ * `widgets` for inline widget declarations without a component
82
+ *
83
+ * @example
84
+ * groupbox('left', 'Player', { icon: 'person', mount: 'SpeedHack' })
85
+ * groupbox('right', 'Visuals', { icon: 'eye', mount: 'PlayerESP' })
86
+ */
87
+ function groupbox(side: 'left' | 'right', title: string, opts?: {
88
+ icon?: string
89
+ mount?: string
90
+ widgets?: WidgetDef[]
91
+ }): GroupboxDef
92
+
93
+ /**
94
+ * Declare a page tab. Place in `src/pages/N_Name.ts`.
95
+ *
96
+ * Tab order follows the numeric filename prefix: `1_Home.ts`, `2_Combat.ts`, …
97
+ * This mirrors the Next.js file-based routing convention.
98
+ *
99
+ * @example
100
+ * // src/pages/1_Home.ts
101
+ * definePage('Home', { icon: 'house' }, () => [
102
+ * groupbox('left', 'Player', { mount: 'SpeedHack' }),
103
+ * groupbox('right', 'Visuals', { mount: 'PlayerESP' }),
104
+ * ])
105
+ */
106
+ function definePage(
107
+ title: string,
108
+ opts: { icon?: string },
109
+ factory: () => GroupboxDef[],
110
+ ): PageDef
111
+
112
+ // ── Layout configuration ───────────────────────────────────────────────────────
113
+
114
+ /**
115
+ * Window/layout configuration — export as `default` from `src/layout.ts`.
116
+ * Drives the UI window title, size, theme, and UI library selection.
117
+ *
118
+ * @example
119
+ * // src/layout.ts
120
+ * export default {
121
+ * title: 'My Script',
122
+ * author: 'YourName',
123
+ * uiLibrary: 'windui',
124
+ * size: [850, 560],
125
+ * theme: 'Indigo',
126
+ * folder: 'MyHub',
127
+ * } satisfies LayoutConfig
128
+ */
129
+ interface LayoutConfig {
130
+ /** Window title displayed in the UI header. */
131
+ title: string
132
+ /** Author subtitle shown below the title. */
133
+ author?: string
134
+ /** Key to toggle the UI open/closed. @default 'RightControl' */
135
+ toggleKey?: string
136
+ /** Window dimensions `[width, height]` in pixels. @default [950, 600] */
137
+ size?: [number, number]
138
+ /** UI library to use. Currently only `'windui'` is supported. */
139
+ uiLibrary?: 'windui'
140
+ /** Theme name (library-specific, e.g. `'Indigo'`, `'Dark'`). */
141
+ theme?: string
142
+ /** Lucide icon name or asset URL for the window icon. */
143
+ icon?: string
144
+ /** Config save folder name (inside executor workspace). */
145
+ folder?: string
146
+ /** Enable acrylic/frosted glass background effect. */
147
+ acrylic?: boolean
148
+ /** Background transparency `[0, 1]`. */
149
+ transparency?: number
150
+ /** Show the open button only on mobile. */
151
+ openButtonMobileOnly?: boolean
152
+ /** Icon for the mobile open button. */
153
+ openButtonIcon?: string
154
+ /** Custom theme overrides. */
155
+ themes?: Array<{ name: string; values: Record<string, string> }>
156
+ /** File paths to exclude from the compat build. */
157
+ compatExclude?: string[]
158
+ }
159
+
160
+ }
161
+
162
+ export type { ComponentRef, GroupboxDef, PageDef, LayoutConfig }
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Event subscription system — RunService, UserInputService, and signal watchers.
3
+ * All connections are automatically cleaned up when the enclosing component is destroyed.
4
+ * Must be called inside a `defineComponent` setup function.
5
+ */
6
+
7
+ // Reference signal types so HandlerOpts can reference PulseSignal<T>
8
+ /// <reference path="./signals.d.ts" />
9
+
10
+ declare global {
11
+
12
+ /**
13
+ * Options for periodic event handlers.
14
+ * Both fields are optional — omit entirely to run every frame with no guard.
15
+ */
16
+ interface HandlerOpts {
17
+ /**
18
+ * Gate signal — while `false` the callback is skipped each frame.
19
+ * The connection stays alive; toggling to `true` resumes immediately.
20
+ *
21
+ * @example on.heartbeat({ when: enabled }, dt => doWork())
22
+ */
23
+ when?: PulseSignal<boolean>
24
+
25
+ /**
26
+ * Throttle — minimum seconds between callback invocations.
27
+ * Pass a `PulseSignal<number>` for live adjustment at runtime.
28
+ * @default 0 (every frame)
29
+ */
30
+ every?: number | PulseSignal<number>
31
+ }
32
+
33
+ /**
34
+ * Event subscription namespace.
35
+ *
36
+ * All connections registered via `on.*` are scoped to the current component
37
+ * and auto-disconnected when the script re-executes or the component is destroyed.
38
+ *
39
+ * @example
40
+ * defineComponent('SpeedHack', (): WidgetDef[] => {
41
+ * const enabled = signal<boolean>(false)
42
+ *
43
+ * on.heartbeat({ when: enabled }, dt => applySpeed())
44
+ * on.respawn(() => resetSpeed())
45
+ * on.signal(enabled, v => console.log('enabled:', v))
46
+ *
47
+ * return [toggle('Speed Hack').bind(enabled)]
48
+ * })
49
+ */
50
+ const on: {
51
+ /**
52
+ * RunService.Heartbeat — fires every physics frame (~60 fps).
53
+ * Equivalent to SolidJS `createEffect` scoped to the RunService.Heartbeat event.
54
+ *
55
+ * @example
56
+ * on.heartbeat({ when: enabled, every: 0.1 }, dt => doWork())
57
+ * on.heartbeat(dt => updateDisplay(dt))
58
+ */
59
+ heartbeat(opts: HandlerOpts, fn: (dt: number) => void): void
60
+ heartbeat(fn: (dt: number) => void): void
61
+
62
+ /**
63
+ * RunService.RenderStepped — fires before every render frame (client-only).
64
+ * Use for camera/visual updates that must sync with rendering.
65
+ */
66
+ renderStepped(opts: HandlerOpts, fn: (dt: number) => void): void
67
+ renderStepped(fn: (dt: number) => void): void
68
+
69
+ /**
70
+ * RunService.Stepped — fires at the physics step rate.
71
+ * Provides both elapsed time and delta time.
72
+ */
73
+ stepped(opts: HandlerOpts, fn: (time: number, dt: number) => void): void
74
+ stepped(fn: (time: number, dt: number) => void): void
75
+
76
+ /** UserInputService.InputBegan — fires when any input starts. */
77
+ inputBegan(fn: (input: any, gameProcessed: boolean) => void): void
78
+
79
+ /** UserInputService.InputEnded — fires when any input ends. */
80
+ inputEnded(fn: (input: any, gameProcessed: boolean) => void): void
81
+
82
+ /** Fires each time the local player's character model is added to the world. */
83
+ respawn(fn: () => void): void
84
+
85
+ /** Player.CharacterAdded — fires when a new character model appears. */
86
+ characterAdded(fn: (character: any) => void): void
87
+
88
+ /** Player.CharacterRemoving — fires just before the character is removed. */
89
+ characterRemoving(fn: (character: any) => void): void
90
+
91
+ /**
92
+ * Subscribe to a `PulseSignal` inside a component.
93
+ * Fires immediately with the current value, then on every change.
94
+ * The subscription is auto-cleaned with the component.
95
+ *
96
+ * @example
97
+ * on.signal(speed, v => applySpeed(v))
98
+ */
99
+ signal<T>(sig: PulseSignal<T>, fn: (val: T) => void): void
100
+
101
+ /**
102
+ * One-shot callback after a delay in seconds.
103
+ * @example on.after(0.5, () => init())
104
+ */
105
+ after(seconds: number, fn: () => void): void
106
+ }
107
+
108
+ }
109
+
110
+ export type { HandlerOpts }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Roblox Lua globals not declared by @rbxts/types.
3
+ *
4
+ * roblox-ts accesses the workspace via `game.Workspace`, but in standard Roblox
5
+ * Lua (and executor environments) it is also available as the `workspace` global.
6
+ * We declare it here so TypeScript recognises it in Pulse scripts.
7
+ */
8
+
9
+ declare global {
10
+
11
+ /**
12
+ * Alias for `game:GetService("Workspace")`.
13
+ * Provides access to the current place's Workspace service.
14
+ *
15
+ * @example
16
+ * const cam = workspace.CurrentCamera
17
+ * cam.FieldOfView = 90
18
+ */
19
+ const workspace: Workspace
20
+
21
+ /**
22
+ * The shared Lua global table.
23
+ * Used internally by the compiler to propagate runtime globals across
24
+ * sandboxed `loadstring` environments in executor contexts.
25
+ */
26
+ const _G: Record<string, unknown>
27
+
28
+ /**
29
+ * Print to the output console (Roblox Studio / executor output).
30
+ * Accepts any number of values, separated by tabs in the output.
31
+ */
32
+ function print(...args: unknown[]): void
33
+
34
+ }
35
+
36
+ export {}
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Pulse runtime globals — the `Pulse` namespace and character helper functions.
3
+ * These are set up automatically by the CDN bundle before your code runs.
4
+ */
5
+
6
+ declare global {
7
+
8
+ // ── Pulse runtime namespace ────────────────────────────────────────────────────
9
+
10
+ /**
11
+ * The Pulse runtime namespace.
12
+ * Populated by the CDN bundle and extended by the adapter (Wind UI).
13
+ *
14
+ * `Pulse.UI` is available after the Wind UI adapter initialises the window.
15
+ * `Pulse.Log` and `Pulse.TestMode` are always available.
16
+ */
17
+ const Pulse: {
18
+ /**
19
+ * Dynamic element builder — populated by the Wind UI adapter.
20
+ * Use this for advanced manual UI construction beyond the standard widget set.
21
+ */
22
+ UI: {
23
+ /** Get the mounted groupbox container for a component reference. */
24
+ gb(component: ComponentRef): any
25
+ /** Add a rich paragraph (title + description) to a container. */
26
+ paragraph(
27
+ container: any,
28
+ title: string,
29
+ desc: string,
30
+ ): { set(title?: string, desc?: string): void }
31
+ /** Add a button to a container. */
32
+ button(container: any, label: string, desc: string, fn: () => void): any
33
+ /** Add a toggle to a container. */
34
+ toggle(
35
+ container: any,
36
+ id: string,
37
+ label: string,
38
+ desc: string,
39
+ defaultVal: boolean,
40
+ fn: (v: boolean) => void,
41
+ ): any
42
+ /** Add a slider to a container. */
43
+ slider(
44
+ container: any,
45
+ id: string,
46
+ label: string,
47
+ min: number,
48
+ max: number,
49
+ defaultVal: number,
50
+ fn: (v: number) => void,
51
+ ): any
52
+ /** Add a dropdown to a container. */
53
+ dropdown(
54
+ container: any,
55
+ id: string,
56
+ label: string,
57
+ options: string[],
58
+ defaultVal: string | undefined,
59
+ multi: boolean,
60
+ fn: (v: string | string[]) => void,
61
+ ): any
62
+ /** Add a collapsible section group to a container. */
63
+ section(container: any, title: string, icon?: string): any
64
+ }
65
+
66
+ /** Structured logging helpers. */
67
+ Log: {
68
+ info(tag: string, msg: string, data?: Record<string, unknown>): void
69
+ warn(tag: string, msg: string, data?: Record<string, unknown>): void
70
+ error(tag: string, msg: string, data?: Record<string, unknown>): void
71
+ }
72
+
73
+ /** Automated test-mode helpers (used by the test harness). */
74
+ TestMode: {
75
+ isActive(): boolean
76
+ isTarget(id: string): boolean
77
+ getTargets(): string[]
78
+ }
79
+ }
80
+
81
+ // ── Character helper functions ─────────────────────────────────────────────────
82
+ // Safe wrappers around LocalPlayer.Character — all return undefined if the
83
+ // character or the specific instance is not present.
84
+
85
+ /** Returns `LocalPlayer.Character` or `undefined`. */
86
+ function _PulseGetChar(): Instance | undefined
87
+
88
+ /** Returns `HumanoidRootPart` from the local character, or `undefined`. */
89
+ function _PulseGetHRP(): BasePart | undefined
90
+
91
+ /** Returns `Humanoid` from the local character, or `undefined`. */
92
+ function _PulseGetHumanoid(): Humanoid | undefined
93
+
94
+ /** Returns `true` while the local humanoid's health is above zero. */
95
+ function _PulseGetAlive(): boolean
96
+
97
+ }
98
+
99
+ export {}
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Reactive signal primitives — SolidJS-style state atoms.
3
+ * No imports needed: these are Lua globals available in every Pulse script.
4
+ */
5
+
6
+ declare global {
7
+
8
+ /**
9
+ * Reactive state cell returned by `signal<T>()`.
10
+ *
11
+ * Call it (no args) to read the current value. Calling `.set()` pushes a new
12
+ * value and propagates it to every bound UI widget, `on.*` guard, and watcher.
13
+ *
14
+ * Mirrors SolidJS `createSignal` but as a single callable object instead of a
15
+ * getter/setter tuple.
16
+ *
17
+ * @example
18
+ * const enabled = signal<boolean>(false)
19
+ * const speed = signal<number>(16)
20
+ *
21
+ * enabled() // → false (read)
22
+ * enabled.set(true) // write, triggers watchers
23
+ * enabled.toggle() // flip boolean in-place
24
+ * speed.update(v => v + 1)
25
+ */
26
+ interface PulseSignal<T> {
27
+ /** Read the current value. */
28
+ (): T
29
+
30
+ /** Write a new value. No-op if the value hasn't changed. */
31
+ set(this: PulseSignal<T>, value: T): void
32
+
33
+ /**
34
+ * Toggle a boolean signal in-place (`sig.set(!sig())`).
35
+ * Type-constrained to `PulseSignal<boolean>`.
36
+ */
37
+ toggle(this: PulseSignal<boolean>): void
38
+
39
+ /**
40
+ * Subscribe to value changes.
41
+ * Fires immediately with the current value, then on every subsequent change.
42
+ * @returns Unsubscribe function — call to stop the subscription.
43
+ */
44
+ watch(this: PulseSignal<T>, fn: (value: T) => void): () => void
45
+
46
+ /**
47
+ * Apply a transform and write the result.
48
+ * @example speed.update(v => v + 10)
49
+ */
50
+ update(this: PulseSignal<T>, fn: (current: T) => T): void
51
+ }
52
+
53
+ /**
54
+ * Create a reactive state cell with an initial value.
55
+ * Always use an explicit generic so TypeScript infers the correct type.
56
+ *
57
+ * @example
58
+ * const enabled = signal<boolean>(false)
59
+ * const speed = signal<number>(16)
60
+ * const mode = signal<string>('Low')
61
+ * const targets = signal<string[]>([])
62
+ */
63
+ function signal<T>(initialValue: T): PulseSignal<T>
64
+
65
+ /**
66
+ * Create a read-only derived value that recomputes lazily when its dependencies
67
+ * change. The result is cached until invalidated.
68
+ *
69
+ * @example
70
+ * const displaySpeed = computed(() => `Speed: ${speed()}x`)
71
+ * label(displaySpeed())
72
+ */
73
+ function computed<T>(fn: () => T): () => T
74
+
75
+ }
76
+
77
+ export type { PulseSignal }
@@ -0,0 +1,208 @@
1
+ /**
2
+ * Widget definitions and factory functions.
3
+ * Every factory returns a builder that you chain to configure and optionally bind to a signal.
4
+ * Return an array of widget defs from `defineComponent` to render them into the UI.
5
+ */
6
+
7
+ /// <reference path="./signals.d.ts" />
8
+
9
+ declare global {
10
+
11
+ // ── Widget interfaces ──────────────────────────────────────────────────────────
12
+
13
+ /**
14
+ * Toggle (checkbox) bound to a `PulseSignal<boolean>`.
15
+ * @example toggle('Speed Hack').bind(enabled)
16
+ */
17
+ interface ToggleDef {
18
+ /** Two-way bind to a boolean signal — UI changes update the signal, signal changes update UI. */
19
+ bind(this: ToggleDef, sig: PulseSignal<boolean>): ToggleDef
20
+ /** Tooltip text shown on hover. */
21
+ withTip(this: ToggleDef, text: string): ToggleDef
22
+ /** Persisted default value loaded from config on startup. */
23
+ withDefault(this: ToggleDef, v: boolean): ToggleDef
24
+ }
25
+
26
+ /**
27
+ * Slider (range input) bound to a `PulseSignal<number>`.
28
+ * @example slider('Walk Speed', { min: 16, max: 250 }).bind(speed)
29
+ */
30
+ interface SliderDef {
31
+ /** Two-way bind to a numeric signal. */
32
+ bind(this: SliderDef, sig: PulseSignal<number>): SliderDef
33
+ /** Tooltip text shown on hover. */
34
+ withTip(this: SliderDef, text: string): SliderDef
35
+ /** Persisted default value loaded from config on startup. */
36
+ withDefault(this: SliderDef, v: number): SliderDef
37
+ }
38
+
39
+ /**
40
+ * Dropdown (single-select) bound to a `PulseSignal<string>`.
41
+ * @example dropdown('Mode', { options: ['Low', 'High'] }).bind(mode)
42
+ */
43
+ interface DropdownDef {
44
+ /** Two-way bind to a string signal. */
45
+ bind(this: DropdownDef, sig: PulseSignal<string>): DropdownDef
46
+ /** Tooltip text shown on hover. */
47
+ withTip(this: DropdownDef, text: string): DropdownDef
48
+ /** Persisted default selected option. */
49
+ withDefault(this: DropdownDef, v: string): DropdownDef
50
+ /** Replace the option list at runtime (e.g. after fetching players). */
51
+ withOptions(this: DropdownDef, opts: string[]): DropdownDef
52
+ }
53
+
54
+ /**
55
+ * Multi-select dropdown bound to a `PulseSignal<string[]>`.
56
+ * @example multidropdown('Targets', { options: ['Player', 'NPC'] }).bind(targets)
57
+ */
58
+ interface MultiDropdownDef {
59
+ /** Two-way bind to a string-array signal. */
60
+ bind(this: MultiDropdownDef, sig: PulseSignal<string[]>): MultiDropdownDef
61
+ /** Tooltip text shown on hover. */
62
+ withTip(this: MultiDropdownDef, text: string): MultiDropdownDef
63
+ /** Persisted default selections. */
64
+ withDefault(this: MultiDropdownDef, v: string[]): MultiDropdownDef
65
+ /** Replace the option list at runtime. */
66
+ withOptions(this: MultiDropdownDef, opts: string[]): MultiDropdownDef
67
+ }
68
+
69
+ /**
70
+ * Button — triggers a callback when clicked.
71
+ * @example button('Reset', () => speed.set(16))
72
+ */
73
+ interface ButtonDef {
74
+ /** Register the click handler. Can be chained. */
75
+ onClick(this: ButtonDef, fn: () => void): ButtonDef
76
+ /** Satisfies `WidgetDef` — returns self. */
77
+ bind(this: ButtonDef): ButtonDef
78
+ }
79
+
80
+ /**
81
+ * Keybind — shows the current key and lets the user rebind it at runtime.
82
+ * @example keybind('Toggle UI', { key: 'RightControl' })
83
+ */
84
+ interface KeybindDef {
85
+ /** Two-way bind to a string signal tracking the current key name. */
86
+ bind(this: KeybindDef, sig: PulseSignal<string>): KeybindDef
87
+ /** Set the default key name (e.g. `'RightControl'`, `'F'`). */
88
+ withDefault(this: KeybindDef, key: string): KeybindDef
89
+ }
90
+
91
+ /**
92
+ * Static text label — non-interactive, just displays text.
93
+ * @example label('v1.3.1 — by YourName')
94
+ */
95
+ interface LabelDef {
96
+ /** Update the displayed text at runtime. */
97
+ setText(this: LabelDef, text: string): void
98
+ }
99
+
100
+ /** Horizontal divider line between widget groups. */
101
+ interface SeparatorDef {
102
+ readonly _type: 'separator'
103
+ }
104
+
105
+ /**
106
+ * Union of all renderable widget types.
107
+ * Use as the return type of the `defineComponent` setup function.
108
+ */
109
+ type WidgetDef =
110
+ | ToggleDef
111
+ | SliderDef
112
+ | DropdownDef
113
+ | MultiDropdownDef
114
+ | ButtonDef
115
+ | KeybindDef
116
+ | LabelDef
117
+ | SeparatorDef
118
+
119
+ // ── Widget factory functions ───────────────────────────────────────────────────
120
+
121
+ /**
122
+ * Create a toggle (checkbox) widget.
123
+ * Chain `.bind(signal)` to connect it to reactive state.
124
+ *
125
+ * @example
126
+ * toggle('Speed Hack').bind(enabled)
127
+ * toggle('Debug', { tip: 'Verbose logging', default: false }).bind(debug)
128
+ */
129
+ function toggle(label: string, opts?: { tip?: string; default?: boolean }): ToggleDef
130
+
131
+ /**
132
+ * Create a numeric slider widget.
133
+ *
134
+ * @example
135
+ * slider('Walk Speed', { min: 16, max: 250 }).bind(speed)
136
+ * slider('FOV', { min: 1, max: 120, suffix: '°', default: 70 }).bind(fov)
137
+ */
138
+ function slider(label: string, opts?: {
139
+ min?: number
140
+ max?: number
141
+ step?: number
142
+ default?: number
143
+ suffix?: string
144
+ tip?: string
145
+ }): SliderDef
146
+
147
+ /**
148
+ * Create a single-select dropdown widget.
149
+ *
150
+ * @example
151
+ * dropdown('Mode', { options: ['Silent', 'Normal', 'Loud'] }).bind(mode)
152
+ */
153
+ function dropdown(label: string, opts?: {
154
+ options?: string[]
155
+ default?: string
156
+ tip?: string
157
+ }): DropdownDef
158
+
159
+ /**
160
+ * Create a multi-select dropdown widget.
161
+ *
162
+ * @example
163
+ * multidropdown('Bypass', { options: ['AntiCheat', 'Logger'] }).bind(bypass)
164
+ */
165
+ function multidropdown(label: string, opts?: {
166
+ options?: string[]
167
+ default?: string[]
168
+ tip?: string
169
+ }): MultiDropdownDef
170
+
171
+ /**
172
+ * Create a button widget.
173
+ *
174
+ * @example
175
+ * button('Reset Speed', () => speed.set(16))
176
+ * button('Open Menu').onClick(() => openMenu())
177
+ */
178
+ function button(label: string, fn: () => void, opts?: { tip?: string }): ButtonDef
179
+ function button(label: string, opts?: { tip?: string }): ButtonDef
180
+
181
+ /**
182
+ * Create a user-rebindable keybind widget.
183
+ *
184
+ * @example
185
+ * keybind('Toggle', { key: 'RightControl' })
186
+ */
187
+ function keybind(label: string, opts?: { key?: string; tip?: string }): KeybindDef
188
+
189
+ /**
190
+ * Create a static text label widget.
191
+ *
192
+ * @example label('v1.3.1')
193
+ */
194
+ function label(text: string): LabelDef
195
+
196
+ /**
197
+ * Insert a horizontal separator line between widgets.
198
+ *
199
+ * @example separator()
200
+ */
201
+ function separator(): SeparatorDef
202
+
203
+ }
204
+
205
+ export type {
206
+ ToggleDef, SliderDef, DropdownDef, MultiDropdownDef,
207
+ ButtonDef, KeybindDef, LabelDef, SeparatorDef, WidgetDef,
208
+ }
package/index.ts DELETED
@@ -1,350 +0,0 @@
1
- /**
2
- * @pulse/core — Global ambient declarations for Pulse framework TypeScript scripts.
3
- *
4
- * Mental model:
5
- * - SolidJS-style reactive signals: `signal(default)` returns a PulseSignal<T>.
6
- * Call it to read, `.set()` to write, `.watch()` to subscribe — just like
7
- * SolidJS `createSignal` but without the getter/setter tuple split.
8
- * - Next.js-style file-based pages: place `src/pages/1_Home.ts`, `2_Combat.ts`…
9
- * Each file calls `definePage(...)` at the top level — tab order is determined
10
- * by the numeric filename prefix, just like Next.js routes.
11
- * - Components are SolidJS-style setup functions: call `defineComponent(name, setup)`
12
- * where `setup` declares signals, registers `on.*` handlers, and returns widgets.
13
- * - All APIs are Lua globals — no imports needed. TypeScript sees them via this
14
- * `declare global {}` block. TSTL will NOT emit any `require()` calls.
15
- *
16
- * Compiled to Lua via typescript-to-lua (TSTL): `rb build` handles this for you.
17
- *
18
- * @example
19
- * // src/combat/SpeedHack.ts
20
- * defineComponent('SpeedHack', () => {
21
- * const enabled = signal(false)
22
- * const speed = signal(16)
23
- * on.heartbeat({ when: enabled }, () => {
24
- * const h = _PulseGetHumanoid() as any
25
- * if (h) h.WalkSpeed = speed()
26
- * })
27
- * return [
28
- * toggle('Speed Hack').bind(enabled),
29
- * slider('Walk Speed', { min: 16, max: 250 }).bind(speed),
30
- * ]
31
- * })
32
- */
33
-
34
- declare global {
35
-
36
- // ── Signal ────────────────────────────────────────────────────────────────────
37
-
38
- /**
39
- * Reactive state cell returned by `signal()`.
40
- *
41
- * Call it to read the current value. Changes propagate to UI widgets,
42
- * event guards, and watchers automatically.
43
- *
44
- * Like SolidJS `createSignal` but without the getter/setter tuple split.
45
- */
46
- interface PulseSignal<T> {
47
- /** Read the current value. */
48
- (): T
49
- /** Write a new value. Triggers UI, handler, and watcher updates. */
50
- set(this: PulseSignal<T>, value: T): void
51
- /**
52
- * Toggle the signal value (boolean signals only).
53
- * Equivalent to `signal.set(!signal())`.
54
- */
55
- toggle(this: PulseSignal<boolean>): void
56
- /**
57
- * Subscribe to value changes. Returns an unsubscribe function.
58
- * Fires immediately with the current value, then on every change.
59
- */
60
- watch(this: PulseSignal<T>, fn: (value: T) => void): () => void
61
- /**
62
- * Apply a function to the current value and set the result.
63
- * @example speed.update(v => v + 10)
64
- */
65
- update(this: PulseSignal<T>, fn: (current: T) => T): void
66
- }
67
-
68
- // ── Event options ─────────────────────────────────────────────────────────────
69
-
70
- /**
71
- * Options for periodic event handlers (`on.heartbeat`, `on.renderStepped`, `on.stepped`).
72
- * Both fields are optional — omit entirely to run every frame with no guard.
73
- */
74
- interface HandlerOpts {
75
- /**
76
- * Gate: skip the callback while this signal is `false`.
77
- * The event connection stays alive but the callback is skipped.
78
- */
79
- when?: PulseSignal<boolean>
80
- /**
81
- * Throttle: minimum seconds between callback invocations.
82
- * Accepts a number or a reactive `PulseSignal<number>` for live adjustment.
83
- * @default 0 (every frame)
84
- */
85
- every?: number | PulseSignal<number>
86
- }
87
-
88
- // ── Widget definitions ────────────────────────────────────────────────────────
89
-
90
- interface ToggleDef {
91
- bind(this: ToggleDef, sig: PulseSignal<boolean>): ToggleDef
92
- withTip(this: ToggleDef, text: string): ToggleDef
93
- withDefault(this: ToggleDef, v: boolean): ToggleDef
94
- }
95
-
96
- interface SliderDef {
97
- bind(this: SliderDef, sig: PulseSignal<number>): SliderDef
98
- withTip(this: SliderDef, text: string): SliderDef
99
- withDefault(this: SliderDef, v: number): SliderDef
100
- }
101
-
102
- interface DropdownDef {
103
- bind(this: DropdownDef, sig: PulseSignal<string>): DropdownDef
104
- withTip(this: DropdownDef, text: string): DropdownDef
105
- withDefault(this: DropdownDef, v: string): DropdownDef
106
- }
107
-
108
- interface ButtonDef {
109
- onClick(this: ButtonDef, fn: () => void): ButtonDef
110
- bind(this: ButtonDef): ButtonDef
111
- }
112
-
113
- type WidgetDef = ToggleDef | SliderDef | DropdownDef | ButtonDef | {
114
- bind(this: any): any
115
- }
116
-
117
- // ── Component / Page / Layout ────────────────────────────────────────────────
118
-
119
- /** Opaque handle returned by `defineComponent`. */
120
- interface ComponentRef {
121
- readonly _name: string
122
- readonly _ui: WidgetDef[]
123
- }
124
-
125
- interface GroupboxDef {
126
- readonly type: 'groupbox'
127
- readonly side: 'left' | 'right'
128
- readonly title: string
129
- readonly icon: string
130
- readonly mount?: string
131
- }
132
-
133
- interface PageDef {
134
- readonly title: string
135
- readonly icon: string
136
- readonly layout: GroupboxDef[]
137
- }
138
-
139
- interface LayoutConfig {
140
- title: string
141
- author?: string
142
- toggleKey?: string
143
- size?: [number, number]
144
- uiLibrary?: 'windui' | 'linoria'
145
- theme?: string
146
- icon?: string
147
- folder?: string
148
- acrylic?: boolean
149
- transparency?: number
150
- openButtonMobileOnly?: boolean
151
- openButtonIcon?: string
152
- themes?: Array<{ name: string; values: Record<string, string> }>
153
- compatExclude?: string[]
154
- }
155
-
156
- // ── Core API — all are Lua globals, no require() ──────────────────────────────
157
-
158
- /**
159
- * SolidJS-style reactive state — see createSignal.
160
- *
161
- * Creates a reactive state cell with an initial value.
162
- * Compiles to `Signal(initialValue)` in Lua.
163
- *
164
- * @example
165
- * const enabled = signal(false) // PulseSignal<boolean>
166
- * const speed = signal(16) // PulseSignal<number>
167
- * enabled() // read
168
- * enabled.set(true) // write
169
- * enabled.toggle() // flip boolean
170
- */
171
- function signal<T>(initialValue: T): PulseSignal<T>
172
-
173
- /**
174
- * Create a derived value that recomputes when its dependencies change.
175
- * Compiles to `Computed(fn)` in Lua.
176
- */
177
- function computed<T>(fn: () => T): () => T
178
-
179
- /**
180
- * SolidJS-style component setup function.
181
- *
182
- * The setup function runs once on init. Declare signals, register `on.*`
183
- * handlers, then return an array of widget definitions. All connections are
184
- * auto-disconnected when the component is destroyed.
185
- *
186
- * @example
187
- * defineComponent('SpeedHack', () => {
188
- * const enabled = signal(false)
189
- * on.heartbeat({ when: enabled }, () => { ... })
190
- * return [toggle('Speed Hack').bind(enabled)]
191
- * })
192
- */
193
- function defineComponent(name: string, setup: () => WidgetDef[]): ComponentRef
194
-
195
- /**
196
- * Event subscription namespace.
197
- * All connections are automatically cleaned up when the component is destroyed.
198
- * Must be called inside a `defineComponent` setup function.
199
- */
200
- const on: {
201
- /**
202
- * RunService.Heartbeat — fires every frame.
203
- * Like SolidJS createEffect scoped to RunService.Heartbeat.
204
- *
205
- * @example
206
- * on.heartbeat({ when: enabled, every: 0.1 }, dt => { doWork() })
207
- * on.heartbeat(dt => updateDisplay(dt))
208
- */
209
- heartbeat(opts: HandlerOpts, fn: (dt: number) => void): void
210
- heartbeat(fn: (dt: number) => void): void
211
-
212
- /** RunService.RenderStepped — fires before every frame render (client only). */
213
- renderStepped(opts: HandlerOpts, fn: (dt: number) => void): void
214
- renderStepped(fn: (dt: number) => void): void
215
-
216
- /** RunService.Stepped — fires at the physics step rate. */
217
- stepped(opts: HandlerOpts, fn: (time: number, dt: number) => void): void
218
- stepped(fn: (time: number, dt: number) => void): void
219
-
220
- /** UserInputService.InputBegan. */
221
- inputBegan(fn: (input: any, gameProcessed: boolean) => void): void
222
-
223
- /** UserInputService.InputEnded. */
224
- inputEnded(fn: (input: any, gameProcessed: boolean) => void): void
225
-
226
- /** Fires when the player's character respawns. */
227
- respawn(fn: () => void): void
228
-
229
- /** Player.CharacterAdded — fires when a new character model is added. */
230
- characterAdded(fn: (character: any) => void): void
231
-
232
- /** Player.CharacterRemoving — fires just before the character is removed. */
233
- characterRemoving(fn: (character: any) => void): void
234
-
235
- /**
236
- * Subscribe to changes on a signal.
237
- * Fires immediately with the current value, then on every change.
238
- */
239
- signal<T>(sig: PulseSignal<T>, fn: (val: T) => void): void
240
-
241
- /**
242
- * Run a one-shot callback after a delay (seconds).
243
- * @example on.after(0.5, () => init())
244
- */
245
- after(seconds: number, fn: () => void): void
246
- }
247
-
248
- // ── Widget factory globals ─────────────────────────────────────────────────────
249
-
250
- /** Toggle widget — checkbox bound to a `PulseSignal<boolean>`. */
251
- function toggle(label: string, opts?: { tip?: string; default?: boolean }): ToggleDef
252
-
253
- /** Slider widget — numeric range input. */
254
- function slider(label: string, opts?: {
255
- min?: number; max?: number; step?: number; default?: number;
256
- suffix?: string; tip?: string
257
- }): SliderDef
258
-
259
- /** Dropdown widget — selectable list of string options. */
260
- function dropdown(label: string, opts?: {
261
- options?: string[]; default?: string; tip?: string
262
- }): DropdownDef
263
-
264
- /** Multi-select dropdown widget. */
265
- function multidropdown(label: string, opts?: {
266
- options?: string[]; default?: string[]; tip?: string
267
- }): DropdownDef
268
-
269
- /** Button widget — triggers a callback when clicked. */
270
- function button(label: string, fn: () => void, opts?: { tip?: string }): ButtonDef
271
- function button(label: string, opts?: { tip?: string }): ButtonDef
272
-
273
- /** Keybind widget — lets the user rebind a hotkey. */
274
- function keybind(label: string, opts?: { key?: string; tip?: string }): WidgetDef
275
-
276
- /** Label widget — static non-interactive text. */
277
- function label(text: string): WidgetDef
278
-
279
- /** Separator widget — horizontal divider line. */
280
- function separator(): WidgetDef
281
-
282
- /**
283
- * Declare a groupbox column in a page.
284
- * @param side 'left' or 'right' column
285
- * @param title Groupbox header text
286
- * @param opts icon, mount (component name string), widgets
287
- */
288
- function groupbox(side: 'left' | 'right', title: string, opts?: {
289
- icon?: string; mount?: string; widgets?: WidgetDef[]
290
- }): GroupboxDef
291
-
292
- /**
293
- * Next.js-style file-based page tab — place in src/pages/N_Name.ts.
294
- *
295
- * Each file calls `definePage` at the top level. Tab order is determined
296
- * by the numeric prefix of the filename (1_Home.ts, 2_Combat.ts, …).
297
- *
298
- * @example
299
- * // src/pages/1_Home.ts
300
- * definePage('Home', { icon: 'house' }, () => [
301
- * groupbox('left', 'Player', { icon: 'person', mount: 'SpeedHack' }),
302
- * ])
303
- */
304
- function definePage(
305
- title: string,
306
- opts: { icon?: string },
307
- factory: () => GroupboxDef[],
308
- ): PageDef
309
-
310
- // ── Roblox globals (minimal stubs) ────────────────────────────────────────────
311
-
312
- const game: {
313
- GetService(name: string): any
314
- HttpGet(url: string): string
315
- }
316
-
317
- const workspace: {
318
- CurrentCamera: any
319
- [key: string]: any
320
- }
321
-
322
- const Enum: Record<string, Record<string, any>>
323
-
324
- const Color3: {
325
- new(r: number, g: number, b: number): any
326
- fromRGB(r: number, g: number, b: number): any
327
- }
328
-
329
- const Vector3: {
330
- new(x: number, y: number, z: number): any
331
- }
332
-
333
- const CFrame: {
334
- new(x: number, y: number, z: number): any
335
- }
336
-
337
- // ── Pulse character helpers ───────────────────────────────────────────────────
338
-
339
- /** Returns `LocalPlayer.Character` or nil. */
340
- function _PulseGetChar(): any
341
- /** Returns `HumanoidRootPart` from the character, or nil. */
342
- function _PulseGetHRP(): any
343
- /** Returns `Humanoid` from the character, or nil. */
344
- function _PulseGetHumanoid(): any
345
- /** Returns true if the local player's humanoid is alive. */
346
- function _PulseGetAlive(): boolean
347
-
348
- }
349
-
350
- export {}