akanjs 3.0.0-alpha.2 → 3.0.0-alpha.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/base/symbols.ts +0 -1
- package/client/cn.ts +7 -0
- package/client/csrTypes.ts +2 -0
- package/client/frameDebug.ts +3 -2
- package/constant/cascadePaths.ts +8 -79
- package/document/database.ts +1 -2
- package/document/databaseRegistry.ts +2 -2
- package/document/into.ts +6 -10
- package/package.json +1 -1
- package/server/SSR_MEMORY_DIAGNOSIS.md +2 -19
- package/server/cachePolicy.ts +12 -114
- package/server/di/diLifecycle.ts +10 -8
- package/server/processMetricsCollector.ts +0 -4
- package/server/resolver/database.resolver.ts +3 -23
- package/server/resolver/index.ts +0 -1
- package/server/resolver/service.resolver.ts +25 -9
- package/server/rscWorker.tsx +4 -32
- package/server/rscWorkerHost.ts +12 -61
- package/server/ssrFromRscRenderer.tsx +2 -2
- package/server/webRouter.ts +96 -9
- package/service/ipcTypes.ts +0 -25
- package/service/predefinedAdaptor/database.adaptor.ts +3 -3
- package/service/serve.ts +1 -5
- package/service/types.ts +0 -2
- package/types/base/symbols.d.ts +0 -1
- package/types/client/cn.d.ts +5 -0
- package/types/client/csrTypes.d.ts +2 -0
- package/types/constant/cascadePaths.d.ts +4 -22
- package/types/document/database.d.ts +1 -2
- package/types/document/databaseRegistry.d.ts +2 -2
- package/types/document/into.d.ts +5 -6
- package/types/server/cachePolicy.d.ts +2 -33
- package/types/server/resolver/database.resolver.d.ts +2 -6
- package/types/server/resolver/index.d.ts +0 -1
- package/types/server/resolver/service.resolver.d.ts +3 -3
- package/types/server/rscWorkerHost.d.ts +0 -13
- package/types/service/ipcTypes.d.ts +0 -24
- package/types/service/predefinedAdaptor/database.adaptor.d.ts +2 -2
- package/types/service/types.d.ts +1 -2
- package/types/ui/Layout/BottomInset.d.ts +2 -1
- package/types/ui/Layout/index.d.ts +1 -1
- package/types/ui/Signal/style.d.ts +1 -1
- package/types/ui/UiOverride/context.d.ts +12 -2
- package/types/ui/recipe/badgeRecipe.d.ts +7 -3
- package/types/ui/recipe/buttonRecipe.d.ts +7 -3
- package/types/ui/recipe/inputRecipe.d.ts +5 -1
- package/types/webkit/useCsrValues.d.ts +3 -3
- package/types/webkit/useFrameRuntime.d.ts +6 -1
- package/ui/Badge.tsx +2 -2
- package/ui/Button.tsx +3 -1
- package/ui/Constant/Doc.tsx +1 -1
- package/ui/Data/ListContainer.tsx +1 -1
- package/ui/DatePicker.tsx +4 -3
- package/ui/DraggableList.tsx +3 -1
- package/ui/Dropdown.tsx +4 -2
- package/ui/Field.tsx +17 -9
- package/ui/Input.tsx +7 -5
- package/ui/Layout/BottomInset.tsx +6 -1
- package/ui/Loading/ProgressBar.tsx +8 -1
- package/ui/Menu.tsx +7 -8
- package/ui/Model/SureToRemove.tsx +2 -1
- package/ui/ObjectId.tsx +3 -4
- package/ui/Pagination.tsx +3 -4
- package/ui/Popconfirm.tsx +5 -7
- package/ui/Select.tsx +1 -1
- package/ui/System/CSR.tsx +6 -2
- package/ui/ToggleSelect.tsx +4 -3
- package/ui/UiOverride/context.ts +12 -2
- package/ui/recipe/badgeRecipe.ts +22 -3
- package/ui/recipe/buttonRecipe.ts +51 -2
- package/ui/recipe/factory.ts +2 -2
- package/ui/recipe/inputRecipe.ts +18 -4
- package/webkit/useCsrValues.ts +121 -4
- package/webkit/useFrameRuntime.ts +42 -12
- package/server/resolver/CascadeRunner.ts +0 -201
- package/types/server/resolver/CascadeRunner.d.ts +0 -13
|
@@ -78,45 +78,14 @@ export declare function shouldStoreRouteCache(input: {
|
|
|
78
78
|
}): RouteCacheRenderState;
|
|
79
79
|
export declare function hasRouteCacheInvalidationScope(invalidation?: RouteCacheInvalidation): boolean;
|
|
80
80
|
export declare function shouldInvalidateRouteCacheEntry(metadata: RouteCacheMetadata, invalidation: RouteCacheInvalidation): boolean;
|
|
81
|
-
export interface LruTtlCacheOptions<T> {
|
|
82
|
-
/**
|
|
83
|
-
* Measures one entry's payload. Entry count alone says nothing about a cache whose entries span
|
|
84
|
-
* three orders of magnitude, and a byte ceiling needs a running total to enforce. The default
|
|
85
|
-
* reports 0 rather than guessing, so `byteSize` stays honest about not knowing.
|
|
86
|
-
*/
|
|
87
|
-
sizeOf?: (value: T) => number;
|
|
88
|
-
/** Total payload ceiling. 0 leaves the cache bounded only by `maxEntries`. */
|
|
89
|
-
maxBytes?: number;
|
|
90
|
-
/** An entry over this is not stored at all, rather than evicting everything else to fit it. */
|
|
91
|
-
maxEntryBytes?: number;
|
|
92
|
-
/**
|
|
93
|
-
* Cadence of the idle sweep. Without one a filled cache never shrinks: an entry is dropped only
|
|
94
|
-
* when its own key is fetched after expiry or when a write evicts it, so a pod that stops
|
|
95
|
-
* serving holds its peak forever — measured at 100 entries / 21.4 MiB still resident 310s after
|
|
96
|
-
* the last request, with a 30s TTL. 0 disables it.
|
|
97
|
-
*/
|
|
98
|
-
sweepIntervalMs?: number;
|
|
99
|
-
}
|
|
100
81
|
export declare class LruTtlCache<T> {
|
|
101
82
|
#private;
|
|
102
83
|
readonly maxEntries: number;
|
|
103
|
-
constructor(maxEntries?: number
|
|
84
|
+
constructor(maxEntries?: number);
|
|
104
85
|
get size(): number;
|
|
105
|
-
get byteSize(): number;
|
|
106
86
|
get(key: string): T | null;
|
|
107
|
-
|
|
108
|
-
set(key: string, value: T, ttlSeconds: number): boolean;
|
|
109
|
-
/**
|
|
110
|
-
* Drops every expired entry. Deliberately a full scan rather than a walk from the oldest that
|
|
111
|
-
* stops at the first live entry: insertion order is *LRU* order because `get` reinserts, and TTLs
|
|
112
|
-
* differ per entry, so expiry is not monotonic in map order and an early break would leave
|
|
113
|
-
* expired entries behind. The map is bounded by `maxEntries`, so the scan is cheap.
|
|
114
|
-
*/
|
|
115
|
-
sweepExpired(now?: number): number;
|
|
116
|
-
/** Stops the idle sweep. The cache stays usable; only the timer goes away. */
|
|
117
|
-
dispose(): void;
|
|
87
|
+
set(key: string, value: T, ttlSeconds: number): void;
|
|
118
88
|
delete(key: string): boolean;
|
|
119
89
|
invalidate(predicate: (key: string, value: T) => boolean): number;
|
|
120
90
|
clear(): void;
|
|
121
|
-
static parseByteCeiling(value: string | undefined | null, fallback?: number): number;
|
|
122
91
|
}
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
import { type ConstantModel } from "akanjs/constant";
|
|
2
|
-
import { type DatabaseInstance, type DatabaseModel
|
|
2
|
+
import { type DatabaseInstance, type DatabaseModel } from "akanjs/document";
|
|
3
3
|
import { type AdaptorCls } from "akanjs/service";
|
|
4
4
|
export declare class DatabaseResolver {
|
|
5
|
-
|
|
6
|
-
static resolveDatabase(constant: ConstantModel, database: DatabaseModel): {
|
|
7
|
-
adaptor: AdaptorCls<DatabaseInstance>;
|
|
8
|
-
schema: DocumentSchema;
|
|
9
|
-
};
|
|
5
|
+
static resolveDatabase(constant: ConstantModel, database: DatabaseModel): AdaptorCls<DatabaseInstance>;
|
|
10
6
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import { type ConstantModel } from "akanjs/constant";
|
|
1
2
|
import { type DatabaseModel } from "akanjs/document";
|
|
2
|
-
import type { ServiceCls } from "akanjs/service";
|
|
3
|
-
import type { CascadeRunner } from "./CascadeRunner.d.ts";
|
|
3
|
+
import type { DatabaseService, ServiceCls } from "akanjs/service";
|
|
4
4
|
export declare class ServiceResolver {
|
|
5
5
|
#private;
|
|
6
|
-
static resolveDatabaseService(database: DatabaseModel, srvRef: ServiceCls,
|
|
6
|
+
static resolveDatabaseService(constant: ConstantModel, database: DatabaseModel, srvRef: ServiceCls, getService: (refName: string) => DatabaseService): ServiceCls;
|
|
7
7
|
}
|
|
@@ -49,19 +49,6 @@ export declare function nextRscHostPendingChunkCount(currentPendingChunks: numbe
|
|
|
49
49
|
export declare function isRscHostPendingChunkOverflow(pendingChunks: number, maxPendingChunks: number): boolean;
|
|
50
50
|
export declare function createIdempotentRscRenderCancel(onCancel: (reason?: unknown) => void): (reason?: unknown) => void;
|
|
51
51
|
export declare function createRscWorkerInvalidateCacheMessage(invalidation?: string | RouteCacheInvalidation): RscWorkerInvalidateCacheMessage;
|
|
52
|
-
/**
|
|
53
|
-
* The RSC worker samples its own process with the same `ProcessMetricsCollector.collect` its host
|
|
54
|
-
* replica uses, so its report carries `pid` / `rssBytes` / `role` / … under the identical names.
|
|
55
|
-
* `AkanServer` spreads the host's report into its own `collect({ role, ...webRouter.getMetrics() })`
|
|
56
|
-
* and `collect` spreads `extra` last (`processMetricsCollector.ts:122`) — so passing these through
|
|
57
|
-
* unprefixed silently overwrote the replica's own process fields, and the replica's RSS was reported
|
|
58
|
-
* nowhere. Rename them onto `rscWorker*`; the `rsc*` render counters, which only the worker
|
|
59
|
-
* produces, pass through untouched.
|
|
60
|
-
*
|
|
61
|
-
* XXX A new process-level field in `ProcessMetricsCollector.collect` must be added here too, or it
|
|
62
|
-
* starts shadowing the replica again.
|
|
63
|
-
*/
|
|
64
|
-
export declare function projectRscWorkerProcessMetrics(metrics: AkanMetricsReport): AkanMetricsReport;
|
|
65
52
|
export declare function createRscHostRenderStream(input: {
|
|
66
53
|
setPending: (pending: RscPending) => void;
|
|
67
54
|
deletePending: () => void;
|
|
@@ -59,25 +59,6 @@ export interface AkanMetricsReport {
|
|
|
59
59
|
rscWorkerRestartCount?: number;
|
|
60
60
|
rscWorkerRecycleCount?: number;
|
|
61
61
|
rscWorkerLastRecycleReason?: string;
|
|
62
|
-
rscWorkerReportedAt?: number;
|
|
63
|
-
rscWorkerRssBytes?: number;
|
|
64
|
-
rscWorkerHeapTotalBytes?: number;
|
|
65
|
-
rscWorkerHeapUsedBytes?: number;
|
|
66
|
-
rscWorkerExternalBytes?: number;
|
|
67
|
-
rscWorkerArrayBuffersBytes?: number;
|
|
68
|
-
rscWorkerCpuUserMicros?: number;
|
|
69
|
-
rscWorkerCpuSystemMicros?: number;
|
|
70
|
-
rscWorkerMaxRssKb?: number;
|
|
71
|
-
rscWorkerJscHeapSizeBytes?: number;
|
|
72
|
-
rscWorkerJscHeapCapacityBytes?: number;
|
|
73
|
-
/** Off-heap bytes JSC attributes to JS objects — typed-array backing stores, i.e. cached Flight chunks. */
|
|
74
|
-
rscWorkerJscExtraMemorySizeBytes?: number;
|
|
75
|
-
rscWorkerJscObjectCount?: number;
|
|
76
|
-
rscWorkerJscProtectedObjectCount?: number;
|
|
77
|
-
rscWorkerEventLoopLagMeanMs?: number;
|
|
78
|
-
rscWorkerEventLoopLagP99Ms?: number;
|
|
79
|
-
rscWorkerEventLoopLagMaxMs?: number;
|
|
80
|
-
rscWorkerGcDurationMs?: number;
|
|
81
62
|
rscPendingRenderCount?: number;
|
|
82
63
|
rscQueuedSendCount?: number;
|
|
83
64
|
rscHostPendingChunkOverflowCount?: number;
|
|
@@ -113,13 +94,9 @@ export interface AkanMetricsReport {
|
|
|
113
94
|
rscLastRenderLoadedModuleDelta?: number;
|
|
114
95
|
rscLastRenderLoadedModules?: string[];
|
|
115
96
|
rscResultCacheEntries?: number;
|
|
116
|
-
rscResultCacheBytes?: number;
|
|
117
|
-
rscPatchResultCacheEntries?: number;
|
|
118
|
-
rscPatchResultCacheBytes?: number;
|
|
119
97
|
rscResultCacheHits?: number;
|
|
120
98
|
rscResultCacheMisses?: number;
|
|
121
99
|
rscResultCacheBypass?: number;
|
|
122
|
-
/** Keys the SSR chunk registry tracks — NOT a memory bound; the modules stay in Bun's ESM registry. */
|
|
123
100
|
ssrChunkRegistrySize?: number;
|
|
124
101
|
ssrChunkLoadCount?: number;
|
|
125
102
|
ssrChunkCacheHitCount?: number;
|
|
@@ -130,7 +107,6 @@ export interface AkanMetricsReport {
|
|
|
130
107
|
httpCsrCount?: number;
|
|
131
108
|
httpImageCount?: number;
|
|
132
109
|
httpHtmlCacheEntries?: number;
|
|
133
|
-
httpHtmlCacheBytes?: number;
|
|
134
110
|
httpHtmlCacheHits?: number;
|
|
135
111
|
httpHtmlCacheMisses?: number;
|
|
136
112
|
httpHtmlCacheBypass?: number;
|
|
@@ -56,7 +56,7 @@ export interface DocumentStore {
|
|
|
56
56
|
matchedCount: number;
|
|
57
57
|
modifiedCount: number;
|
|
58
58
|
}>;
|
|
59
|
-
|
|
59
|
+
deleteManyByQuery(query: DocumentQuery): Promise<{
|
|
60
60
|
acknowledged: boolean;
|
|
61
61
|
matchedCount: number;
|
|
62
62
|
modifiedCount: number;
|
|
@@ -309,7 +309,7 @@ export declare class SqlDocumentStore {
|
|
|
309
309
|
matchedCount: number;
|
|
310
310
|
modifiedCount: number;
|
|
311
311
|
}>;
|
|
312
|
-
|
|
312
|
+
deleteManyByQuery(query: DocumentQuery): Promise<{
|
|
313
313
|
acknowledged: boolean;
|
|
314
314
|
matchedCount: number;
|
|
315
315
|
modifiedCount: number;
|
package/types/service/types.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Cls, MergeAllTypes, PromiseOrObject } from "akanjs/base";
|
|
2
2
|
import type { Logger } from "akanjs/common";
|
|
3
3
|
import type { QueryOf } from "akanjs/constant";
|
|
4
|
-
import type { CRUDEventType, DatabaseModel, DataInputOf, FilterInstance, FindQueryOption, GetDocObject, ListQueryOption, QueryMethodPart, SaveEventType
|
|
4
|
+
import type { CRUDEventType, DatabaseModel, DataInputOf, FilterInstance, FindQueryOption, GetDocObject, ListQueryOption, QueryMethodPart, SaveEventType } from "akanjs/document";
|
|
5
5
|
type ServiceMixinOmitKey = "onInit" | "onDestroy" | "_libsOnInit" | "_libsOnDestroy" | "_preCreate" | "_postCreate" | "_preUpdate" | "_postUpdate" | "_preRemove" | "_postRemove" | "_libsPreCreate" | "_libsPostCreate" | "_libsPreUpdate" | "_libsPostUpdate" | "_libsPreRemove" | "_libsPostRemove";
|
|
6
6
|
type DatabaseQueryMethods<Query, Sort, Obj, Doc, Insight, FindQueryOption, ListQueryOption, DocQuery> = QueryMethodPart<Query, Sort, Obj, Doc, Insight, FindQueryOption, ListQueryOption, DocQuery>;
|
|
7
7
|
type DocumentLike = {
|
|
@@ -31,7 +31,6 @@ export type DatabaseService<T extends string = string, Input = any, Doc = any, O
|
|
|
31
31
|
__create: (data: _DataInputOfDoc) => Promise<Doc>;
|
|
32
32
|
__update: (id: string, data: Partial<Doc>) => Promise<Doc>;
|
|
33
33
|
__remove: (id: string) => Promise<Doc>;
|
|
34
|
-
__removeMany: (query: _QueryOfDoc) => Promise<UpdateResult>;
|
|
35
34
|
__list(query?: _QueryOfDoc, queryOption?: _ListQueryOption): Promise<Doc[]>;
|
|
36
35
|
__listIds(query?: _QueryOfDoc, queryOption?: _ListQueryOption): Promise<string[]>;
|
|
37
36
|
__find(query?: _QueryOfDoc, queryOption?: _FindQueryOption): Promise<Doc | null>;
|
|
@@ -4,10 +4,11 @@ export interface BottomInsetProps {
|
|
|
4
4
|
className?: string;
|
|
5
5
|
children: ReactNode;
|
|
6
6
|
keyboardSticky?: boolean;
|
|
7
|
+
contentAnchor?: "bottom";
|
|
7
8
|
role?: "bottomChrome" | "keyboardAccessory";
|
|
8
9
|
estimatedHeight?: number;
|
|
9
10
|
frameScope?: FrameSlotRegistration["scope"];
|
|
10
11
|
frameSource?: FrameSlotRegistration["source"];
|
|
11
12
|
frameCache?: boolean;
|
|
12
13
|
}
|
|
13
|
-
export declare const BottomInset: ({ className, children, keyboardSticky, role, estimatedHeight, frameScope, frameSource, frameCache, }: BottomInsetProps) => import("react/jsx-runtime").JSX.Element;
|
|
14
|
+
export declare const BottomInset: ({ className, children, keyboardSticky, contentAnchor, role, estimatedHeight, frameScope, frameSource, frameCache, }: BottomInsetProps) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -4,7 +4,7 @@ export declare const Layout: {
|
|
|
4
4
|
Navbar: ({ back, className, height, children, title, left, right }: import("./Navbar.d.ts").NavbarProps) => import("react/jsx-runtime").JSX.Element;
|
|
5
5
|
TopInset: ({ className, children, estimatedHeight }: import("./TopInset.d.ts").TopInsetProps) => import("react/jsx-runtime").JSX.Element;
|
|
6
6
|
BottomTab: ({ className, tabs, height }: import("./BottomTab.d.ts").BottomTabProps) => import("react/jsx-runtime").JSX.Element;
|
|
7
|
-
BottomInset: ({ className, children, keyboardSticky, role, estimatedHeight, frameScope, frameSource, frameCache, }: import("./BottomInset.d.ts").BottomInsetProps) => import("react/jsx-runtime").JSX.Element;
|
|
7
|
+
BottomInset: ({ className, children, keyboardSticky, contentAnchor, role, estimatedHeight, frameScope, frameSource, frameCache, }: import("./BottomInset.d.ts").BottomInsetProps) => import("react/jsx-runtime").JSX.Element;
|
|
8
8
|
Template: ({ className, children }: import("./Template.d.ts").TemplateProps) => import("react/jsx-runtime").JSX.Element;
|
|
9
9
|
Unit: ({ className, children, href }: import("./Unit.d.ts").UnitProps) => import("react/jsx-runtime").JSX.Element;
|
|
10
10
|
View: ({ className, children }: import("./View.d.ts").ViewProps) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -11,4 +11,4 @@ export declare const signalUi: {
|
|
|
11
11
|
export declare const getEndpointBadgeClassName: (type: string) => string;
|
|
12
12
|
export declare const getGuardBadgeClassName: (guard: string) => string;
|
|
13
13
|
export declare const getStatusBadgeClassName: (status: string) => string;
|
|
14
|
-
export declare const getStatusTextareaClassName: (status: string) => "" | "pointer-events-none opacity-50" | "border-destructive text-destructive"
|
|
14
|
+
export declare const getStatusTextareaClassName: (status: string) => "" | "border-primary" | "pointer-events-none opacity-50" | "border-destructive text-destructive";
|
|
@@ -16,7 +16,7 @@ import type { ModalProps } from "../Modal.d.ts";
|
|
|
16
16
|
import type { PaginationProps } from "../Pagination.d.ts";
|
|
17
17
|
import type { PopconfirmProps } from "../Popconfirm.d.ts";
|
|
18
18
|
import type { ItemProps as RadioItemProps, RadioProps } from "../Radio.d.ts";
|
|
19
|
-
import type { BadgeVariants, ButtonVariants } from "../recipe.d.ts";
|
|
19
|
+
import type { BadgeVariants, ButtonVariants, InputSurfaceVariants } from "../recipe.d.ts";
|
|
20
20
|
import type { SelectProps } from "../Select.d.ts";
|
|
21
21
|
import type { TableProps } from "../Table.d.ts";
|
|
22
22
|
import type { MultiProps as ToggleSelectMultiProps, ToggleSelectProps } from "../ToggleSelect.d.ts";
|
|
@@ -75,11 +75,21 @@ export type AkanModalComponent = AkanUiOverrides["Modal"];
|
|
|
75
75
|
* manifest: `override({ recipes: { button: neonButtonRecipe } })`. A recipe swap changes
|
|
76
76
|
* only the className factory — the component's structure/behavior (async states, focus,
|
|
77
77
|
* a11y) is untouched. Each replacement must accept the framework recipe's full variant
|
|
78
|
-
* contract, so every call site keeps working
|
|
78
|
+
* contract, so every call site keeps working (a replacement with *extra* optional axes is
|
|
79
|
+
* assignable — contravariance — but those axes are only reachable from code that knows the
|
|
80
|
+
* replacement's own type).
|
|
81
|
+
*
|
|
82
|
+
* Scope: a recipe slot is a **client-side, route-scoped restyle**. It reaches framework
|
|
83
|
+
* client components, which resolve through `useUiRecipe(...)`. It never reaches a raw
|
|
84
|
+
* `xRecipe(...)` call in app JSX (statically imported — no context), nor server components
|
|
85
|
+
* (`Unit`/`View`), which intentionally render the canonical framework recipe. Extending the
|
|
86
|
+
* *vocabulary* is not this slot's job: add the axis to the framework recipe, or author an
|
|
87
|
+
* app recipe in `apps/<app>/ui/Recipe/`.
|
|
79
88
|
*/
|
|
80
89
|
export interface AkanUiRecipes {
|
|
81
90
|
button: (variants?: ButtonVariants, className?: ClassValue) => string;
|
|
82
91
|
badge: (variants?: BadgeVariants, className?: ClassValue) => string;
|
|
92
|
+
input: (variants?: InputSurfaceVariants, className?: ClassValue) => string;
|
|
83
93
|
}
|
|
84
94
|
/** Shape of an `_overrides.tsx` manifest: component slots plus an optional recipe-slot map. */
|
|
85
95
|
export type AkanUiOverrideManifest = Partial<AkanUiOverrides> & {
|
|
@@ -1,11 +1,15 @@
|
|
|
1
|
-
/** 뱃지 look — 시맨틱 variant
|
|
1
|
+
/** 뱃지 look — 시맨틱 variant × size, outline 플래그는 색을 유지한 외곽선 스타일. `<Badge>` 가 소비하며, recipes.badge 슬롯으로 교체 가능. */
|
|
2
2
|
export declare const badgeRecipe: (variants?: Omit<({
|
|
3
|
-
|
|
3
|
+
size?: "lg" | "md" | "sm" | "xs" | undefined;
|
|
4
|
+
outline?: boolean | undefined;
|
|
5
|
+
variant?: "default" | "error" | "info" | "warning" | "success" | "primary" | "secondary" | "accent" | "neutral" | "outline" | undefined;
|
|
4
6
|
} & {
|
|
5
7
|
class?: import("tailwind-variants").ClassValue;
|
|
6
8
|
className?: never;
|
|
7
9
|
}) | ({
|
|
8
|
-
|
|
10
|
+
size?: "lg" | "md" | "sm" | "xs" | undefined;
|
|
11
|
+
outline?: boolean | undefined;
|
|
12
|
+
variant?: "default" | "error" | "info" | "warning" | "success" | "primary" | "secondary" | "accent" | "neutral" | "outline" | undefined;
|
|
9
13
|
} & {
|
|
10
14
|
class?: never;
|
|
11
15
|
className?: import("tailwind-variants").ClassValue;
|
|
@@ -1,13 +1,17 @@
|
|
|
1
|
-
/** 버튼 look — 시맨틱 variant × size
|
|
1
|
+
/** 버튼 look — 시맨틱 variant × size × shape, outline 플래그는 색을 유지한 외곽선 스타일. `<Button>` 이 소비하며, `_overrides.tsx` 의 recipes.button 슬롯으로 교체 가능. */
|
|
2
2
|
export declare const buttonRecipe: (variants?: Omit<({
|
|
3
3
|
size?: "lg" | "md" | "sm" | "xs" | "icon" | undefined;
|
|
4
|
-
|
|
4
|
+
outline?: boolean | undefined;
|
|
5
|
+
variant?: "link" | "default" | "info" | "warning" | "success" | "primary" | "secondary" | "accent" | "destructive" | "neutral" | "outline" | "ghost" | undefined;
|
|
6
|
+
shape?: "default" | "circle" | "square" | undefined;
|
|
5
7
|
} & {
|
|
6
8
|
class?: import("tailwind-variants").ClassValue;
|
|
7
9
|
className?: never;
|
|
8
10
|
}) | ({
|
|
9
11
|
size?: "lg" | "md" | "sm" | "xs" | "icon" | undefined;
|
|
10
|
-
|
|
12
|
+
outline?: boolean | undefined;
|
|
13
|
+
variant?: "link" | "default" | "info" | "warning" | "success" | "primary" | "secondary" | "accent" | "destructive" | "neutral" | "outline" | "ghost" | undefined;
|
|
14
|
+
shape?: "default" | "circle" | "square" | undefined;
|
|
11
15
|
} & {
|
|
12
16
|
class?: never;
|
|
13
17
|
className?: import("tailwind-variants").ClassValue;
|
|
@@ -1,11 +1,15 @@
|
|
|
1
|
-
/** 입력 표면 look — Input/TextArea 가 공유하는 필드 셸. kind 로 한 줄 필드(field)/멀티라인(area)
|
|
1
|
+
/** 입력 표면 look — Input/TextArea/Select 가 공유하는 필드 셸. kind 로 한 줄 필드(field)/멀티라인(area), tone 으로 강조/오류 상태를 고른다. */
|
|
2
2
|
export declare const inputRecipe: (variants?: Omit<({
|
|
3
|
+
size?: "xl" | "lg" | "md" | "sm" | "xs" | undefined;
|
|
3
4
|
kind?: "field" | "area" | undefined;
|
|
5
|
+
tone?: "default" | "error" | "primary" | undefined;
|
|
4
6
|
} & {
|
|
5
7
|
class?: import("tailwind-variants").ClassValue;
|
|
6
8
|
className?: never;
|
|
7
9
|
}) | ({
|
|
10
|
+
size?: "xl" | "lg" | "md" | "sm" | "xs" | undefined;
|
|
8
11
|
kind?: "field" | "area" | undefined;
|
|
12
|
+
tone?: "default" | "error" | "primary" | undefined;
|
|
9
13
|
} & {
|
|
10
14
|
class?: never;
|
|
11
15
|
className?: import("tailwind-variants").ClassValue;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { type NavigationIntent, type PathRoute, type RouteGuide, type RouterInstance, type TransitionType } from "akanjs/client";
|
|
1
|
+
import { type NavigationIntent, type PageTransition, type PathRoute, type RouteGuide, type RouterInstance, type TransitionType } from "akanjs/client";
|
|
2
2
|
export declare const useCsrValues: (rootRouteGuide: RouteGuide, pathRoutes: PathRoute[]) => {
|
|
3
|
+
page: PageTransition | null;
|
|
3
4
|
topSafeArea: import("akanjs/client").SafeAreaTransition | null;
|
|
4
|
-
|
|
5
|
-
prevPage: import("akanjs/client").PageTransition | null;
|
|
5
|
+
prevPage: PageTransition | null;
|
|
6
6
|
topInset: import("akanjs/client").ContainerTransition | null;
|
|
7
7
|
bottomInset: import("akanjs/client").ContainerTransition | null;
|
|
8
8
|
topLeftAction: import("akanjs/client").ContainerTransition | null;
|
|
@@ -4,7 +4,10 @@ export type FrameSlotBucket = "active" | "pending";
|
|
|
4
4
|
export type FrameSlotMapByBucket = Record<FrameSlotBucket, FrameSlotMap>;
|
|
5
5
|
export declare const PENDING_FRAME_READY_TIMEOUT_MS = 80;
|
|
6
6
|
export declare const PENDING_FRAME_READY_MAX_TIMEOUT_MS = 120;
|
|
7
|
-
export declare const
|
|
7
|
+
export declare const KEYBOARD_SHOW_ANIMATION_DURATION_MS = 420;
|
|
8
|
+
export declare const KEYBOARD_HIDE_ANIMATION_DURATION_MS = 90;
|
|
9
|
+
export declare const KEYBOARD_SHOW_ANIMATION_EASING = "cubic-bezier(0.16, 1, 0.3, 1)";
|
|
10
|
+
export declare const KEYBOARD_HIDE_ANIMATION_EASING = "cubic-bezier(0.7, 0, 0.84, 0)";
|
|
8
11
|
export declare const FRAME_Z_INDEX: {
|
|
9
12
|
readonly page: 10;
|
|
10
13
|
readonly previousPage: 0;
|
|
@@ -39,6 +42,7 @@ export declare function useFrameViewport(): {
|
|
|
39
42
|
export declare function getKeyboardAccessorySlots(path: string, frameSlots: FrameSlotMap): FrameSlotRegistration[];
|
|
40
43
|
export declare function resolveKeyboardAccessoryHeight(path: string, frameSlots: FrameSlotMap): number;
|
|
41
44
|
export declare function hasKeyboardStickySlot(path: string, frameSlots: FrameSlotMap): boolean;
|
|
45
|
+
export declare function hasBottomAnchoredKeyboardSlot(path: string, frameSlots: FrameSlotMap): boolean;
|
|
42
46
|
export declare function resolveKeyboardFrame({ keyboardHeight, bottomSafeArea, visualViewportKeyboardHeight, platformProfile, sticky, freeze, }: {
|
|
43
47
|
keyboardHeight: number;
|
|
44
48
|
bottomSafeArea: number;
|
|
@@ -57,6 +61,7 @@ export declare function useKeyboardFrame({ bottomSafeArea, sticky, viewport, pla
|
|
|
57
61
|
bottomSafeArea: number;
|
|
58
62
|
sticky: boolean;
|
|
59
63
|
viewport: {
|
|
64
|
+
height: number;
|
|
60
65
|
visualHeight: number;
|
|
61
66
|
visualOffsetTop: number;
|
|
62
67
|
};
|
package/ui/Badge.tsx
CHANGED
|
@@ -7,10 +7,10 @@ export { badgeRecipe, type BadgeVariants };
|
|
|
7
7
|
|
|
8
8
|
export type BadgeProps = HTMLAttributes<HTMLSpanElement> & BadgeVariants;
|
|
9
9
|
|
|
10
|
-
const DefaultBadge = ({ className, variant, ...rest }: BadgeProps) => {
|
|
10
|
+
const DefaultBadge = ({ className, variant, size, outline, ...rest }: BadgeProps) => {
|
|
11
11
|
|
|
12
12
|
const recipe = useUiRecipe("badge") ?? badgeRecipe;
|
|
13
|
-
return <span className={recipe({ variant }, className)} {...rest} />;
|
|
13
|
+
return <span className={recipe({ variant, size, outline }, className)} {...rest} />;
|
|
14
14
|
};
|
|
15
15
|
|
|
16
16
|
/** Status/label pill. Route-overridable via `page/**\/_overrides.tsx` (slot `Badge`). */
|
package/ui/Button.tsx
CHANGED
|
@@ -61,6 +61,8 @@ const DefaultButton = <Result = unknown>({
|
|
|
61
61
|
className,
|
|
62
62
|
variant,
|
|
63
63
|
size,
|
|
64
|
+
shape,
|
|
65
|
+
outline,
|
|
64
66
|
type = "button",
|
|
65
67
|
loadingMode = "hold",
|
|
66
68
|
showError = true,
|
|
@@ -84,7 +86,7 @@ const DefaultButton = <Result = unknown>({
|
|
|
84
86
|
<button
|
|
85
87
|
type={type}
|
|
86
88
|
className={recipe(
|
|
87
|
-
{ variant, size },
|
|
89
|
+
{ variant, size, shape, outline },
|
|
88
90
|
|
|
89
91
|
cn(loadingMode === "hold" && "relative", busy && !rest.disabled && "disabled:opacity-100", className),
|
|
90
92
|
)}
|
package/ui/Constant/Doc.tsx
CHANGED
|
@@ -238,7 +238,7 @@ const EnumList = ({ enums = getConstantSchemaDoc().enums }: EnumProps) => {
|
|
|
238
238
|
const { l } = usePage();
|
|
239
239
|
return (
|
|
240
240
|
<div className="overflow-x-auto rounded-xl bg-muted p-3">
|
|
241
|
-
<table className=
|
|
241
|
+
<table className={tableClass}>
|
|
242
242
|
<thead>
|
|
243
243
|
<tr>
|
|
244
244
|
<th>Key</th>
|
|
@@ -268,7 +268,7 @@ export default function ListContainer<
|
|
|
268
268
|
<div className={cn("m-4", className)}>
|
|
269
269
|
<div className="mb-3 flex flex-wrap justify-between">
|
|
270
270
|
<div className="flex pb-1">
|
|
271
|
-
<p className="
|
|
271
|
+
<p className="text-lg">
|
|
272
272
|
{title ?? l._(`${sliceName}.modelName`)}({modelInsight.count})
|
|
273
273
|
</p>
|
|
274
274
|
<div className="ml-3 flex items-center">
|
package/ui/DatePicker.tsx
CHANGED
|
@@ -5,6 +5,7 @@ import { lazy } from "akanjs/webkit";
|
|
|
5
5
|
import { type FocusEvent, useEffect, useRef } from "react";
|
|
6
6
|
import { AiOutlineSwapRight } from "react-icons/ai";
|
|
7
7
|
|
|
8
|
+
import { inputRecipe } from "./recipe";
|
|
8
9
|
import { createOverridable } from "./UiOverride";
|
|
9
10
|
|
|
10
11
|
const reactDatePickerPackage = "react-datepicker";
|
|
@@ -58,7 +59,7 @@ const DefaultDatePicker = ({
|
|
|
58
59
|
|
|
59
60
|
return (
|
|
60
61
|
<ReactDatePicker
|
|
61
|
-
className={
|
|
62
|
+
className={inputRecipe({}, ["text-center", className])}
|
|
62
63
|
selected={value ? value.toDate() : new Date()}
|
|
63
64
|
disabledKeyboardNavigation
|
|
64
65
|
onFocus={(e: FocusEvent<HTMLInputElement>) => {
|
|
@@ -110,9 +111,9 @@ const DefaultRangePicker = ({
|
|
|
110
111
|
onChange([value[0] ?? dayjs(), dayjs(date)]);
|
|
111
112
|
};
|
|
112
113
|
|
|
113
|
-
const pickerClassName = "m-0
|
|
114
|
+
const pickerClassName = "m-0 h-full w-full border-none bg-transparent p-3 text-center focus:outline-hidden z-50";
|
|
114
115
|
return (
|
|
115
|
-
<div className={
|
|
116
|
+
<div className={inputRecipe({}, ["flex h-full w-fit items-center gap-2 p-0", className])}>
|
|
116
117
|
<ReactDatePicker
|
|
117
118
|
className={pickerClassName}
|
|
118
119
|
selected={value[0] ? value[0].toDate() : undefined}
|
package/ui/DraggableList.tsx
CHANGED
|
@@ -7,6 +7,7 @@ import { createContext, type ReactElement, type ReactNode, useContext, useRef }
|
|
|
7
7
|
import { BiTrash } from "react-icons/bi";
|
|
8
8
|
import { MdDragIndicator } from "react-icons/md";
|
|
9
9
|
import { buttonRecipe } from "./Button";
|
|
10
|
+
import { useUiRecipe } from "./UiOverride";
|
|
10
11
|
|
|
11
12
|
const clamp = (value: number, min: number, max: number) => Math.min(Math.max(value, min), max);
|
|
12
13
|
|
|
@@ -169,6 +170,7 @@ const Item = ({
|
|
|
169
170
|
removeClassName,
|
|
170
171
|
}: ItemProps) => {
|
|
171
172
|
const { onRemove } = useDragList();
|
|
173
|
+
const recipe = useUiRecipe("button") ?? buttonRecipe;
|
|
172
174
|
return (
|
|
173
175
|
<div className={cn("flex w-full items-center gap-2", className)}>
|
|
174
176
|
{cursor ? (
|
|
@@ -179,7 +181,7 @@ const Item = ({
|
|
|
179
181
|
{children}
|
|
180
182
|
{removable && (
|
|
181
183
|
<button
|
|
182
|
-
className={
|
|
184
|
+
className={recipe({ variant: "outline", size: "xs" }, [
|
|
183
185
|
"size-6 border-destructive p-0 text-destructive hover:bg-destructive hover:text-destructive-foreground",
|
|
184
186
|
removeClassName,
|
|
185
187
|
])}
|
package/ui/Dropdown.tsx
CHANGED
|
@@ -3,7 +3,7 @@ import { cn } from "akanjs/client";
|
|
|
3
3
|
import { type ReactNode, useEffect, useRef, useState } from "react";
|
|
4
4
|
|
|
5
5
|
import { buttonRecipe } from "./Button";
|
|
6
|
-
import { createOverridable } from "./UiOverride";
|
|
6
|
+
import { createOverridable, useUiRecipe } from "./UiOverride";
|
|
7
7
|
|
|
8
8
|
export interface DropdownProps {
|
|
9
9
|
/** Button/trigger content. */
|
|
@@ -21,6 +21,8 @@ export interface DropdownProps {
|
|
|
21
21
|
export const DefaultDropdown = ({ value, content, className, buttonClassName, dropdownClassName }: DropdownProps) => {
|
|
22
22
|
const [opened, setOpened] = useState(false);
|
|
23
23
|
const ref = useRef<HTMLDivElement>(null);
|
|
24
|
+
|
|
25
|
+
const recipe = useUiRecipe("button") ?? buttonRecipe;
|
|
24
26
|
useEffect(() => {
|
|
25
27
|
if (!opened) return;
|
|
26
28
|
const onClick = (e: MouseEvent) => {
|
|
@@ -35,7 +37,7 @@ export const DefaultDropdown = ({ value, content, className, buttonClassName, dr
|
|
|
35
37
|
<div ref={ref} className={cn("relative inline-block", className)}>
|
|
36
38
|
<button
|
|
37
39
|
type="button"
|
|
38
|
-
className={
|
|
40
|
+
className={recipe({ variant: "ghost" }, ["flex", buttonClassName])}
|
|
39
41
|
onClick={() => {
|
|
40
42
|
setOpened((o) => !o);
|
|
41
43
|
}}
|
package/ui/Field.tsx
CHANGED
|
@@ -13,10 +13,12 @@ import { badgeRecipe } from "./Badge";
|
|
|
13
13
|
import { buttonRecipe } from "./Button";
|
|
14
14
|
import { DraggableList } from "./DraggableList";
|
|
15
15
|
import { Input } from "./Input";
|
|
16
|
+
import { inputRecipe } from "./recipe";
|
|
16
17
|
import { Select } from "./Select";
|
|
17
18
|
import { Switch as UiSwitch } from "./Switch";
|
|
18
19
|
import { ToggleSelect as UtilToggleSelect } from "./ToggleSelect";
|
|
19
20
|
import { Tooltip } from "./Tooltip";
|
|
21
|
+
import { useUiRecipe } from "./UiOverride";
|
|
20
22
|
|
|
21
23
|
interface LabelProps {
|
|
22
24
|
className?: string;
|
|
@@ -101,6 +103,7 @@ const List = <Item,>({
|
|
|
101
103
|
renderItem,
|
|
102
104
|
}: ListProps<Item>) => {
|
|
103
105
|
const { l } = usePage();
|
|
106
|
+
const recipe = useUiRecipe("button") ?? buttonRecipe;
|
|
104
107
|
return (
|
|
105
108
|
<div className={cn("flex w-full flex-col", className)}>
|
|
106
109
|
{label ? <Label className={labelClassName} nullable={nullable} label={label} desc={desc} /> : null}
|
|
@@ -111,7 +114,7 @@ const List = <Item,>({
|
|
|
111
114
|
{renderItem(item, idx)}
|
|
112
115
|
<div className="flex gap-2 border-border border-l pl-2">
|
|
113
116
|
<button
|
|
114
|
-
className={
|
|
117
|
+
className={recipe(
|
|
115
118
|
{ variant: "outline", size: "icon" },
|
|
116
119
|
"size-6 border-destructive p-0 text-destructive hover:bg-destructive hover:text-destructive-foreground",
|
|
117
120
|
)}
|
|
@@ -127,7 +130,7 @@ const List = <Item,>({
|
|
|
127
130
|
</>
|
|
128
131
|
))}
|
|
129
132
|
<button
|
|
130
|
-
className={
|
|
133
|
+
className={recipe({ variant: "outline" })}
|
|
131
134
|
onClick={() => {
|
|
132
135
|
onAdd();
|
|
133
136
|
}}
|
|
@@ -533,6 +536,7 @@ const TextList = ({
|
|
|
533
536
|
inputClassName,
|
|
534
537
|
}: TextListProps) => {
|
|
535
538
|
const { l } = usePage();
|
|
539
|
+
const recipe = useUiRecipe("button") ?? buttonRecipe;
|
|
536
540
|
return (
|
|
537
541
|
<div className={cn("flex flex-col", className)}>
|
|
538
542
|
{label ? <Label className={labelClassName} nullable={!minlength} label={label} desc={desc} /> : null}
|
|
@@ -570,7 +574,7 @@ const TextList = ({
|
|
|
570
574
|
disabled={disabled}
|
|
571
575
|
/>
|
|
572
576
|
<button
|
|
573
|
-
className={
|
|
577
|
+
className={recipe(
|
|
574
578
|
{ variant: "outline", size: "icon" },
|
|
575
579
|
"size-6 border-destructive p-0 text-destructive hover:bg-destructive hover:text-destructive-foreground",
|
|
576
580
|
)}
|
|
@@ -588,7 +592,7 @@ const TextList = ({
|
|
|
588
592
|
<div className="my-5 h-[0.5px] bg-foreground/20" />
|
|
589
593
|
{value.length <= maxTextlength ? (
|
|
590
594
|
<button
|
|
591
|
-
className={
|
|
595
|
+
className={recipe({ variant: "outline" }, "w-full")}
|
|
592
596
|
onClick={() => {
|
|
593
597
|
onChange([...value, ""]);
|
|
594
598
|
}}
|
|
@@ -638,6 +642,7 @@ const Tags = ({
|
|
|
638
642
|
inputClassName,
|
|
639
643
|
}: TagsProps) => {
|
|
640
644
|
const { l } = usePage();
|
|
645
|
+
const badge = useUiRecipe("badge") ?? badgeRecipe;
|
|
641
646
|
const [inputVisible, setInputVisible] = useState(false);
|
|
642
647
|
const [tag, setTag] = useState("");
|
|
643
648
|
const addTag = () => {
|
|
@@ -652,7 +657,7 @@ const Tags = ({
|
|
|
652
657
|
{label ? <Label className={labelClassName} nullable={!minlength} label={label} desc={desc} /> : null}
|
|
653
658
|
<div className="flex w-full flex-wrap items-center gap-1 rounded-md border border-foreground/20 p-2">
|
|
654
659
|
{value.map((val, idx) => (
|
|
655
|
-
<span className={
|
|
660
|
+
<span className={badge({ variant: "outline" }, "items-center")} key={idx}>
|
|
656
661
|
<div className="text-xs italic">#</div>
|
|
657
662
|
{val}
|
|
658
663
|
<BiX
|
|
@@ -733,13 +738,16 @@ const Date = <Nullable extends boolean>({
|
|
|
733
738
|
return (
|
|
734
739
|
<div className={cn("flex flex-col", className)}>
|
|
735
740
|
{label ? <Label className={labelClassName} nullable={nullable} label={label} desc={desc} /> : null}
|
|
736
|
-
{/* //!
|
|
741
|
+
{/* //! datetime-local 에 max 를 주면 오른쪽 끝이 잘리는 렌더 이슈가 있어 max 를 넘기지 않는다.
|
|
742
|
+
원래 daisyUI 버그로 기록돼 있었으나 daisyUI 는 제거됐다 — 브라우저 네이티브 동작일 수 있어
|
|
743
|
+
회피책은 유지한다. 실제 렌더 확인 후에만 걷어낼 것. */}
|
|
737
744
|
<input
|
|
738
745
|
type={showTime ? "datetime-local" : "date"}
|
|
739
|
-
className={
|
|
740
|
-
|
|
746
|
+
className={inputRecipe({}, [
|
|
747
|
+
|
|
748
|
+
"user-invalid:border-destructive text-xs outline-none duration-200 focus-within:outline-none focus:outline-none",
|
|
741
749
|
dateClassName,
|
|
742
|
-
)}
|
|
750
|
+
])}
|
|
743
751
|
min={min ? (showTime ? dayjs(min).format("YYYY-MM-DDTHH:mm") : dayjs(min).format("YYYY-MM-DD")) : undefined}
|
|
744
752
|
max={max ? (showTime ? dayjs(max).format("YYYY-MM-DDTHH:mm") : dayjs(max).format("YYYY-MM-DD")) : undefined}
|
|
745
753
|
value={value ? (showTime ? dayjs(value).format("YYYY-MM-DDTHH:mm") : dayjs(value).format("YYYY-MM-DD")) : ""}
|