Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion packages/core/src/fields/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,18 @@ export class FieldDefinition<T = unknown> {
}

toConfig(): FieldConfig {
return structuredClone(this._config)
return cloneWithFunctions(this._config) as FieldConfig
}
}

function cloneWithFunctions(value: unknown): unknown {
if (value === null || typeof value !== 'object') return value
if (typeof value === 'function') return value
if (Array.isArray(value)) return value.map(cloneWithFunctions)
const result: Record<string, unknown> = {}
for (const key of Object.keys(value as object)) {
const v = (value as Record<string, unknown>)[key]
result[key] = typeof v === 'function' ? v : cloneWithFunctions(v)
}
return result
}
30 changes: 26 additions & 4 deletions packages/core/src/fields/list.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { FieldDefinition } from './base'
import type { SchemaProvide } from '../types'
import type { SchemaDefinition } from '../schema'

export class ListFieldDefinition extends FieldDefinition<Record<string, unknown>[]> {
constructor(attrs: Record<string, unknown> = {}) {
super('list', 'array', attrs)
}

itemSchema(schema: SchemaDefinition<any>): this {
this._config.attrs = { ...this._config.attrs, itemSchema: schema.provide() }
itemSchema(schema: SchemaProvide): this {
this._config.attrs = { ...this._config.attrs, itemSchema: schema }
return this
}

Expand All @@ -25,8 +26,29 @@ export class ListFieldDefinition extends FieldDefinition<Record<string, unknown>
this._config.validations = [...this._config.validations, { rule: 'maxItems', params: { value: n } }]
return this
}

events(events: Record<string, unknown>): this {
this._config.attrs = { ...this._config.attrs, events }
return this
}

hooks(hooks: Record<string, unknown>): this {
this._config.attrs = { ...this._config.attrs, hooks }
return this
}

handlers(handlers: Record<string, unknown>): this {
this._config.attrs = { ...this._config.attrs, handlers }
return this
}
}

