alepha 0.20.2 → 0.20.4
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/README.md +0 -1
- package/assets/swagger-ui/swagger-ui-bundle.js +1 -1
- package/assets/swagger-ui/swagger-ui.css +1 -1
- package/dist/api/audits/index.browser.js +49 -0
- package/dist/api/audits/index.browser.js.map +1 -1
- package/dist/api/audits/index.js +49 -0
- package/dist/api/audits/index.js.map +1 -1
- package/dist/api/files/index.js.map +1 -1
- package/dist/api/jobs/index.d.ts +2 -61
- package/dist/api/jobs/index.d.ts.map +1 -1
- package/dist/api/jobs/index.js.map +1 -1
- package/dist/api/keys/index.d.ts +4 -4
- package/dist/api/keys/index.js.map +1 -1
- package/dist/api/notifications/index.d.ts +1 -10
- package/dist/api/notifications/index.d.ts.map +1 -1
- package/dist/api/parameters/index.browser.js +37 -0
- package/dist/api/parameters/index.browser.js.map +1 -1
- package/dist/api/parameters/index.d.ts +12 -68
- package/dist/api/parameters/index.d.ts.map +1 -1
- package/dist/api/parameters/index.js +57 -4
- package/dist/api/parameters/index.js.map +1 -1
- package/dist/api/payments/index.js.map +1 -1
- package/dist/api/users/index.browser.js +6 -0
- package/dist/api/users/index.browser.js.map +1 -1
- package/dist/api/users/index.d.ts +148 -227
- package/dist/api/users/index.d.ts.map +1 -1
- package/dist/api/users/index.js +60 -14
- package/dist/api/users/index.js.map +1 -1
- package/dist/api/verifications/index.d.ts.map +1 -1
- package/dist/api/verifications/index.js +2 -1
- package/dist/api/verifications/index.js.map +1 -1
- package/dist/bucket/index.d.ts +77 -107
- package/dist/bucket/index.d.ts.map +1 -1
- package/dist/bucket/index.js +153 -5
- package/dist/bucket/index.js.map +1 -1
- package/dist/bucket/index.workerd.js +12 -2
- package/dist/bucket/index.workerd.js.map +1 -1
- package/dist/cache/core/index.d.ts +26 -0
- package/dist/cache/core/index.d.ts.map +1 -1
- package/dist/cache/core/index.js +11 -1
- package/dist/cache/core/index.js.map +1 -1
- package/dist/cache/core/index.workerd.js +11 -1
- package/dist/cache/core/index.workerd.js.map +1 -1
- package/dist/captcha/index.js.map +1 -1
- package/dist/cli/config/index.d.ts +7 -5
- package/dist/cli/config/index.d.ts.map +1 -1
- package/dist/cli/config/index.js +2 -3
- package/dist/cli/config/index.js.map +1 -1
- package/dist/cli/core/index.d.ts +637 -11660
- package/dist/cli/core/index.d.ts.map +1 -1
- package/dist/cli/core/index.js +707 -532
- package/dist/cli/core/index.js.map +1 -1
- package/dist/cli/devtools/index.d.ts +4 -8
- package/dist/cli/devtools/index.d.ts.map +1 -1
- package/dist/cli/devtools/index.js +20 -16
- package/dist/cli/devtools/index.js.map +1 -1
- package/dist/cli/platform/index.d.ts +51 -77
- package/dist/cli/platform/index.d.ts.map +1 -1
- package/dist/cli/platform/index.js +65 -15
- package/dist/cli/platform/index.js.map +1 -1
- package/dist/cli/vendor/index.d.ts +10 -13
- package/dist/cli/vendor/index.d.ts.map +1 -1
- package/dist/cli/vendor/index.js +30 -12
- package/dist/cli/vendor/index.js.map +1 -1
- package/dist/command/index.js +1 -1
- package/dist/command/index.js.map +1 -1
- package/dist/core/index.browser.js +27 -3
- package/dist/core/index.browser.js.map +1 -1
- package/dist/core/index.d.ts +8 -11
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +27 -3
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.native.js +27 -3
- package/dist/core/index.native.js.map +1 -1
- package/dist/core/index.workerd.js +27 -3
- package/dist/core/index.workerd.js.map +1 -1
- package/dist/crypto/index.js.map +1 -1
- package/dist/datetime/index.d.ts +69 -10
- package/dist/datetime/index.d.ts.map +1 -1
- package/dist/datetime/index.js +135 -13
- package/dist/datetime/index.js.map +1 -1
- package/dist/email/core/index.js.map +1 -1
- package/dist/email/smtp/index.js +130 -16
- package/dist/email/smtp/index.js.map +1 -1
- package/dist/fake/index.js.map +1 -1
- package/dist/lock/core/index.d.ts +30 -2
- package/dist/lock/core/index.d.ts.map +1 -1
- package/dist/lock/core/index.js +35 -12
- package/dist/lock/core/index.js.map +1 -1
- package/dist/lock/redis/index.js.map +1 -1
- package/dist/logger/index.js +32 -1
- package/dist/logger/index.js.map +1 -1
- package/dist/mcp/index.d.ts +238 -31
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +198 -67
- package/dist/mcp/index.js.map +1 -1
- package/dist/orm/core/index.browser.js +2 -362
- package/dist/orm/core/index.browser.js.map +1 -1
- package/dist/orm/core/index.bun.js +18 -409
- package/dist/orm/core/index.bun.js.map +1 -1
- package/dist/orm/core/index.d.ts +41 -194
- package/dist/orm/core/index.d.ts.map +1 -1
- package/dist/orm/core/index.js +27 -422
- package/dist/orm/core/index.js.map +1 -1
- package/dist/orm/postgres/index.bun.js +17 -20
- package/dist/orm/postgres/index.bun.js.map +1 -1
- package/dist/orm/postgres/index.d.ts +1 -5
- package/dist/orm/postgres/index.d.ts.map +1 -1
- package/dist/orm/postgres/index.js +17 -20
- package/dist/orm/postgres/index.js.map +1 -1
- package/dist/react/core/index.d.ts +102 -1
- package/dist/react/core/index.d.ts.map +1 -1
- package/dist/react/core/index.js +65 -1
- package/dist/react/core/index.js.map +1 -1
- package/dist/react/form/index.d.ts +6 -0
- package/dist/react/form/index.d.ts.map +1 -1
- package/dist/react/form/index.js +7 -7
- package/dist/react/form/index.js.map +1 -1
- package/dist/react/i18n/index.d.ts +7 -1
- package/dist/react/i18n/index.d.ts.map +1 -1
- package/dist/react/i18n/index.js +6 -0
- package/dist/react/i18n/index.js.map +1 -1
- package/dist/react/intro/index.js +22 -17
- package/dist/react/intro/index.js.map +1 -1
- package/dist/react/router/index.browser.js +98 -4
- package/dist/react/router/index.browser.js.map +1 -1
- package/dist/react/router/index.d.ts +58 -5
- package/dist/react/router/index.d.ts.map +1 -1
- package/dist/react/router/index.js +122 -6
- package/dist/react/router/index.js.map +1 -1
- package/dist/react/testing/{chunk-DBEY4PJZ.js → chunk-6Ep1yQYe.js} +1 -1
- package/dist/react/testing/index.js +1 -1
- package/dist/react/testing/index.js.map +1 -1
- package/dist/react/ui/index.d.ts +195 -1
- package/dist/react/ui/index.d.ts.map +1 -1
- package/dist/react/ui/index.js +64 -1
- package/dist/react/ui/index.js.map +1 -1
- package/dist/react/websocket/index.js.map +1 -1
- package/dist/redis/index.js.map +1 -1
- package/dist/scheduler/index.d.ts +1 -2
- package/dist/scheduler/index.d.ts.map +1 -1
- package/dist/scheduler/index.js +1 -1
- package/dist/scheduler/index.js.map +1 -1
- package/dist/scheduler/index.workerd.js +1 -1
- package/dist/scheduler/index.workerd.js.map +1 -1
- package/dist/security/index.browser.js.map +1 -1
- package/dist/security/index.d.ts.map +1 -1
- package/dist/security/index.js +2 -2
- package/dist/security/index.js.map +1 -1
- package/dist/server/auth/index.d.ts.map +1 -1
- package/dist/server/auth/index.js +24 -10
- package/dist/server/auth/index.js.map +1 -1
- package/dist/server/cookies/index.js.map +1 -1
- package/dist/server/core/index.browser.js +10 -3
- package/dist/server/core/index.browser.js.map +1 -1
- package/dist/server/core/index.d.ts +1 -4
- package/dist/server/core/index.d.ts.map +1 -1
- package/dist/server/core/index.js +47 -9
- package/dist/server/core/index.js.map +1 -1
- package/dist/server/links/index.browser.js.map +1 -1
- package/dist/server/links/index.js.map +1 -1
- package/dist/server/metrics/index.js +19 -1
- package/dist/server/metrics/index.js.map +1 -1
- package/dist/server/rate-limit/index.js.map +1 -1
- package/dist/server/static/index.js.map +1 -1
- package/dist/server/swagger/index.d.ts.map +1 -1
- package/dist/server/swagger/index.js +4 -5
- package/dist/server/swagger/index.js.map +1 -1
- package/dist/sms/index.js.map +1 -1
- package/dist/system/index.browser.js.map +1 -1
- package/dist/system/index.js.map +1 -1
- package/dist/system/index.workerd.js.map +1 -1
- package/dist/topic/core/index.js.map +1 -1
- package/dist/websocket/index.browser.js +32 -5
- package/dist/websocket/index.browser.js.map +1 -1
- package/dist/websocket/index.d.ts +3 -1
- package/dist/websocket/index.d.ts.map +1 -1
- package/dist/websocket/index.js +42 -6
- package/dist/websocket/index.js.map +1 -1
- package/package.json +685 -274
- package/src/api/files/__tests__/FileController.spec.ts +1 -1
- package/src/api/jobs/__tests__/$job.spec.ts +5 -1
- package/src/api/parameters/services/ParameterProvider.ts +21 -4
- package/src/api/users/__tests__/SessionService.spec.ts +99 -0
- package/src/api/users/__tests__/UserJobs.spec.ts +67 -0
- package/src/api/users/atoms/realmAuthSettingsAtom.ts +15 -0
- package/src/api/users/entities/sessions.ts +6 -0
- package/src/api/users/jobs/UserJobs.ts +44 -17
- package/src/api/users/providers/RealmProvider.ts +4 -0
- package/src/api/users/schemas/userQuerySchema.ts +0 -1
- package/src/api/users/services/SessionService.ts +27 -0
- package/src/api/users/services/UserService.ts +1 -5
- package/src/api/verifications/__tests__/CodeVerification.spec.ts +14 -0
- package/src/api/verifications/__tests__/LinkVerification.spec.ts +14 -0
- package/src/api/verifications/services/VerificationService.ts +1 -0
- package/src/bucket/__tests__/NodeS3BucketProvider.spec.ts +74 -0
- package/src/bucket/index.ts +19 -2
- package/src/bucket/primitives/$bucket.ts +9 -1
- package/src/bucket/providers/CloudflareR2Provider.ts +2 -137
- package/src/bucket/providers/NodeS3BucketProvider.ts +218 -0
- package/src/cache/core/index.ts +29 -0
- package/src/cache/core/primitives/$cache.ts +14 -1
- package/src/cli/config/defineConfig.ts +13 -15
- package/src/cli/core/__tests__/init.spec.ts +214 -7
- package/src/cli/core/commands/init.ts +12 -0
- package/src/cli/core/services/PackageManagerUtils.ts +23 -6
- package/src/cli/core/services/ProjectScaffolder.ts +315 -33
- package/src/cli/core/tasks/BuildCloudflareTask.ts +5 -0
- package/src/cli/core/tasks/BuildDockerTask.ts +9 -10
- package/src/cli/core/tasks/BuildServerTask.ts +8 -0
- package/src/cli/core/templates/agentMd.ts +2 -10
- package/src/cli/core/templates/apiIndexTs.ts +23 -1
- package/src/cli/core/templates/componentsJsonTs.ts +39 -0
- package/src/cli/core/templates/mainCss.ts +1 -0
- package/src/cli/core/templates/saasAdminLayoutTsx.ts +77 -0
- package/src/cli/core/templates/saasAdminPagesTsx.ts +26 -0
- package/src/cli/core/templates/saasAuthLayoutTsx.ts +20 -0
- package/src/cli/core/templates/saasAuthPagesTsx.ts +62 -0
- package/src/cli/core/templates/saasRealmProviderTs.ts +46 -0
- package/src/cli/core/templates/webAppRouterTs.ts +104 -1
- package/src/cli/core/templates/webIndexTs.ts +23 -1
- package/src/cli/devtools/index.ts +12 -26
- package/src/cli/platform/__tests__/SecretsCommand.spec.ts +2 -0
- package/src/cli/platform/index.ts +15 -24
- package/src/cli/vendor/atoms/vendorOptions.ts +1 -1
- package/src/cli/vendor/index.ts +14 -23
- package/src/command/providers/CliProvider.ts +1 -1
- package/src/core/Alepha.ts +11 -1
- package/src/core/helpers/ref.ts +18 -0
- package/src/core/index.shared.ts +1 -0
- package/src/core/interfaces/Service.ts +3 -1
- package/src/core/providers/SchemaValidator.ts +9 -1
- package/src/core/providers/TypeProvider.ts +2 -3
- package/src/datetime/REFACTORING.md +118 -0
- package/src/datetime/providers/DateTimeProvider.ts +203 -24
- package/src/lock/core/index.ts +31 -0
- package/src/lock/core/primitives/$lock.ts +14 -1
- package/src/logger/services/Logger.ts +1 -1
- package/src/mcp/__tests__/$resource.spec.ts +1 -1
- package/src/mcp/__tests__/$tool.spec.ts +1 -1
- package/src/mcp/__tests__/McpServerProvider.spec.ts +1 -1
- package/src/mcp/__tests__/jsonrpc.spec.ts +1 -1
- package/src/mcp/helpers/jsonrpc.ts +26 -1
- package/src/mcp/index.ts +10 -5
- package/src/mcp/interfaces/McpTypes.ts +83 -6
- package/src/mcp/primitives/$prompt.ts +18 -1
- package/src/mcp/primitives/$resource.ts +18 -1
- package/src/mcp/primitives/$tool.ts +83 -7
- package/src/mcp/providers/McpServerProvider.ts +74 -16
- package/src/mcp/transports/StreamableHttpMcpTransport.ts +226 -0
- package/src/orm/REFACTORING.md +330 -0
- package/src/orm/__tests__/$repository-tests.ts +1 -0
- package/src/orm/__tests__/orm-next-tests.ts +2 -67
- package/src/orm/__tests__/orm-next.spec.ts +0 -21
- package/src/orm/core/index.shared.ts +0 -2
- package/src/orm/core/index.ts +1 -2
- package/src/orm/core/primitives/$repository.ts +3 -6
- package/src/orm/core/primitives/$transactional.ts +11 -0
- package/src/orm/core/providers/drivers/DatabaseProvider.ts +0 -5
- package/src/orm/core/providers/drivers/NodeSqliteProvider.ts +11 -13
- package/src/orm/core/schemas/updateSchema.ts +1 -1
- package/src/orm/core/services/ModelBuilder.ts +1 -13
- package/src/orm/core/services/PgRelationManager.ts +4 -2
- package/src/orm/core/services/Repository.ts +1 -42
- package/src/orm/core/services/SqliteModelBuilder.ts +2 -33
- package/src/orm/postgres/services/PostgresModelBuilder.ts +10 -45
- package/src/react/core/__tests__/useQuery.browser.spec.tsx +86 -0
- package/src/react/core/hooks/useQuery.ts +153 -0
- package/src/react/core/index.ts +1 -0
- package/src/react/form/services/FormModel.ts +15 -6
- package/src/react/form/services/parseField.ts +8 -0
- package/src/react/i18n/providers/I18nProvider.ts +8 -2
- package/src/react/intro/components/GettingStartedAuthSlide.tsx +11 -4
- package/src/react/router/__tests__/$page.spec.tsx +0 -16
- package/src/react/router/__tests__/ReactBrowserProvider.browser.spec.ts +213 -2
- package/src/react/router/__tests__/ssr.spec.tsx +339 -0
- package/src/react/router/primitives/$page.ts +28 -4
- package/src/react/router/providers/ReactBrowserProvider.ts +73 -0
- package/src/react/router/providers/ReactBrowserRouterProvider.ts +1 -1
- package/src/react/router/providers/ReactPageProvider.ts +27 -9
- package/src/react/router/providers/ReactPreloadProvider.ts +1 -1
- package/src/react/router/providers/ReactServerProvider.ts +1 -0
- package/src/react/ui/atoms/uiThemeListAtom.ts +36 -0
- package/src/react/ui/index.ts +6 -0
- package/src/react/ui/services/SchemaControl.ts +209 -0
- package/src/scheduler/providers/CronProvider.ts +1 -1
- package/src/security/primitives/$basicAuth.ts +1 -1
- package/src/security/primitives/$issuer.ts +6 -3
- package/src/server/auth/providers/ServerAuthProvider.ts +5 -1
- package/src/server/core/__tests__/ServerRouterProvider-serializationError.spec.ts +75 -0
- package/src/server/core/__tests__/ServerRouterProvider-validationError.spec.ts +306 -0
- package/src/server/core/errors/ValidationError.ts +13 -1
- package/src/server/core/interfaces/ServerRequest.ts +1 -0
- package/src/server/core/primitives/$action.ts +16 -5
- package/src/server/core/providers/ServerProvider.ts +1 -1
- package/src/server/core/providers/ServerRouterProvider.ts +28 -6
- package/src/server/core/services/HttpClient.ts +1 -1
- package/src/server/swagger/providers/ServerSwaggerProvider.ts +6 -8
- package/src/websocket/providers/NodeWebSocketServerProvider.ts +10 -4
- package/src/websocket/services/WebSocketClient.ts +11 -5
- package/src/mcp/transports/SseMcpTransport.ts +0 -182
- package/src/orm/core/__tests__/parseQueryString.spec.ts +0 -196
- package/src/orm/core/helpers/parseQueryString.ts +0 -502
- package/src/orm/core/primitives/$view.ts +0 -88
package/dist/react/ui/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import * as _$alepha from "alepha";
|
|
|
2
2
|
import { Static } from "alepha";
|
|
3
3
|
import * as _$alepha_react_head0 from "alepha/react/head";
|
|
4
4
|
import * as _$alepha_server_cookies0 from "alepha/server/cookies";
|
|
5
|
+
import { FormModel } from "alepha/react/form";
|
|
5
6
|
import * as _$typebox from "typebox";
|
|
6
7
|
|
|
7
8
|
//#region ../../src/react/ui/atoms/uiAtom.d.ts
|
|
@@ -20,6 +21,33 @@ declare const uiAtom: _$alepha.Atom<_$alepha.TObject<{
|
|
|
20
21
|
}>, "alepha.react.ui">;
|
|
21
22
|
type UiState = Static<typeof uiAtom.schema>;
|
|
22
23
|
//#endregion
|
|
24
|
+
//#region ../../src/react/ui/atoms/uiThemeListAtom.d.ts
|
|
25
|
+
/**
|
|
26
|
+
* Available themes the user can pick from. Apps populate this atom on boot
|
|
27
|
+
* (e.g. `alepha.store.set(uiThemeListAtom, MY_THEMES)`); UI consumers like
|
|
28
|
+
* `<ButtonTheme/>` read it to render a picker. The selected theme id is
|
|
29
|
+
* persisted separately in `uiAtom.theme`.
|
|
30
|
+
*
|
|
31
|
+
* Defaults to a single `"default"` entry so the registry stays usable when
|
|
32
|
+
* an app doesn't declare its own list.
|
|
33
|
+
*/
|
|
34
|
+
declare const uiThemeListAtom: _$alepha.Atom<_$alepha.TArray<_$alepha.TObject<{
|
|
35
|
+
/** Stable id stored in `uiAtom.theme`. Mapped to a CSS class on `<html>`. */id: _$alepha.TString; /** Human-readable label shown in the picker. */
|
|
36
|
+
label: _$alepha.TString;
|
|
37
|
+
/**
|
|
38
|
+
* Optional 4-color preview swatch in 2×2 order (TL, TR, BL, BR). Any
|
|
39
|
+
* CSS-valid color string.
|
|
40
|
+
*/
|
|
41
|
+
swatch: _$alepha.TOptional<_$alepha.TArray<_$alepha.TString>>;
|
|
42
|
+
/**
|
|
43
|
+
* Optional stylesheet URL (typically Google Fonts) loaded lazily when
|
|
44
|
+
* the theme is selected.
|
|
45
|
+
*/
|
|
46
|
+
fontHref: _$alepha.TOptional<_$alepha.TString>;
|
|
47
|
+
}>>, "alepha.react.ui.themes">;
|
|
48
|
+
type UiThemeList = Static<typeof uiThemeListAtom.schema>;
|
|
49
|
+
type UiTheme = UiThemeList[number];
|
|
50
|
+
//#endregion
|
|
23
51
|
//#region ../../src/react/ui/components/ColorScheme.d.ts
|
|
24
52
|
/**
|
|
25
53
|
* Applies `class="dark"` and an optional theme palette class
|
|
@@ -80,6 +108,171 @@ declare const useTheme: () => {
|
|
|
80
108
|
setTheme: (next: string) => void;
|
|
81
109
|
};
|
|
82
110
|
//#endregion
|
|
111
|
+
//#region ../../src/react/ui/services/SchemaControl.d.ts
|
|
112
|
+
/**
|
|
113
|
+
* Schema-bound metadata read by `<Control>` (in `@alepha/ui-registry`) to
|
|
114
|
+
* configure how a field renders. Place under `$control` on any TypeBox
|
|
115
|
+
* schema option.
|
|
116
|
+
*
|
|
117
|
+
* Two forms:
|
|
118
|
+
*
|
|
119
|
+
* 1. **Object** — static configuration baked into the schema:
|
|
120
|
+
* ```ts
|
|
121
|
+
* t.string({ $control: { password: true, icon: "key" } })
|
|
122
|
+
* ```
|
|
123
|
+
*
|
|
124
|
+
* 2. **Function** — dynamic, computed from current form state:
|
|
125
|
+
* ```ts
|
|
126
|
+
* t.string({
|
|
127
|
+
* $control: ({ form, value }) => {
|
|
128
|
+
* if (form.currentValues.kind !== "advanced") return false; // hide
|
|
129
|
+
* return { items: () => fetchOptions(form.currentValues.kind) };
|
|
130
|
+
* },
|
|
131
|
+
* })
|
|
132
|
+
* ```
|
|
133
|
+
*
|
|
134
|
+
* The function may return:
|
|
135
|
+
* - a partial `SchemaControl` to merge with explicit `<Control>` props
|
|
136
|
+
* - `false` to hide the control entirely
|
|
137
|
+
* - `undefined` to leave the field as-is
|
|
138
|
+
*/
|
|
139
|
+
interface SchemaControl {
|
|
140
|
+
text?: boolean;
|
|
141
|
+
area?: boolean;
|
|
142
|
+
password?: boolean;
|
|
143
|
+
switch?: boolean;
|
|
144
|
+
number?: boolean;
|
|
145
|
+
file?: boolean;
|
|
146
|
+
date?: boolean;
|
|
147
|
+
datetime?: boolean;
|
|
148
|
+
time?: boolean;
|
|
149
|
+
select?: boolean;
|
|
150
|
+
combobox?: boolean;
|
|
151
|
+
segmented?: boolean;
|
|
152
|
+
slider?: boolean;
|
|
153
|
+
object?: boolean;
|
|
154
|
+
array?: boolean;
|
|
155
|
+
/**
|
|
156
|
+
* Icon name. The registry control maps this to its icon set
|
|
157
|
+
* (lucide-react). Pass `null` to suppress the schema-inferred icon.
|
|
158
|
+
*/
|
|
159
|
+
icon?: string | null;
|
|
160
|
+
label?: string;
|
|
161
|
+
description?: string;
|
|
162
|
+
placeholder?: string;
|
|
163
|
+
/**
|
|
164
|
+
* HTML `autocomplete` attribute. Use standard tokens like
|
|
165
|
+
* `"username"`, `"email"`, `"new-password"`, `"current-password"`,
|
|
166
|
+
* `"street-address"`, `"address-line1"`, `"address-level2"` (city),
|
|
167
|
+
* `"postal-code"`, `"country"`, `"cc-number"`, `"cc-exp"`,
|
|
168
|
+
* `"cc-csc"`, `"cc-name"`, `"tel"`, etc.
|
|
169
|
+
*/
|
|
170
|
+
autoComplete?: string;
|
|
171
|
+
/**
|
|
172
|
+
* Static or async option list for select / combobox / multi-select.
|
|
173
|
+
* Each item is either a bare string (used as both value & label) or a
|
|
174
|
+
* `{ value, label, description?, tag? }` object.
|
|
175
|
+
*/
|
|
176
|
+
items?: Array<string | SchemaControlItem> | SchemaControlItemsFn;
|
|
177
|
+
/**
|
|
178
|
+
* Re-fetch `items` (when async) whenever any of these reference values
|
|
179
|
+
* change. Useful for cascading selects.
|
|
180
|
+
*/
|
|
181
|
+
itemsWatch?: unknown[];
|
|
182
|
+
/**
|
|
183
|
+
* Allow the user to create a new option by typing into a select /
|
|
184
|
+
* multi-select. Pass `true` for `{ value: query, label: query }`, or a
|
|
185
|
+
* function returning a custom option built from the query.
|
|
186
|
+
*/
|
|
187
|
+
createNewEntry?: boolean | ((query: string) => {
|
|
188
|
+
value: string;
|
|
189
|
+
label: string;
|
|
190
|
+
});
|
|
191
|
+
/**
|
|
192
|
+
* Width slot inside an `<AutoForm>` group. Mapped to a grid column span.
|
|
193
|
+
* - `100` → full row
|
|
194
|
+
* - `75` → 3/4 row
|
|
195
|
+
* - `66` → 2/3 row
|
|
196
|
+
* - `50` → half
|
|
197
|
+
* - `33` → one third (default for plain primitives)
|
|
198
|
+
* - `25` → one quarter
|
|
199
|
+
*/
|
|
200
|
+
width?: 100 | 75 | 66 | 50 | 33 | 25;
|
|
201
|
+
/**
|
|
202
|
+
* Render `null` (hide) when truthy. Equivalent to a function `$control`
|
|
203
|
+
* returning `false`, but available as a static value.
|
|
204
|
+
*/
|
|
205
|
+
hidden?: boolean;
|
|
206
|
+
disabled?: boolean;
|
|
207
|
+
readOnly?: boolean;
|
|
208
|
+
/**
|
|
209
|
+
* Render before/after the field. Both receive the resolved input.
|
|
210
|
+
* Typed loosely — UI layer narrows to `ReactNode`.
|
|
211
|
+
*/
|
|
212
|
+
top?: unknown;
|
|
213
|
+
bottom?: unknown;
|
|
214
|
+
/**
|
|
215
|
+
* Render a managed upload control (image preview, multi, drag-drop)
|
|
216
|
+
* that posts to the file API and stores the resulting file ID(s) in
|
|
217
|
+
* the form. Pass `true` for defaults or an options object:
|
|
218
|
+
*
|
|
219
|
+
* ```ts
|
|
220
|
+
* $control: { upload: { multi: true, accept: "image/*", maxSize: 5_000_000 } }
|
|
221
|
+
* ```
|
|
222
|
+
*/
|
|
223
|
+
upload?: boolean | {
|
|
224
|
+
multi?: boolean;
|
|
225
|
+
accept?: string;
|
|
226
|
+
maxSize?: number;
|
|
227
|
+
bucket?: string;
|
|
228
|
+
};
|
|
229
|
+
arrayProps?: {
|
|
230
|
+
confirmDelete?: boolean | {
|
|
231
|
+
title?: string;
|
|
232
|
+
message?: string;
|
|
233
|
+
}; /** Computed label for each tab when an array uses tabs mode. */
|
|
234
|
+
renderTabName?: (i: number, value: unknown) => string;
|
|
235
|
+
sortable?: boolean;
|
|
236
|
+
collapsible?: boolean; /** Force grouped (CreateForm-style) tabs even for short arrays. */
|
|
237
|
+
forceTabs?: boolean;
|
|
238
|
+
};
|
|
239
|
+
[key: string]: unknown;
|
|
240
|
+
}
|
|
241
|
+
interface SchemaControlItem {
|
|
242
|
+
value: string | number | boolean;
|
|
243
|
+
label: string;
|
|
244
|
+
description?: string;
|
|
245
|
+
tag?: string;
|
|
246
|
+
}
|
|
247
|
+
type SchemaControlItemsFn = (query: string) => Array<string | SchemaControlItem> | Promise<Array<string | SchemaControlItem>>;
|
|
248
|
+
/**
|
|
249
|
+
* Function form of `$control`. Receives the live form model + the current
|
|
250
|
+
* field value, and returns a partial config (merged with explicit props),
|
|
251
|
+
* `false` to hide, or `undefined` to leave as-is.
|
|
252
|
+
*/
|
|
253
|
+
type SchemaControlFn = (context: {
|
|
254
|
+
form: FormModel<any>;
|
|
255
|
+
value: unknown;
|
|
256
|
+
}) => Partial<SchemaControl> | false | undefined;
|
|
257
|
+
type SchemaControlOption = SchemaControl | SchemaControlFn;
|
|
258
|
+
/**
|
|
259
|
+
* Resolve a raw `$control` value (object or function) into a concrete
|
|
260
|
+
* partial config. Returns `null` when the field should be hidden.
|
|
261
|
+
*/
|
|
262
|
+
declare const resolveSchemaControl: (raw: unknown, context: {
|
|
263
|
+
form: FormModel<any>;
|
|
264
|
+
value: unknown;
|
|
265
|
+
}) => Partial<SchemaControl> | null;
|
|
266
|
+
declare module "typebox" {
|
|
267
|
+
interface TSchemaOptions {
|
|
268
|
+
/**
|
|
269
|
+
* UI metadata read by `<Control>` from `@alepha/ui-registry`. See
|
|
270
|
+
* {@link SchemaControl}.
|
|
271
|
+
*/
|
|
272
|
+
$control?: SchemaControlOption;
|
|
273
|
+
}
|
|
274
|
+
} //# sourceMappingURL=SchemaControl.d.ts.map
|
|
275
|
+
//#endregion
|
|
83
276
|
//#region ../../src/react/ui/services/UiPersistence.d.ts
|
|
84
277
|
/**
|
|
85
278
|
* Binds the {@link uiAtom} to an `alepha-ui` cookie + injects an inline
|
|
@@ -108,6 +301,7 @@ declare class UiPersistence {
|
|
|
108
301
|
declare module "alepha" {
|
|
109
302
|
interface State {
|
|
110
303
|
"alepha.react.ui": UiState;
|
|
304
|
+
"alepha.react.ui.themes": UiThemeList;
|
|
111
305
|
}
|
|
112
306
|
}
|
|
113
307
|
/**
|
|
@@ -120,5 +314,5 @@ declare module "alepha" {
|
|
|
120
314
|
*/
|
|
121
315
|
declare const AlephaReactUi: _$alepha.Service<_$alepha.Module>;
|
|
122
316
|
//#endregion
|
|
123
|
-
export { AlephaReactUi, ColorMode, ColorScheme, ResolvedColorMode, UiPersistence, UiState, uiAtom, useColorMode, useSidebarState, useTheme };
|
|
317
|
+
export { AlephaReactUi, ColorMode, ColorScheme, ResolvedColorMode, SchemaControl, SchemaControlFn, SchemaControlItem, SchemaControlItemsFn, SchemaControlOption, UiPersistence, UiState, UiTheme, UiThemeList, resolveSchemaControl, uiAtom, uiThemeListAtom, useColorMode, useSidebarState, useTheme };
|
|
124
318
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/react/ui/atoms/uiAtom.ts","../../../src/react/ui/components/ColorScheme.tsx","../../../src/react/ui/hooks/useColorMode.ts","../../../src/react/ui/hooks/useSidebarState.ts","../../../src/react/ui/hooks/useTheme.ts","../../../src/react/ui/services/UiPersistence.ts","../../../src/react/ui/index.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/react/ui/atoms/uiAtom.ts","../../../src/react/ui/atoms/uiThemeListAtom.ts","../../../src/react/ui/components/ColorScheme.tsx","../../../src/react/ui/hooks/useColorMode.ts","../../../src/react/ui/hooks/useSidebarState.ts","../../../src/react/ui/hooks/useTheme.ts","../../../src/react/ui/services/SchemaControl.ts","../../../src/react/ui/services/UiPersistence.ts","../../../src/react/ui/index.ts"],"mappings":";;;;;;;;;;;;;;cAQa,MAAA,EAAM,QAAA,CAAA,IAAA,UAAA,OAAA;EAAN,4EAiBX,QAAA,CAAA,OAAA;;;;;;KAEU,OAAA,GAAU,MAAA,QAAc,MAAA,CAAO,MAAA;;;;;;;;;;AAnB3C;;cCGa,eAAA,EAAe,QAAA,CAAA,IAAA,CAAA,QAAA,CAAA,MAAA,UAAA,OAAA;EDc1B,iFCOA,QAAA,CAAA,OAAA;;;;;;;EDxBiB;;;;;;KC0BP,WAAA,GAAc,MAAA,QAAc,eAAA,CAAgB,MAAA;AAAA,KAC5C,OAAA,GAAU,WAAA;;;;;;;;;;;AD3BtB;;iBEMgB,WAAA,CAAA;;;KCVJ,SAAA;AAAA,KACA,iBAAA;;;;;;;AHGZ;;;cGQa,YAAA;;;kBAQO,SAAA;AAAA;;;;;;;;;;;cCbP,eAAA;;;;;;;;;;;;;;;cCAA,QAAA;;;;;;;;;;;;;ALHb;;;;;;;;;;;;;;;;;;;;UMqBiB,aAAA;EAEf,IAAA;EACA,IAAA;EACA,QAAA;EACA,MAAA;EACA,MAAA;EACA,IAAA;EACA,IAAA;EACA,QAAA;EACA,IAAA;EACA,MAAA;EACA,QAAA;EACA,SAAA;EACA,MAAA;EACA,MAAA;EACA,KAAA;ELbA;;;;EKoBA,IAAA;EACA,KAAA;EACA,WAAA;EACA,WAAA;;;;;;;;EAQA,YAAA;ELpD0B;;;;;EK4D1B,KAAA,GAAQ,KAAA,UAAe,iBAAA,IAAqB,oBAAA;;;;;EAM5C,UAAA;;;;AL3CF;;EKkDE,cAAA,eAEM,KAAA;IAAoB,KAAA;IAAe,KAAA;EAAA;ELpDa;;;AACxD;;;;;;EK+DE,KAAA;;AJpFF;;;EI2FE,MAAA;EACA,QAAA;EACA,QAAA;;;AHvGF;;EG8GE,GAAA;EACA,MAAA;EH/GmB;AACrB;;;;;AAWA;;;EG+GE,MAAA;IAGM,KAAA;IACA,MAAA;IACA,OAAA;IACA,MAAA;EAAA;EAIN,UAAA;IACE,aAAA;MAA4B,KAAA;MAAgB,OAAA;IAAA;IAE5C,aAAA,IAAiB,CAAA,UAAW,KAAA;IAC5B,QAAA;IACA,WAAA;IAEA,SAAA;EAAA;EAAA,CAID,GAAA;AAAA;AAAA,UAGc,iBAAA;EACf,KAAA;EACA,KAAA;EACA,WAAA;EACA,GAAA;AAAA;AAAA,KAGU,oBAAA,IACV,KAAA,aAEE,KAAA,UAAe,iBAAA,IACf,OAAA,CAAQ,KAAA,UAAe,iBAAA;;;;ADvJ3B;;KC8JY,eAAA,IAAmB,OAAA;EAC7B,IAAA,EAAM,SAAA;EACN,KAAA;AAAA,MACI,OAAA,CAAQ,aAAA;AAAA,KAEF,mBAAA,GAAsB,aAAA,GAAgB,eAAA;;;;;cAMrC,oBAAA,GACX,GAAA,WACA,OAAA;EAAW,IAAA,EAAM,SAAA;EAAgB,KAAA;AAAA,MAChC,OAAA,CAAQ,aAAA;AAAA;EAAA,UAkBC,cAAA;IAlIkC;;;;IAuI1C,QAAA,GAAW,mBAAA;EAAA;AAAA;;;;;;;;;;;ANtMf;;;;cOoBa,aAAA;EACX,EAAA,EAAE,wBAAA,CAAA,uBAAA,WAAA,OAAA;UADsB,SAAA,CAAA,OAAA;;;;;;EASxB,IAAA,EARE,oBAAA,CAQE,aAAA;AAAA;;;;YCjBa,KAAA;IACf,iBAAA,EAAmB,OAAA;IACnB,wBAAA,EAA0B,WAAA;EAAA;AAAA;;;;;;;;;cAcjB,aAAA,EAAa,QAAA,CAAA,OAAA,CAIxB,QAAA,CAJwB,MAAA"}
|
package/dist/react/ui/index.js
CHANGED
|
@@ -3,6 +3,43 @@ import { $head } from "alepha/react/head";
|
|
|
3
3
|
import { $cookie } from "alepha/server/cookies";
|
|
4
4
|
import { useEffect, useState } from "react";
|
|
5
5
|
import { useStore } from "alepha/react";
|
|
6
|
+
//#region ../../src/react/ui/atoms/uiThemeListAtom.ts
|
|
7
|
+
/**
|
|
8
|
+
* Available themes the user can pick from. Apps populate this atom on boot
|
|
9
|
+
* (e.g. `alepha.store.set(uiThemeListAtom, MY_THEMES)`); UI consumers like
|
|
10
|
+
* `<ButtonTheme/>` read it to render a picker. The selected theme id is
|
|
11
|
+
* persisted separately in `uiAtom.theme`.
|
|
12
|
+
*
|
|
13
|
+
* Defaults to a single `"default"` entry so the registry stays usable when
|
|
14
|
+
* an app doesn't declare its own list.
|
|
15
|
+
*/
|
|
16
|
+
const uiThemeListAtom = $atom({
|
|
17
|
+
name: "alepha.react.ui.themes",
|
|
18
|
+
schema: t.array(t.object({
|
|
19
|
+
/** Stable id stored in `uiAtom.theme`. Mapped to a CSS class on `<html>`. */
|
|
20
|
+
id: t.string(),
|
|
21
|
+
/** Human-readable label shown in the picker. */
|
|
22
|
+
label: t.string(),
|
|
23
|
+
/**
|
|
24
|
+
* Optional 4-color preview swatch in 2×2 order (TL, TR, BL, BR). Any
|
|
25
|
+
* CSS-valid color string.
|
|
26
|
+
*/
|
|
27
|
+
swatch: t.optional(t.array(t.string(), {
|
|
28
|
+
minItems: 4,
|
|
29
|
+
maxItems: 4
|
|
30
|
+
})),
|
|
31
|
+
/**
|
|
32
|
+
* Optional stylesheet URL (typically Google Fonts) loaded lazily when
|
|
33
|
+
* the theme is selected.
|
|
34
|
+
*/
|
|
35
|
+
fontHref: t.optional(t.string())
|
|
36
|
+
})),
|
|
37
|
+
default: [{
|
|
38
|
+
id: "default",
|
|
39
|
+
label: "Default"
|
|
40
|
+
}]
|
|
41
|
+
});
|
|
42
|
+
//#endregion
|
|
6
43
|
//#region ../../src/react/ui/atoms/uiAtom.ts
|
|
7
44
|
/**
|
|
8
45
|
* Persisted UI state — color mode, theme palette, sidebar collapsed state, etc.
|
|
@@ -13,12 +50,15 @@ import { useStore } from "alepha/react";
|
|
|
13
50
|
const uiAtom = $atom({
|
|
14
51
|
name: "alepha.react.ui",
|
|
15
52
|
schema: t.object({
|
|
53
|
+
/** Color mode preference. `"system"` follows the OS-level setting. */
|
|
16
54
|
mode: t.enum([
|
|
17
55
|
"light",
|
|
18
56
|
"dark",
|
|
19
57
|
"system"
|
|
20
58
|
]),
|
|
59
|
+
/** Theme palette name. UI consumers map this to a CSS class on the root. */
|
|
21
60
|
theme: t.string(),
|
|
61
|
+
/** Sidebar UI state. */
|
|
22
62
|
sidebar: t.object({ collapsed: t.boolean() })
|
|
23
63
|
}),
|
|
24
64
|
default: {
|
|
@@ -187,6 +227,28 @@ const useSidebarState = () => {
|
|
|
187
227
|
};
|
|
188
228
|
};
|
|
189
229
|
//#endregion
|
|
230
|
+
//#region ../../src/react/ui/services/SchemaControl.ts
|
|
231
|
+
/**
|
|
232
|
+
* Resolve a raw `$control` value (object or function) into a concrete
|
|
233
|
+
* partial config. Returns `null` when the field should be hidden.
|
|
234
|
+
*/
|
|
235
|
+
const resolveSchemaControl = (raw, context) => {
|
|
236
|
+
if (raw == null) return {};
|
|
237
|
+
if (typeof raw === "function") {
|
|
238
|
+
const result = raw(context);
|
|
239
|
+
if (result === false) return null;
|
|
240
|
+
if (!result) return {};
|
|
241
|
+
if (result.hidden) return null;
|
|
242
|
+
return result;
|
|
243
|
+
}
|
|
244
|
+
if (typeof raw === "object") {
|
|
245
|
+
const obj = raw;
|
|
246
|
+
if (obj.hidden) return null;
|
|
247
|
+
return obj;
|
|
248
|
+
}
|
|
249
|
+
return {};
|
|
250
|
+
};
|
|
251
|
+
//#endregion
|
|
190
252
|
//#region ../../src/react/ui/index.ts
|
|
191
253
|
/**
|
|
192
254
|
* Persisted UI state: color mode, theme palette, sidebar collapsed state.
|
|
@@ -198,9 +260,10 @@ const useSidebarState = () => {
|
|
|
198
260
|
*/
|
|
199
261
|
const AlephaReactUi = $module({
|
|
200
262
|
name: "alepha.react.ui",
|
|
263
|
+
atoms: [uiThemeListAtom],
|
|
201
264
|
services: [UiPersistence]
|
|
202
265
|
});
|
|
203
266
|
//#endregion
|
|
204
|
-
export { AlephaReactUi, ColorScheme, UiPersistence, uiAtom, useColorMode, useSidebarState, useTheme };
|
|
267
|
+
export { AlephaReactUi, ColorScheme, UiPersistence, resolveSchemaControl, uiAtom, uiThemeListAtom, useColorMode, useSidebarState, useTheme };
|
|
205
268
|
|
|
206
269
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../../src/react/ui/atoms/uiAtom.ts","../../../src/react/ui/services/UiPersistence.ts","../../../src/react/ui/hooks/useColorMode.ts","../../../src/react/ui/hooks/useTheme.ts","../../../src/react/ui/components/ColorScheme.tsx","../../../src/react/ui/hooks/useSidebarState.ts","../../../src/react/ui/index.ts"],"sourcesContent":["import { $atom, type Static, t } from \"alepha\";\n\n/**\n * Persisted UI state — color mode, theme palette, sidebar collapsed state, etc.\n *\n * The atom is bound to a single `alepha-ui` cookie via {@link UiPersistence},\n * so values survive page reloads and are available during SSR.\n */\nexport const uiAtom = $atom({\n name: \"alepha.react.ui\",\n schema: t.object({\n /** Color mode preference. `\"system\"` follows the OS-level setting. */\n mode: t.enum([\"light\", \"dark\", \"system\"]),\n /** Theme palette name. UI consumers map this to a CSS class on the root. */\n theme: t.string(),\n /** Sidebar UI state. */\n sidebar: t.object({\n collapsed: t.boolean(),\n }),\n }),\n default: {\n mode: \"system\",\n theme: \"default\",\n sidebar: { collapsed: false },\n },\n});\n\nexport type UiState = Static<typeof uiAtom.schema>;\n","import { $head } from \"alepha/react/head\";\nimport { $cookie } from \"alepha/server/cookies\";\nimport { uiAtom } from \"../atoms/uiAtom.ts\";\n\n/**\n * Inline `<script>` rendered by SSR into the document `<head>`. Runs\n * synchronously before any CSS or React hydration: reads the `alepha-ui`\n * cookie, resolves `mode === \"system\"` via `prefers-color-scheme`, and\n * applies `class=\"dark\"` (and optional `theme-<name>`) on `<html>`.\n *\n * This is what kills the flash-of-wrong-theme (FOUC) you'd otherwise get\n * with React-effect-based theming. Failures are swallowed silently — at\n * worst the page paints in light mode for one frame.\n */\nconst colorSchemeBoot = `(function(){try{var m=document.cookie.match(/(?:^|;\\\\s*)alepha-ui=([^;]+)/);var s=m?JSON.parse(decodeURIComponent(m[1])):{};var mode=s.mode||\"system\";var dark=mode===\"dark\"||(mode===\"system\"&&window.matchMedia&&window.matchMedia(\"(prefers-color-scheme: dark)\").matches);var r=document.documentElement;if(dark)r.classList.add(\"dark\");if(s.theme&&s.theme!==\"default\")r.classList.add(\"theme-\"+s.theme);}catch(e){}})();`;\n\n/**\n * Binds the {@link uiAtom} to an `alepha-ui` cookie + injects an inline\n * boot script into the SSR head to prevent FOUC on first paint.\n *\n * Reading flow: on app boot the cookie is parsed and pushed into the atom\n * (via the `key` option on `$cookie`). Writing flow: every time the atom\n * mutates, the cookie is rewritten — a single `useStore(uiAtom)` call is\n * enough to persist UI preferences across reloads.\n *\n * Persists for 365 days; SameSite=lax so it travels on top-level navigation\n * but not on cross-origin requests.\n */\nexport class UiPersistence {\n ui = $cookie({\n name: \"alepha-ui\",\n key: uiAtom.key,\n schema: uiAtom.schema,\n ttl: [365, \"days\"],\n sameSite: \"lax\",\n });\n\n head = $head({\n script: [{ content: colorSchemeBoot }],\n });\n}\n","import { useStore } from \"alepha/react\";\nimport { useEffect, useState } from \"react\";\nimport { uiAtom } from \"../atoms/uiAtom.ts\";\n\nexport type ColorMode = \"light\" | \"dark\" | \"system\";\nexport type ResolvedColorMode = \"light\" | \"dark\";\n\n/**\n * Read and update the user's color-mode preference. `\"system\"` resolves to\n * the OS preference and updates live as the OS toggles between light/dark.\n *\n * @example\n * const { mode, setMode, resolved } = useColorMode();\n * setMode(\"dark\");\n * document.documentElement.classList.toggle(\"dark\", resolved === \"dark\");\n */\nexport const useColorMode = () => {\n const [state, set] = useStore(uiAtom);\n const mode = (state?.mode ?? \"system\") as ColorMode;\n const resolved = useResolvedColorMode(mode);\n\n return {\n mode,\n resolved,\n setMode: (next: ColorMode) => {\n set({ ...(state ?? uiAtom.options.default!), mode: next });\n },\n };\n};\n\nconst useResolvedColorMode = (mode: ColorMode): ResolvedColorMode => {\n const [systemDark, setSystemDark] = useState<boolean>(() => {\n if (typeof window === \"undefined\") return false;\n return window.matchMedia?.(\"(prefers-color-scheme: dark)\").matches ?? false;\n });\n\n useEffect(() => {\n if (typeof window === \"undefined\") return;\n const mq = window.matchMedia?.(\"(prefers-color-scheme: dark)\");\n if (!mq) return;\n const onChange = (ev: MediaQueryListEvent) => setSystemDark(ev.matches);\n mq.addEventListener(\"change\", onChange);\n return () => mq.removeEventListener(\"change\", onChange);\n }, []);\n\n if (mode === \"dark\") return \"dark\";\n if (mode === \"light\") return \"light\";\n return systemDark ? \"dark\" : \"light\";\n};\n","import { useStore } from \"alepha/react\";\nimport { uiAtom } from \"../atoms/uiAtom.ts\";\n\n/**\n * Read and update the active theme palette name. UI consumers typically map\n * the value to a class on the document root (e.g. `theme-claude`).\n *\n * @example\n * const { theme, setTheme } = useTheme();\n * setTheme(\"claude\");\n */\nexport const useTheme = () => {\n const [state, set] = useStore(uiAtom);\n const theme = state?.theme ?? \"default\";\n\n return {\n theme,\n setTheme: (next: string) => {\n set({ ...(state ?? uiAtom.options.default!), theme: next });\n },\n };\n};\n","import { useEffect } from \"react\";\nimport { useColorMode } from \"../hooks/useColorMode.ts\";\nimport { useTheme } from \"../hooks/useTheme.ts\";\n\n/**\n * Applies `class=\"dark\"` and an optional theme palette class\n * (`theme-<name>`) to the document root, syncing whenever the underlying\n * atom mutates.\n *\n * Mount once near the root of your tree (typically inside the layout).\n *\n * @example\n * <ColorScheme />\n */\nexport function ColorScheme() {\n const { resolved } = useColorMode();\n const { theme } = useTheme();\n\n useEffect(() => {\n if (typeof document === \"undefined\") return;\n document.documentElement.classList.toggle(\"dark\", resolved === \"dark\");\n }, [resolved]);\n\n useEffect(() => {\n if (typeof document === \"undefined\") return;\n const root = document.documentElement;\n const previous: string[] = [];\n root.classList.forEach((cls) => {\n if (cls.startsWith(\"theme-\")) previous.push(cls);\n });\n for (const cls of previous) root.classList.remove(cls);\n if (theme && theme !== \"default\") root.classList.add(`theme-${theme}`);\n }, [theme]);\n\n return null;\n}\n","import { useStore } from \"alepha/react\";\nimport { uiAtom } from \"../atoms/uiAtom.ts\";\n\n/**\n * Read and update the sidebar collapsed state. The value is persisted via the\n * `alepha-ui` cookie so it survives reloads and is available during SSR — no\n * flash of expanded-then-collapsed when the user prefers a collapsed shell.\n *\n * @example\n * const { collapsed, setCollapsed, toggle } = useSidebarState();\n */\nexport const useSidebarState = () => {\n const [state, set] = useStore(uiAtom);\n const collapsed = state?.sidebar.collapsed ?? false;\n\n const setCollapsed = (next: boolean) => {\n const base = state ?? uiAtom.options.default!;\n set({ ...base, sidebar: { ...base.sidebar, collapsed: next } });\n };\n\n return {\n collapsed,\n setCollapsed,\n toggle: () => setCollapsed(!collapsed),\n };\n};\n","import { $module } from \"alepha\";\nimport type { UiState } from \"./atoms/uiAtom.ts\";\nimport { UiPersistence } from \"./services/UiPersistence.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport * from \"./atoms/uiAtom.ts\";\nexport * from \"./components/ColorScheme.tsx\";\nexport * from \"./hooks/useColorMode.ts\";\nexport * from \"./hooks/useSidebarState.ts\";\nexport * from \"./hooks/useTheme.ts\";\nexport * from \"./services/UiPersistence.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\ndeclare module \"alepha\" {\n export interface State {\n \"alepha.react.ui\": UiState;\n }\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Persisted UI state: color mode, theme palette, sidebar collapsed state.\n *\n * Backed by an `alepha-ui` cookie so preferences survive reloads and are\n * available during SSR (no flash of wrong theme).\n *\n * @module alepha.react.ui\n */\nexport const AlephaReactUi = $module({\n name: \"alepha.react.ui\",\n services: [UiPersistence],\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../src/react/ui/atoms/uiThemeListAtom.ts","../../../src/react/ui/atoms/uiAtom.ts","../../../src/react/ui/services/UiPersistence.ts","../../../src/react/ui/hooks/useColorMode.ts","../../../src/react/ui/hooks/useTheme.ts","../../../src/react/ui/components/ColorScheme.tsx","../../../src/react/ui/hooks/useSidebarState.ts","../../../src/react/ui/services/SchemaControl.ts","../../../src/react/ui/index.ts"],"sourcesContent":["import { $atom, type Static, t } from \"alepha\";\n\n/**\n * Available themes the user can pick from. Apps populate this atom on boot\n * (e.g. `alepha.store.set(uiThemeListAtom, MY_THEMES)`); UI consumers like\n * `<ButtonTheme/>` read it to render a picker. The selected theme id is\n * persisted separately in `uiAtom.theme`.\n *\n * Defaults to a single `\"default\"` entry so the registry stays usable when\n * an app doesn't declare its own list.\n */\nexport const uiThemeListAtom = $atom({\n name: \"alepha.react.ui.themes\",\n schema: t.array(\n t.object({\n /** Stable id stored in `uiAtom.theme`. Mapped to a CSS class on `<html>`. */\n id: t.string(),\n /** Human-readable label shown in the picker. */\n label: t.string(),\n /**\n * Optional 4-color preview swatch in 2×2 order (TL, TR, BL, BR). Any\n * CSS-valid color string.\n */\n swatch: t.optional(t.array(t.string(), { minItems: 4, maxItems: 4 })),\n /**\n * Optional stylesheet URL (typically Google Fonts) loaded lazily when\n * the theme is selected.\n */\n fontHref: t.optional(t.string()),\n }),\n ),\n default: [{ id: \"default\", label: \"Default\" }],\n});\n\nexport type UiThemeList = Static<typeof uiThemeListAtom.schema>;\nexport type UiTheme = UiThemeList[number];\n","import { $atom, type Static, t } from \"alepha\";\n\n/**\n * Persisted UI state — color mode, theme palette, sidebar collapsed state, etc.\n *\n * The atom is bound to a single `alepha-ui` cookie via {@link UiPersistence},\n * so values survive page reloads and are available during SSR.\n */\nexport const uiAtom = $atom({\n name: \"alepha.react.ui\",\n schema: t.object({\n /** Color mode preference. `\"system\"` follows the OS-level setting. */\n mode: t.enum([\"light\", \"dark\", \"system\"]),\n /** Theme palette name. UI consumers map this to a CSS class on the root. */\n theme: t.string(),\n /** Sidebar UI state. */\n sidebar: t.object({\n collapsed: t.boolean(),\n }),\n }),\n default: {\n mode: \"system\",\n theme: \"default\",\n sidebar: { collapsed: false },\n },\n});\n\nexport type UiState = Static<typeof uiAtom.schema>;\n","import { $head } from \"alepha/react/head\";\nimport { $cookie } from \"alepha/server/cookies\";\nimport { uiAtom } from \"../atoms/uiAtom.ts\";\n\n/**\n * Inline `<script>` rendered by SSR into the document `<head>`. Runs\n * synchronously before any CSS or React hydration: reads the `alepha-ui`\n * cookie, resolves `mode === \"system\"` via `prefers-color-scheme`, and\n * applies `class=\"dark\"` (and optional `theme-<name>`) on `<html>`.\n *\n * This is what kills the flash-of-wrong-theme (FOUC) you'd otherwise get\n * with React-effect-based theming. Failures are swallowed silently — at\n * worst the page paints in light mode for one frame.\n */\nconst colorSchemeBoot = `(function(){try{var m=document.cookie.match(/(?:^|;\\\\s*)alepha-ui=([^;]+)/);var s=m?JSON.parse(decodeURIComponent(m[1])):{};var mode=s.mode||\"system\";var dark=mode===\"dark\"||(mode===\"system\"&&window.matchMedia&&window.matchMedia(\"(prefers-color-scheme: dark)\").matches);var r=document.documentElement;if(dark)r.classList.add(\"dark\");if(s.theme&&s.theme!==\"default\")r.classList.add(\"theme-\"+s.theme);}catch(e){}})();`;\n\n/**\n * Binds the {@link uiAtom} to an `alepha-ui` cookie + injects an inline\n * boot script into the SSR head to prevent FOUC on first paint.\n *\n * Reading flow: on app boot the cookie is parsed and pushed into the atom\n * (via the `key` option on `$cookie`). Writing flow: every time the atom\n * mutates, the cookie is rewritten — a single `useStore(uiAtom)` call is\n * enough to persist UI preferences across reloads.\n *\n * Persists for 365 days; SameSite=lax so it travels on top-level navigation\n * but not on cross-origin requests.\n */\nexport class UiPersistence {\n ui = $cookie({\n name: \"alepha-ui\",\n key: uiAtom.key,\n schema: uiAtom.schema,\n ttl: [365, \"days\"],\n sameSite: \"lax\",\n });\n\n head = $head({\n script: [{ content: colorSchemeBoot }],\n });\n}\n","import { useStore } from \"alepha/react\";\nimport { useEffect, useState } from \"react\";\nimport { uiAtom } from \"../atoms/uiAtom.ts\";\n\nexport type ColorMode = \"light\" | \"dark\" | \"system\";\nexport type ResolvedColorMode = \"light\" | \"dark\";\n\n/**\n * Read and update the user's color-mode preference. `\"system\"` resolves to\n * the OS preference and updates live as the OS toggles between light/dark.\n *\n * @example\n * const { mode, setMode, resolved } = useColorMode();\n * setMode(\"dark\");\n * document.documentElement.classList.toggle(\"dark\", resolved === \"dark\");\n */\nexport const useColorMode = () => {\n const [state, set] = useStore(uiAtom);\n const mode = (state?.mode ?? \"system\") as ColorMode;\n const resolved = useResolvedColorMode(mode);\n\n return {\n mode,\n resolved,\n setMode: (next: ColorMode) => {\n set({ ...(state ?? uiAtom.options.default!), mode: next });\n },\n };\n};\n\nconst useResolvedColorMode = (mode: ColorMode): ResolvedColorMode => {\n const [systemDark, setSystemDark] = useState<boolean>(() => {\n if (typeof window === \"undefined\") return false;\n return window.matchMedia?.(\"(prefers-color-scheme: dark)\").matches ?? false;\n });\n\n useEffect(() => {\n if (typeof window === \"undefined\") return;\n const mq = window.matchMedia?.(\"(prefers-color-scheme: dark)\");\n if (!mq) return;\n const onChange = (ev: MediaQueryListEvent) => setSystemDark(ev.matches);\n mq.addEventListener(\"change\", onChange);\n return () => mq.removeEventListener(\"change\", onChange);\n }, []);\n\n if (mode === \"dark\") return \"dark\";\n if (mode === \"light\") return \"light\";\n return systemDark ? \"dark\" : \"light\";\n};\n","import { useStore } from \"alepha/react\";\nimport { uiAtom } from \"../atoms/uiAtom.ts\";\n\n/**\n * Read and update the active theme palette name. UI consumers typically map\n * the value to a class on the document root (e.g. `theme-claude`).\n *\n * @example\n * const { theme, setTheme } = useTheme();\n * setTheme(\"claude\");\n */\nexport const useTheme = () => {\n const [state, set] = useStore(uiAtom);\n const theme = state?.theme ?? \"default\";\n\n return {\n theme,\n setTheme: (next: string) => {\n set({ ...(state ?? uiAtom.options.default!), theme: next });\n },\n };\n};\n","import { useEffect } from \"react\";\nimport { useColorMode } from \"../hooks/useColorMode.ts\";\nimport { useTheme } from \"../hooks/useTheme.ts\";\n\n/**\n * Applies `class=\"dark\"` and an optional theme palette class\n * (`theme-<name>`) to the document root, syncing whenever the underlying\n * atom mutates.\n *\n * Mount once near the root of your tree (typically inside the layout).\n *\n * @example\n * <ColorScheme />\n */\nexport function ColorScheme() {\n const { resolved } = useColorMode();\n const { theme } = useTheme();\n\n useEffect(() => {\n if (typeof document === \"undefined\") return;\n document.documentElement.classList.toggle(\"dark\", resolved === \"dark\");\n }, [resolved]);\n\n useEffect(() => {\n if (typeof document === \"undefined\") return;\n const root = document.documentElement;\n const previous: string[] = [];\n root.classList.forEach((cls) => {\n if (cls.startsWith(\"theme-\")) previous.push(cls);\n });\n for (const cls of previous) root.classList.remove(cls);\n if (theme && theme !== \"default\") root.classList.add(`theme-${theme}`);\n }, [theme]);\n\n return null;\n}\n","import { useStore } from \"alepha/react\";\nimport { uiAtom } from \"../atoms/uiAtom.ts\";\n\n/**\n * Read and update the sidebar collapsed state. The value is persisted via the\n * `alepha-ui` cookie so it survives reloads and is available during SSR — no\n * flash of expanded-then-collapsed when the user prefers a collapsed shell.\n *\n * @example\n * const { collapsed, setCollapsed, toggle } = useSidebarState();\n */\nexport const useSidebarState = () => {\n const [state, set] = useStore(uiAtom);\n const collapsed = state?.sidebar.collapsed ?? false;\n\n const setCollapsed = (next: boolean) => {\n const base = state ?? uiAtom.options.default!;\n set({ ...base, sidebar: { ...base.sidebar, collapsed: next } });\n };\n\n return {\n collapsed,\n setCollapsed,\n toggle: () => setCollapsed(!collapsed),\n };\n};\n","import type { FormModel } from \"alepha/react/form\";\n\n/**\n * Schema-bound metadata read by `<Control>` (in `@alepha/ui-registry`) to\n * configure how a field renders. Place under `$control` on any TypeBox\n * schema option.\n *\n * Two forms:\n *\n * 1. **Object** — static configuration baked into the schema:\n * ```ts\n * t.string({ $control: { password: true, icon: \"key\" } })\n * ```\n *\n * 2. **Function** — dynamic, computed from current form state:\n * ```ts\n * t.string({\n * $control: ({ form, value }) => {\n * if (form.currentValues.kind !== \"advanced\") return false; // hide\n * return { items: () => fetchOptions(form.currentValues.kind) };\n * },\n * })\n * ```\n *\n * The function may return:\n * - a partial `SchemaControl` to merge with explicit `<Control>` props\n * - `false` to hide the control entirely\n * - `undefined` to leave the field as-is\n */\nexport interface SchemaControl {\n // ── Variant forcing ────────────────────────────────────────────────\n text?: boolean;\n area?: boolean;\n password?: boolean;\n switch?: boolean;\n number?: boolean;\n file?: boolean;\n date?: boolean;\n datetime?: boolean;\n time?: boolean;\n select?: boolean;\n combobox?: boolean;\n segmented?: boolean;\n slider?: boolean;\n object?: boolean;\n array?: boolean;\n\n // ── Labels / hints ────────────────────────────────────────────────\n /**\n * Icon name. The registry control maps this to its icon set\n * (lucide-react). Pass `null` to suppress the schema-inferred icon.\n */\n icon?: string | null;\n label?: string;\n description?: string;\n placeholder?: string;\n /**\n * HTML `autocomplete` attribute. Use standard tokens like\n * `\"username\"`, `\"email\"`, `\"new-password\"`, `\"current-password\"`,\n * `\"street-address\"`, `\"address-line1\"`, `\"address-level2\"` (city),\n * `\"postal-code\"`, `\"country\"`, `\"cc-number\"`, `\"cc-exp\"`,\n * `\"cc-csc\"`, `\"cc-name\"`, `\"tel\"`, etc.\n */\n autoComplete?: string;\n\n // ── Data ──────────────────────────────────────────────────────────\n /**\n * Static or async option list for select / combobox / multi-select.\n * Each item is either a bare string (used as both value & label) or a\n * `{ value, label, description?, tag? }` object.\n */\n items?: Array<string | SchemaControlItem> | SchemaControlItemsFn;\n\n /**\n * Re-fetch `items` (when async) whenever any of these reference values\n * change. Useful for cascading selects.\n */\n itemsWatch?: unknown[];\n\n /**\n * Allow the user to create a new option by typing into a select /\n * multi-select. Pass `true` for `{ value: query, label: query }`, or a\n * function returning a custom option built from the query.\n */\n createNewEntry?:\n | boolean\n | ((query: string) => { value: string; label: string });\n\n // ── Layout ────────────────────────────────────────────────────────\n /**\n * Width slot inside an `<AutoForm>` group. Mapped to a grid column span.\n * - `100` → full row\n * - `75` → 3/4 row\n * - `66` → 2/3 row\n * - `50` → half\n * - `33` → one third (default for plain primitives)\n * - `25` → one quarter\n */\n width?: 100 | 75 | 66 | 50 | 33 | 25;\n\n // ── Behavior ─────────────────────────────────────────────────────\n /**\n * Render `null` (hide) when truthy. Equivalent to a function `$control`\n * returning `false`, but available as a static value.\n */\n hidden?: boolean;\n disabled?: boolean;\n readOnly?: boolean;\n\n // ── Slots ─────────────────────────────────────────────────────────\n /**\n * Render before/after the field. Both receive the resolved input.\n * Typed loosely — UI layer narrows to `ReactNode`.\n */\n top?: unknown;\n bottom?: unknown;\n\n // ── File upload ───────────────────────────────────────────────────\n /**\n * Render a managed upload control (image preview, multi, drag-drop)\n * that posts to the file API and stores the resulting file ID(s) in\n * the form. Pass `true` for defaults or an options object:\n *\n * ```ts\n * $control: { upload: { multi: true, accept: \"image/*\", maxSize: 5_000_000 } }\n * ```\n */\n upload?:\n | boolean\n | {\n multi?: boolean;\n accept?: string;\n maxSize?: number;\n bucket?: string;\n };\n\n // ── Array specifics ───────────────────────────────────────────────\n arrayProps?: {\n confirmDelete?: boolean | { title?: string; message?: string };\n /** Computed label for each tab when an array uses tabs mode. */\n renderTabName?: (i: number, value: unknown) => string;\n sortable?: boolean;\n collapsible?: boolean;\n /** Force grouped (CreateForm-style) tabs even for short arrays. */\n forceTabs?: boolean;\n };\n\n // ── Open extension ────────────────────────────────────────────────\n [key: string]: unknown;\n}\n\nexport interface SchemaControlItem {\n value: string | number | boolean;\n label: string;\n description?: string;\n tag?: string;\n}\n\nexport type SchemaControlItemsFn = (\n query: string,\n) =>\n | Array<string | SchemaControlItem>\n | Promise<Array<string | SchemaControlItem>>;\n\n/**\n * Function form of `$control`. Receives the live form model + the current\n * field value, and returns a partial config (merged with explicit props),\n * `false` to hide, or `undefined` to leave as-is.\n */\nexport type SchemaControlFn = (context: {\n form: FormModel<any>;\n value: unknown;\n}) => Partial<SchemaControl> | false | undefined;\n\nexport type SchemaControlOption = SchemaControl | SchemaControlFn;\n\n/**\n * Resolve a raw `$control` value (object or function) into a concrete\n * partial config. Returns `null` when the field should be hidden.\n */\nexport const resolveSchemaControl = (\n raw: unknown,\n context: { form: FormModel<any>; value: unknown },\n): Partial<SchemaControl> | null => {\n if (raw == null) return {};\n if (typeof raw === \"function\") {\n const result = (raw as SchemaControlFn)(context);\n if (result === false) return null;\n if (!result) return {};\n if (result.hidden) return null;\n return result;\n }\n if (typeof raw === \"object\") {\n const obj = raw as SchemaControl;\n if (obj.hidden) return null;\n return obj;\n }\n return {};\n};\n\ndeclare module \"typebox\" {\n interface TSchemaOptions {\n /**\n * UI metadata read by `<Control>` from `@alepha/ui-registry`. See\n * {@link SchemaControl}.\n */\n $control?: SchemaControlOption;\n }\n}\n","import { $module } from \"alepha\";\nimport type { UiState } from \"./atoms/uiAtom.ts\";\nimport type { UiThemeList } from \"./atoms/uiThemeListAtom.ts\";\nimport { uiThemeListAtom } from \"./atoms/uiThemeListAtom.ts\";\nimport { UiPersistence } from \"./services/UiPersistence.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport * from \"./atoms/uiAtom.ts\";\nexport * from \"./atoms/uiThemeListAtom.ts\";\nexport * from \"./components/ColorScheme.tsx\";\nexport * from \"./hooks/useColorMode.ts\";\nexport * from \"./hooks/useSidebarState.ts\";\nexport * from \"./hooks/useTheme.ts\";\nexport * from \"./services/SchemaControl.ts\";\nexport * from \"./services/UiPersistence.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\ndeclare module \"alepha\" {\n export interface State {\n \"alepha.react.ui\": UiState;\n \"alepha.react.ui.themes\": UiThemeList;\n }\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Persisted UI state: color mode, theme palette, sidebar collapsed state.\n *\n * Backed by an `alepha-ui` cookie so preferences survive reloads and are\n * available during SSR (no flash of wrong theme).\n *\n * @module alepha.react.ui\n */\nexport const AlephaReactUi = $module({\n name: \"alepha.react.ui\",\n atoms: [uiThemeListAtom],\n services: [UiPersistence],\n});\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../../src/react/websocket/hooks/useRoom.tsx"],"sourcesContent":["import type { Static } from \"alepha\";\nimport { useAlepha, useInject } from \"alepha/react\";\nimport type { ChannelPrimitive, TWSObject } from \"alepha/websocket\";\nimport { WebSocketClient } from \"alepha/websocket\";\nimport { useEffect, useRef, useState } from \"react\";\n\n/**\n * UseRoom options\n */\nexport interface UseRoomOptions<\n TClient extends TWSObject,\n TServer extends TWSObject,\n> {\n /**\n * Room ID to connect to\n */\n roomId: string;\n\n /**\n * Channel primitive defining the schemas\n */\n channel: ChannelPrimitive<TClient, TServer>;\n\n /**\n * Handler for incoming messages from the server\n */\n handler: (message: Static<TClient>) => void;\n\n /**\n * Optional WebSocket URL override\n * Defaults to auto-detected URL based on window.location\n */\n url?: string;\n\n /**\n * Enable automatic reconnection on disconnect\n * @default true\n */\n autoReconnect?: boolean;\n\n /**\n * Reconnection interval in milliseconds\n * @default 3000\n */\n reconnectInterval?: number;\n\n /**\n * Maximum reconnection attempts (-1 for infinite)\n * @default 10\n */\n maxReconnectAttempts?: number;\n\n /**\n * Called when connection is established\n */\n onConnect?: () => void;\n\n /**\n * Called when connection is closed\n */\n onDisconnect?: () => void;\n\n /**\n * Called on connection error\n */\n onError?: (error: Error) => void;\n}\n\n/**\n * UseRoom return value\n */\nexport interface UseRoomReturn<TServer extends TWSObject> {\n /**\n * Send a message to the server\n */\n send: (message: Static<TServer>) => Promise<void>;\n\n /**\n * Whether the connection is established\n */\n isConnected: boolean;\n\n /**\n * Whether the connection is in progress\n */\n isConnecting: boolean;\n\n /**\n * Whether there was an error\n */\n isError: boolean;\n\n /**\n * The error object if any\n */\n error?: Error;\n\n /**\n * Manually reconnect\n */\n reconnect: () => void;\n\n /**\n * Manually disconnect\n */\n disconnect: () => void;\n}\n\n/**\n * React hook for WebSocket room communication\n *\n * Provides automatic connection management, reconnection, and type-safe messaging\n * for WebSocket rooms using the injected WebSocketClient service.\n *\n * Multiple useRoom hooks on the same channel will share a single WebSocket connection.\n *\n * @example\n * ```tsx\n * const chat = useRoom({\n * roomId: \"room-123\",\n * channel: chatChannel,\n * handler: (message) => {\n * if (message.type === \"append\") {\n * setMessages(prev => [...prev, message]);\n * }\n * }\n * }, [roomId]);\n *\n * const sendMessage = async () => {\n * await chat.send({\n * content: \"Hello, world!\"\n * });\n * };\n * ```\n */\nexport const useRoom = <TClient extends TWSObject, TServer extends TWSObject>(\n options: UseRoomOptions<TClient, TServer>,\n deps: unknown[],\n): UseRoomReturn<TServer> => {\n const webSocketClient = useInject(WebSocketClient);\n const unsubscribeRef = useRef<(() => void) | null>(null);\n\n const [isConnected, setIsConnected] = useState(false);\n const [isConnecting, setIsConnecting] = useState(false);\n const [isError, setIsError] = useState(false);\n const [error, setError] = useState<Error | undefined>(undefined);\n\n const {\n roomId,\n channel,\n handler,\n url,\n autoReconnect,\n reconnectInterval,\n maxReconnectAttempts,\n onConnect,\n onDisconnect,\n onError,\n } = options;\n\n // Keep handler ref stable to avoid stale closures\n const handlerRef = useRef(handler);\n handlerRef.current = handler;\n\n useEffect(() => {\n // Subscribe to room — use ref so handler is always current\n const unsubscribe = webSocketClient.subscribe(\n roomId,\n channel,\n (msg) => handlerRef.current(msg),\n {\n url,\n autoReconnect,\n reconnectInterval,\n maxReconnectAttempts,\n onConnect: () => {\n setIsConnected(true);\n setIsConnecting(false);\n setIsError(false);\n setError(undefined);\n onConnect?.();\n },\n onDisconnect: () => {\n setIsConnected(false);\n setIsConnecting(false);\n onDisconnect?.();\n },\n onError: (err) => {\n setIsError(true);\n setError(err);\n setIsConnecting(false);\n onError?.(err);\n },\n },\n );\n\n unsubscribeRef.current = unsubscribe;\n\n // Get initial state from connection\n const connection = webSocketClient.getConnection(channel);\n if (connection) {\n setIsConnected(connection.isConnected);\n setIsConnecting(connection.isConnecting);\n setIsError(connection.isError);\n setError(connection.error);\n }\n\n // Cleanup on unmount or deps change\n return () => {\n unsubscribe();\n unsubscribeRef.current = null;\n };\n }, deps);\n\n const alepha = useAlepha();\n\n if (!alepha.isBrowser()) {\n return {\n send: async (_message: Static<TServer>) => {\n // No-op on server\n },\n isConnected: false,\n isConnecting: false,\n isError: false,\n error: undefined,\n reconnect: () => {\n // No-op on server\n },\n disconnect: () => {\n // No-op on server\n },\n };\n }\n\n return {\n send: async (message: Static<TServer>) => {\n await webSocketClient.send(roomId, channel, message);\n },\n isConnected,\n isConnecting,\n isError,\n error,\n reconnect: () => {\n const connection = webSocketClient.getConnection(channel);\n connection?.reconnect();\n },\n disconnect: () => {\n unsubscribeRef.current?.();\n },\n };\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuIA,MAAa,WACX,SACA,SAC2B;CAC3B,MAAM,kBAAkB,UAAU,gBAAgB;CAClD,MAAM,iBAAiB,OAA4B,KAAK;CAExD,MAAM,CAAC,aAAa,kBAAkB,SAAS,MAAM;CACrD,MAAM,CAAC,cAAc,mBAAmB,SAAS,MAAM;CACvD,MAAM,CAAC,SAAS,cAAc,SAAS,MAAM;CAC7C,MAAM,CAAC,OAAO,YAAY,SAA4B,KAAA,EAAU;CAEhE,MAAM,EACJ,QACA,SACA,SACA,KACA,eACA,mBACA,sBACA,WACA,cACA,YACE;CAGJ,MAAM,aAAa,OAAO,QAAQ;AAClC,YAAW,UAAU;AAErB,iBAAgB;EAEd,MAAM,cAAc,gBAAgB,UAClC,QACA,UACC,QAAQ,WAAW,QAAQ,IAAI,EAChC;GACE;GACA;GACA;GACA;GACA,iBAAiB;AACf,mBAAe,KAAK;AACpB,oBAAgB,MAAM;AACtB,eAAW,MAAM;AACjB,aAAS,KAAA,EAAU;AACnB,iBAAa;;GAEf,oBAAoB;AAClB,mBAAe,MAAM;AACrB,oBAAgB,MAAM;AACtB,oBAAgB;;GAElB,UAAU,QAAQ;AAChB,eAAW,KAAK;AAChB,aAAS,IAAI;AACb,oBAAgB,MAAM;AACtB,cAAU,IAAI;;GAEjB,CACF;AAED,iBAAe,UAAU;EAGzB,MAAM,aAAa,gBAAgB,cAAc,QAAQ;AACzD,MAAI,YAAY;AACd,kBAAe,WAAW,YAAY;AACtC,mBAAgB,WAAW,aAAa;AACxC,cAAW,WAAW,QAAQ;AAC9B,YAAS,WAAW,MAAM;;AAI5B,eAAa;AACX,gBAAa;AACb,kBAAe,UAAU;;IAE1B,KAAK;AAIR,KAAI,CAFW,
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../src/react/websocket/hooks/useRoom.tsx"],"sourcesContent":["import type { Static } from \"alepha\";\nimport { useAlepha, useInject } from \"alepha/react\";\nimport type { ChannelPrimitive, TWSObject } from \"alepha/websocket\";\nimport { WebSocketClient } from \"alepha/websocket\";\nimport { useEffect, useRef, useState } from \"react\";\n\n/**\n * UseRoom options\n */\nexport interface UseRoomOptions<\n TClient extends TWSObject,\n TServer extends TWSObject,\n> {\n /**\n * Room ID to connect to\n */\n roomId: string;\n\n /**\n * Channel primitive defining the schemas\n */\n channel: ChannelPrimitive<TClient, TServer>;\n\n /**\n * Handler for incoming messages from the server\n */\n handler: (message: Static<TClient>) => void;\n\n /**\n * Optional WebSocket URL override\n * Defaults to auto-detected URL based on window.location\n */\n url?: string;\n\n /**\n * Enable automatic reconnection on disconnect\n * @default true\n */\n autoReconnect?: boolean;\n\n /**\n * Reconnection interval in milliseconds\n * @default 3000\n */\n reconnectInterval?: number;\n\n /**\n * Maximum reconnection attempts (-1 for infinite)\n * @default 10\n */\n maxReconnectAttempts?: number;\n\n /**\n * Called when connection is established\n */\n onConnect?: () => void;\n\n /**\n * Called when connection is closed\n */\n onDisconnect?: () => void;\n\n /**\n * Called on connection error\n */\n onError?: (error: Error) => void;\n}\n\n/**\n * UseRoom return value\n */\nexport interface UseRoomReturn<TServer extends TWSObject> {\n /**\n * Send a message to the server\n */\n send: (message: Static<TServer>) => Promise<void>;\n\n /**\n * Whether the connection is established\n */\n isConnected: boolean;\n\n /**\n * Whether the connection is in progress\n */\n isConnecting: boolean;\n\n /**\n * Whether there was an error\n */\n isError: boolean;\n\n /**\n * The error object if any\n */\n error?: Error;\n\n /**\n * Manually reconnect\n */\n reconnect: () => void;\n\n /**\n * Manually disconnect\n */\n disconnect: () => void;\n}\n\n/**\n * React hook for WebSocket room communication\n *\n * Provides automatic connection management, reconnection, and type-safe messaging\n * for WebSocket rooms using the injected WebSocketClient service.\n *\n * Multiple useRoom hooks on the same channel will share a single WebSocket connection.\n *\n * @example\n * ```tsx\n * const chat = useRoom({\n * roomId: \"room-123\",\n * channel: chatChannel,\n * handler: (message) => {\n * if (message.type === \"append\") {\n * setMessages(prev => [...prev, message]);\n * }\n * }\n * }, [roomId]);\n *\n * const sendMessage = async () => {\n * await chat.send({\n * content: \"Hello, world!\"\n * });\n * };\n * ```\n */\nexport const useRoom = <TClient extends TWSObject, TServer extends TWSObject>(\n options: UseRoomOptions<TClient, TServer>,\n deps: unknown[],\n): UseRoomReturn<TServer> => {\n const webSocketClient = useInject(WebSocketClient);\n const unsubscribeRef = useRef<(() => void) | null>(null);\n\n const [isConnected, setIsConnected] = useState(false);\n const [isConnecting, setIsConnecting] = useState(false);\n const [isError, setIsError] = useState(false);\n const [error, setError] = useState<Error | undefined>(undefined);\n\n const {\n roomId,\n channel,\n handler,\n url,\n autoReconnect,\n reconnectInterval,\n maxReconnectAttempts,\n onConnect,\n onDisconnect,\n onError,\n } = options;\n\n // Keep handler ref stable to avoid stale closures\n const handlerRef = useRef(handler);\n handlerRef.current = handler;\n\n useEffect(() => {\n // Subscribe to room — use ref so handler is always current\n const unsubscribe = webSocketClient.subscribe(\n roomId,\n channel,\n (msg) => handlerRef.current(msg),\n {\n url,\n autoReconnect,\n reconnectInterval,\n maxReconnectAttempts,\n onConnect: () => {\n setIsConnected(true);\n setIsConnecting(false);\n setIsError(false);\n setError(undefined);\n onConnect?.();\n },\n onDisconnect: () => {\n setIsConnected(false);\n setIsConnecting(false);\n onDisconnect?.();\n },\n onError: (err) => {\n setIsError(true);\n setError(err);\n setIsConnecting(false);\n onError?.(err);\n },\n },\n );\n\n unsubscribeRef.current = unsubscribe;\n\n // Get initial state from connection\n const connection = webSocketClient.getConnection(channel);\n if (connection) {\n setIsConnected(connection.isConnected);\n setIsConnecting(connection.isConnecting);\n setIsError(connection.isError);\n setError(connection.error);\n }\n\n // Cleanup on unmount or deps change\n return () => {\n unsubscribe();\n unsubscribeRef.current = null;\n };\n }, deps);\n\n const alepha = useAlepha();\n\n if (!alepha.isBrowser()) {\n return {\n send: async (_message: Static<TServer>) => {\n // No-op on server\n },\n isConnected: false,\n isConnecting: false,\n isError: false,\n error: undefined,\n reconnect: () => {\n // No-op on server\n },\n disconnect: () => {\n // No-op on server\n },\n };\n }\n\n return {\n send: async (message: Static<TServer>) => {\n await webSocketClient.send(roomId, channel, message);\n },\n isConnected,\n isConnecting,\n isError,\n error,\n reconnect: () => {\n const connection = webSocketClient.getConnection(channel);\n connection?.reconnect();\n },\n disconnect: () => {\n unsubscribeRef.current?.();\n },\n };\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuIA,MAAa,WACX,SACA,SAC2B;CAC3B,MAAM,kBAAkB,UAAU,gBAAgB;CAClD,MAAM,iBAAiB,OAA4B,KAAK;CAExD,MAAM,CAAC,aAAa,kBAAkB,SAAS,MAAM;CACrD,MAAM,CAAC,cAAc,mBAAmB,SAAS,MAAM;CACvD,MAAM,CAAC,SAAS,cAAc,SAAS,MAAM;CAC7C,MAAM,CAAC,OAAO,YAAY,SAA4B,KAAA,EAAU;CAEhE,MAAM,EACJ,QACA,SACA,SACA,KACA,eACA,mBACA,sBACA,WACA,cACA,YACE;CAGJ,MAAM,aAAa,OAAO,QAAQ;AAClC,YAAW,UAAU;AAErB,iBAAgB;EAEd,MAAM,cAAc,gBAAgB,UAClC,QACA,UACC,QAAQ,WAAW,QAAQ,IAAI,EAChC;GACE;GACA;GACA;GACA;GACA,iBAAiB;AACf,mBAAe,KAAK;AACpB,oBAAgB,MAAM;AACtB,eAAW,MAAM;AACjB,aAAS,KAAA,EAAU;AACnB,iBAAa;;GAEf,oBAAoB;AAClB,mBAAe,MAAM;AACrB,oBAAgB,MAAM;AACtB,oBAAgB;;GAElB,UAAU,QAAQ;AAChB,eAAW,KAAK;AAChB,aAAS,IAAI;AACb,oBAAgB,MAAM;AACtB,cAAU,IAAI;;GAEjB,CACF;AAED,iBAAe,UAAU;EAGzB,MAAM,aAAa,gBAAgB,cAAc,QAAQ;AACzD,MAAI,YAAY;AACd,kBAAe,WAAW,YAAY;AACtC,mBAAgB,WAAW,aAAa;AACxC,cAAW,WAAW,QAAQ;AAC9B,YAAS,WAAW,MAAM;;AAI5B,eAAa;AACX,gBAAa;AACb,kBAAe,UAAU;;IAE1B,KAAK;AAIR,KAAI,CAFW,WAEJ,CAAC,WAAW,CACrB,QAAO;EACL,MAAM,OAAO,aAA8B;EAG3C,aAAa;EACb,cAAc;EACd,SAAS;EACT,OAAO,KAAA;EACP,iBAAiB;EAGjB,kBAAkB;EAGnB;AAGH,QAAO;EACL,MAAM,OAAO,YAA6B;AACxC,SAAM,gBAAgB,KAAK,QAAQ,SAAS,QAAQ;;EAEtD;EACA;EACA;EACA;EACA,iBAAiB;AACI,mBAAgB,cAAc,QACvC,EAAE,WAAW;;EAEzB,kBAAkB;AAChB,kBAAe,WAAW;;EAE7B"}
|