export function list(attrs?: Record<string, unknown>): ListFieldDefinition {
return new ListFieldDefinition(attrs)
export function list(schema?: SchemaDefinition<any> | Record<string, unknown>): ListFieldDefinition {
if (schema && 'provide' in schema && typeof (schema as SchemaDefinition<any>).provide === 'function') {
const field = new ListFieldDefinition()
// call provide() so the stored value is plain data that survives deep cloning in toConfig()
field.itemSchema((schema as SchemaDefinition<any>).provide())
return field
}
return new ListFieldDefinition(schema as Record<string, unknown> | undefined)
}
3 changes: 2 additions & 1 deletion packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export { isScopePermitted, isActionPermitted } from './permission'

export { buildInitialState, isInScope } from './scope'

export { Position, Scope } from './types'
export { Position, Scope, FetchType } from './types'

export { ptBR } from './locales/pt-BR'

Expand Down Expand Up @@ -62,6 +62,7 @@ export type {
HandlerContext,
BootstrapHookContext,
BootstrapHookFn,
FetchTypeValue,
FetchHookContext,
FetchHookFn,
SchemaHooks,
Expand Down
7 changes: 5 additions & 2 deletions packages/core/src/locales/pt-BR.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,13 @@ export const ptBR = {
destroy: "Excluir",
"create.invalid": "Corrija os erros antes de enviar",
"create.success": "Registro criado com sucesso",
"create.error": "Erro ao criar registro",
"update.invalid": "Corrija os erros antes de enviar",
"update.success": "Registro atualizado com sucesso",
"destroy.confirm": "Deseja realmente excluir este registro?",
"update.error": "Erro ao atualizar registro",
"destroy.confirm": "Tem certeza que deseja excluir este registro? Esta ação não pode ser desfeita.",
"destroy.success": "Registro excluído com sucesso",
"destroy.error": "Erro ao excluir registro",
},
table: {
columns: "Colunas",
Expand All @@ -25,7 +28,7 @@ export const ptBR = {
actions: "Ações",
recordsPerPage: "Registros por página",
},
dialog: { confirm: "Confirmar", cancel: "Cancelar", ok: "OK", alert: "Alerta" },
dialog: { confirm: "Confirmar", confirmDestructiveTitle: "Confirmar exclusão", cancel: "Cancelar", ok: "OK", alert: "Alerta" },
scopes: { index: "Listagem", add: "Cadastro", view: "Visualização", edit: "Edição" },
forbidden: "Acesso não permitido",
},
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ export function createMockContext<F extends Record<string, FieldDefinition>> (
push: fn(),
back: fn(),
replace: fn(),
open: fn(),
},
dialog: {
confirm: fn(async () => true),
Expand All @@ -97,6 +98,7 @@ export function createMockContext<F extends Record<string, FieldDefinition>> (
valid: true,
validate: fn(() => true),
reset: fn(),
setErrors: fn(),
}

const table: TableContract = {
Expand Down
10 changes: 4 additions & 6 deletions packages/core/src/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,20 +45,18 @@ type SchemaHandlers<F extends Record<string, FieldDefinition>> =

type HookBootstrapContext<F extends Record<string, FieldDefinition>> = {
context: Record<string, unknown>
hydrate(data: Record<string, unknown>): void
schema: { [K in keyof F]: FieldProxy }
component: ComponentContract
}

type HookBootstrapFn<F extends Record<string, FieldDefinition>> =
(ctx: HookBootstrapContext<F>) => void | Promise<void>

type HookFetchContext = {
params: PaginateParams
component: ComponentContract
}
type HookFetchContext =
| { type: 'record'; context: Record<string, unknown>; params: PaginateParams; hydrate(data: Record<string, unknown>): void; component: ComponentContract }
| { type: 'collection'; context: Record<string, unknown>; params: PaginateParams; hydrate(result: PaginatedResult<Record<string, unknown>>): void; component: ComponentContract }

type HookFetchFn = (ctx: HookFetchContext) => Promise<PaginatedResult<Record<string, unknown>>>
type HookFetchFn = (ctx: HookFetchContext) => void | Promise<void>

type SchemaHooks<F extends Record<string, FieldDefinition>> = {
bootstrap?: Partial<Record<ScopeValue, HookBootstrapFn<F>>>
Expand Down
47 changes: 35 additions & 12 deletions packages/core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export const Position = {
footer: "footer",
floating: "floating",
row: "row",
header: "header",
} as const;

export type PositionValue = typeof Position[keyof typeof Position]
Expand All @@ -14,7 +15,7 @@ export const Scope = {
edit: "edit",
} as const;

export type ScopeValue = typeof Scope[keyof typeof Scope]
export type ScopeValue = typeof Scope[keyof typeof Scope] | string;

export interface FormConfig {
width: number;
Expand Down Expand Up @@ -96,10 +97,12 @@ export interface NavigatorContract {
back (): void;

replace (path: string, params?: Record<string, unknown>): void;

open (route: ScopeRoute, params?: Record<string, unknown>): void;
}

export interface DialogContract {
confirm (message: string): Promise<boolean>;
confirm (message: string, options?: { destructive?: boolean }): Promise<boolean>;

alert (message: string): Promise<void>;
}
Expand Down Expand Up @@ -128,6 +131,8 @@ export interface FormContract {
validate (): boolean;

reset (values?: Record<string, unknown>): void;

setErrors (errors: Record<string, string[]>): void;
}

export interface ComponentContract {
Expand Down Expand Up @@ -184,23 +189,41 @@ export interface HandlerContext {
component: ComponentContract
form?: FormContract
table?: TableContract
value?: unknown
}

export interface BootstrapHookContext {
export interface BootstrapHookContext<_T = Record<string, unknown>> {
context: Record<string, unknown>
hydrate(data: Record<string, unknown>): void
schema: Record<string, FieldProxy>
component: ComponentContract
}

export type BootstrapHookFn = (ctx: BootstrapHookContext) => void | Promise<void>

export interface FetchHookContext {
params: PaginateParams
component: ComponentContract
}

export type FetchHookFn = (ctx: FetchHookContext) => Promise<PaginatedResult<Record<string, unknown>>>
export type BootstrapHookFn<_T = Record<string, unknown>> = (ctx: BootstrapHookContext) => void | Promise<void>

export const FetchType = {
record: 'record',
collection: 'collection',
} as const

export type FetchTypeValue = typeof FetchType[keyof typeof FetchType]

export type FetchHookContext<T = Record<string, unknown>> =
| {
type: 'record'
context: Record<string, unknown>
params: PaginateParams
hydrate(data: T): void
component: ComponentContract
}
| {
type: 'collection'
context: Record<string, unknown>
params: PaginateParams
hydrate(result: PaginatedResult<T>): void
component: ComponentContract
}

export type FetchHookFn<T = Record<string, unknown>> = (ctx: FetchHookContext<T>) => void | Promise<void>

export interface SchemaHooks {
bootstrap?: Partial<Record<ScopeValue, BootstrapHookFn>>
Expand Down
39 changes: 24 additions & 15 deletions packages/demo/src/settings/hooks.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,36 @@
import type { ServiceContract, BootstrapHookContext, FetchHookContext } from "@ybyra/core";
import { Scope } from "@ybyra/core";
import { type BootstrapHookContext, type FetchHookContext, FetchType, Scope, type ServiceContract } from '@ybyra/core'

export function createDefault (service: ServiceContract) {
return {
bootstrap: {
async [Scope.view] ({ context, schema, hydrate }: BootstrapHookContext) {
if (!context.id) return;
const data = await service.read(context.id as string);
hydrate(data);
async [Scope.view] ({ schema }: BootstrapHookContext) {
for (const field of Object.values(schema)) {
field.disabled = true;
field.disabled = true
}
},
async [Scope.edit] ({ context, hydrate }: BootstrapHookContext) {
if (!context.id) return;
const data = await service.read(context.id as string);
hydrate(data);
},
},
fetch: {
async [Scope.index] ({ params }: FetchHookContext) {
return service.paginate(params);
async [Scope.view] (context: FetchHookContext) {
if (context.type !== FetchType.record || !context.context.id) {
return
}
const data = await service.read(context.context.id as string)
context.hydrate(data)
},
async [Scope.edit] (context: FetchHookContext) {
if (context.type !== FetchType.record || !context.context.id) {
return
}
const data = await service.read(context.context.id as string)
context.hydrate(data)
},
async [Scope.index] (context: FetchHookContext) {
if (context.type !== FetchType.collection) {
return
}
const result = await service.paginate(context.params)
context.hydrate(result)
},
},
};
}
}
Loading
Loading