Skip to main content

08 — Glossary

Tham chiếu nhanh: variable abbreviations, function purposes, file mapping. Mỗi entry có ý nghĩa + nơi xuất hiện + ví dụ.


New terms (post-refactor)

definitions.jsTrait definition data file

File chứa DEFINITIONS_DATA — pure data map của reusable trait definitions. Mỗi definition có writes map (multi-target dispatch). Không import Vue.

Nơi: src/components/editor_v2/components/trait/fields/definitions.js

Dùng để: Reuse trait config across elements. Vd width_select definition dùng cho cả FlexBlock, Grid, etc. Thay vì định nghĩa lại trong mỗi element.


registry.js (trong trait/fields/) — Trait registry với Vue

Attach Vue component field vào definitions data. Import DEFINITIONS_DATA từ definitions.js, gắn .component field, export COMPONENT_DEFINITIONS.

Khác composable/editor_v2/registry.js (element registry).


writesMulti-target dispatch map

Object trong definition: { key: { target, schema } }. Khi 1 attribute thay đổi, có thể update nhiều node data keys cùng lúc.

{ padding: { target: 'style', schema: {...} },
isPaddingLinked: { target: 'specials', schema: {...} } }

→ 1 PaddingTrait emit padding + isPaddingLinked.


schema_helpers.jsJSON Schema builder utilities

File chứa helpers như cssLength, cssColor, responsive, enumOf, etc. Build JSON Schema objects (Ajv-compatible).


ai.js (element sidecar) — AI-generation metadata

Optional file nodes/<name>/ai.js. Lazy-loaded, contains: description, useWhen/avoidWhen, examples, semantics. Dùng khi building LLM tool schema (Phase 1 của AI gen).


buildElementSchema(meta)Pure function

Walk element meta.traits → resolve definitions → return JSON Schema object matching { type: 'object', properties: { style?, config?, specials? } } shape. Output matches createNodeTree contract.


allowedKeysGuard against typos

Set tất cả valid keys từ traits của element. Store _writeNamespace action guard:

  • Check incoming key có trong allowedKeys
  • Nếu không → console.warn('unknown key dropped')
  • Drop từ patch, write rest

Catch hallucination từ typo / AI misfire.


index.vue (element convention) — Component + factory home

Element structure: nodes/<name>/index.vue chứa Vue component + factory function. Import meta từ ./meta.js, spread + override với factory.

import { meta as baseMeta } from './meta.js'
export const meta = { ...baseMeta, factory: (overrides) => ... }

meta.js (element convention) — Runtime metadata file

Pure data file: type, label, traits, rules. NO Vue imports, NO @/ aliases (tránh cycle). Được import bởi index.vue sau.


Variable abbreviations

nsnamespace

Loại data trong node: 'style', 'config', hoặc 'specials'. Quyết định nơi đọc/ghi.

const ns = SEED_NS.includes(field.target) ? field.target : 'style'
// ns sẽ là 'style', 'config', hoặc 'specials'

Xuất hiện: mergeNamespace, _writeNamespace, seedField, writeDefault.


bpbreakpoint

1 viewport definition {key, label, width, isMobile} HOẶC chỉ là key string 'desktop'/'laptop'/'tablet'/'mobile'.

for (const bp of BREAKPOINTS) { // bp = { key, label, width, isMobile }
if (bp.width < curBpDef.width) continue
}

const bp = useUIStore().breakpointActive // bp = 'tablet' (chỉ key string)

Phân biệt: full object vs key string xem qua context. Khi loop BREAKPOINTS là object; khi đọc breakpointActive là string.

Biến liên quan:

  • bpKey = key string ('mobile')
  • bpDef = full breakpoint definition object
  • curBpDef = current breakpoint definition
  • currentBpKey = current breakpoint key string

def — element definition

reg[type] = object đã register = { ...meta, factory: wrapped, defaults, component }.

const def = getDef('flex-section')
// def = {
// type: 'flex-section', label: 'Section', icon: ...,
// isContainer: true, rules: {...},
// factory: (overrides) => {...}, // wrapped factory (seed defaults)
// defaults: { style:{...}, ... }, // extracted từ traits
// component: FlexSectionV2, // Vue SFC
// traits: {...}, // schema gốc
// }

Khác với meta (raw export từ file element). def = meta + component + defaults + wrapped factory.


meta — element metadata

Object raw export const meta = {...} trong file element. Chưa qua registry.

// nodes/HeadingV2.vue
export const meta = {
type: 'heading',
label: 'Heading',
factory: (overrides) => createNode({...}),
traits: {...},
}

Sau khi registerElement(meta, component) thì meta được biến thành def trong registry.


ctxcontext (trong dialog)

Binding info pass qua ui store khi user click TraitAssetInput.

ctx = { field, nodeId }
// field = trait schema entry (vd { key:'padding', target:'style', type:'spacing', ... })
// nodeId = ID của node đang được edit (vd 'fs_abc12345')

Dialog đọc ctx để biết "tôi đang edit field gì của node nào".


rawraw (unprocessed) value

Giá trị thô trước khi formatter hoặc fallback.

// Trong TraitField.value:
const raw = mergeNamespace(node, 'style', bp)[key] // chưa format
// raw có thể là '20px 24px 20px 24px' (4-side expanded)

// Sau formatter:
const value = formatter(raw) // '20px 24px' (collapsed)

Cũng dùng cho event handler:

handleChange(key, val) {
const raw = val && val.target !== undefined ? val.target.value : val
// raw = string giá trị input thô
const n = Number(raw)
}

merged — accumulator object trong cascade

Result của mergeNamespace. Bắt đầu bằng base, accumulate per-bp slots.

let merged = { ...base }
for (const bp of BREAKPOINTS) {
if (bp.width < curBpDef.width) continue
if (slot) merged = { ...merged, ...slot[ns] }
}
return merged

outoutput accumulator

Pattern phổ biến trong extractTraitDefaults và utility functions:

const extractTraitDefaults = (traits) => {
const out = { style: {}, config: {}, specials: {}, responsive: {} }
// ... loop và fill out
return out
}

out thường là object kết quả mà function đang build dần.


fmtformatter

Function transform value thành display string/object.

const FORMATTERS_BY_TYPE = {
spacing: (v) => formatSides(parseSides(v)),
}
const fmt = FORMATTERS_BY_TYPE[this.schema.type]
return fmt ? fmt(effective) : effective

ssides (parsed)

Object {top, right, bottom, left} từ parseSides.

const s = parseSides('20px 24px')
// s = { top: 20, right: 24, bottom: 20, left: 24 }

Cũng có thể là viết tắt cho style trong element template:

sectionStyle() {
const s = this.mergedStyle // s = mergedStyle (đỡ phải gõ this.mergedStyle)
return { padding: s.padding }
}

nnumber (sau Number())

Coercion result, có thể NaN.

const n = Number(raw)
const next = Number.isFinite(n) ? n : 0

ffield (trong loop)

Trait field schema entry — short form trong inner loops:

for (const f of fields) seedField(out, f)
// f = { key, type, target, default, ... }

cur / curBpDefcurrent breakpoint

Đang hover/edit ở bp nào.

const cur = BREAKPOINTS.find((b) => b.key === currentBpKey)
if (!cur) return result

_lastCommitted — instance field, không reactive

Track giá trị vừa được commit để skip self-echo loop.

data() { return { paddings: {...} } } // không khai _lastCommitted ở đây
// → assign trực tiếp this._lastCommitted = ... → plain instance field
methods: {
commit() {
const value = formatSides(this.paddings)
this._lastCommitted = value // non-reactive
applyTrait(...)
}
}
watch: {
currentValue(val) {
if (val === this._lastCommitted) return // skip echo
this.syncFromValue(val)
}
}

Prefix _ ở Vue convention = "internal, non-reactive".


Key functions

mergeNamespace(node, ns, currentBpKey) → object

File: composable/editor_v2/mergeNode.js

Merge base + per-bp slots cho 1 namespace, theo cascade desktop-first.

mergeNamespace(node, 'style', 'mobile')
// → { color: '#333', padding: '20px 15px', ... }

Dùng ở:

  • mixins/nodeBase.mergedStyle, mergedConfig
  • EdgeOverlays.mergedStyle
  • TraitField.value
  • PaddingDialog.currentValue

isBreakpointMap(value) → boolean

File: composable/editor_v2/mergeNode.js

Check object có phải responsive map (có key base/_/bp name) hay không.

isBreakpointMap({ base: '20px', mobile: '15px' }) // true
isBreakpointMap({ x: 0, y: 4 }) // false (complex value)
isBreakpointMap('20px') // false (primitive)

Dùng ở:

  • registry.seedField — split per-bp hay ghi vào base
  • mergeNode.resolveDefaultForBp — cascade hay return as-is

resolveDefaultForBp(def, currentBpKey) → value

File: composable/editor_v2/mergeNode.js

Resolve schema default value cho bp hiện tại, dùng cascade logic giống mergeNamespace.

resolveDefaultForBp({ base: '20px 24px', mobile: '20px 15px' }, 'mobile')
// → '20px 15px'

resolveDefaultForBp({ base: '20px 24px', mobile: '20px 15px' }, 'tablet')
// → '20px 24px' (cascade từ base)

resolveDefaultForBp('20px', 'mobile') // → '20px' (primitive)
resolveDefaultForBp({ x:0, y:4 }, 'mobile') // → { x:0, y:4 } (complex)

Dùng ở: TraitField.value (display fallback).


applyTrait(nodeId, field, value, opts?)

File: stores/editor_v2/node.js (action)

Generic dispatcher — route value vào đúng changeStyle / changeConfig / changeSpecials theo field.target.

nodeStore.applyTrait('fs_xxx', paddingField, '20px 24px')
// → changeStyle('fs_xxx', { padding: '20px 24px' })

nodeStore.applyTrait('h_yyy', textField, 'Hello')
// → changeSpecials('h_yyy', { text: 'Hello' })

nodeStore.applyTrait('fs_xxx', paddingField, '0', { breakpoint: 'base' })
// → changeStyle('fs_xxx', { padding: '0' }, { breakpoint: 'base' })

Dùng ở: dialogs (PaddingDialog), trait panel custom widgets.


changeStyle(id, patch, opts?) / changeConfig / changeSpecials

File: stores/editor_v2/node.js (action)

Per-namespace writer.

ActionDefault targetHỗ trợ per-bp?
changeStylecurrent bp (responsive[bp].style)Yes
changeConfigbase (data.config)Yes (opt-in qua {breakpoint:'current'})
changeSpecialsbase (data.specials)No (luôn base)

opts.breakpoint:

  • 'current' (default cho style) → current bp slot
  • 'base' (default cho config) → base slot
  • 'mobile' / 'tablet' / etc. → explicit slot
  • omit → dùng default của action

Patch key với value === undefined → REMOVE key khỏi target slot.


getFieldComponent(field) → Vue component | null

File: components/editor_v2/components/trait/fields/registry.js

2-layer resolve:

  1. field.component (string) → FIELD_COMPONENTS[name]
  2. field.typeCOMPONENT_BY_TYPE[type]

getFieldIcon(field) → Vue component | null

File: như trên

Resolve icon cho slot #icon của TraitAssetInput.

  1. field.icon (string) → ICON_COMPONENTS[name]
  2. field.props.dialogTypeDIALOG_ICON_BY_TYPE[dialogType]ICON_COMPONENTS

getDef(type) → def | null

File: composable/editor_v2/registry.js

Lookup element definition theo type string.

const def = getDef('flex-section')
// def.component, def.factory, def.label, def.defaults, def.traits, ...

factoryFor(type, overrides) → Node | null

File: như trên

Create node mới qua wrapped factory (đã seed defaults).

const node = factoryFor('heading', { style: { color: 'red' } })
// node.data.style = { color: 'red', /* + defaults */ }

getDefaultsFor(type) → { style, config, specials, responsive } | null

File: như trên

Expose defaults map cho trait panel "Reset to default" UI sau này.

const defaults = getDefaultsFor('flex-section')
nodeStore.applyTrait(id, paddingField, defaults.style.padding)

parseSides(value) → { top, right, bottom, left }

File: composable/editor_v2/cssShorthand.js

Parse CSS shorthand thành 4 numeric sides (px).

parseSides('20px 24px') // { top:20, right:24, bottom:20, left:24 }
parseSides('10px 20px 30px') // { top:10, right:20, bottom:30, left:20 }
parseSides('5px') // { top:5, right:5, bottom:5, left:5 }
parseSides(null) // { top:0, right:0, bottom:0, left:0 }

formatSides({top, right, bottom, left}) → string

File: như trên

Compose 4 sides thành CSS shorthand ngắn nhất.

formatSides({top:20, right:20, bottom:20, left:20}) // '20px'
formatSides({top:20, right:24, bottom:20, left:24}) // '20px 24px'
formatSides({top:10, right:20, bottom:30, left:20}) // '10px 20px 30px'
formatSides({top:10, right:20, bottom:30, left:40}) // '10px 20px 30px 40px'

isBreakpointMap(value) → boolean

Đã giải thích ở trên.


getBreakpoint(key), getBreakpointWidth(key), isMobileBreakpoint(key)

File: composable/editor_v2/constants.js

Helpers tra cứu breakpoint metadata.

getBreakpoint('mobile') // { key:'mobile', label:'Mobile', width:360, isMobile:true }
getBreakpointWidth('tablet') // 768
isMobileBreakpoint('mobile') // true
isMobileBreakpoint('tablet') // false

resolveBreakpointSlot(target, currentBp) → bpKey | null

File: composable/editor_v2/createNode.js

Convert sentinel/key thành slot key cho _writeNamespace.

resolveBreakpointSlot('current', 'tablet') // 'tablet'
resolveBreakpointSlot('base', 'tablet') // null (→ base slot)
resolveBreakpointSlot('mobile', 'tablet') // 'mobile' (explicit)
resolveBreakpointSlot(null, ...) // null

File mapping cheat sheet

FolderFileTrách nhiệmKhi nào sửa
composable/editor_v2/constants.jsBREAKPOINTS, ROOT_NODEThêm bp mới, đổi default
createNode.jscreateNode, createNodeTree, wrapTree, resolveBreakpointSlotHiếm — chỉ shape Node thay đổi
mergeNode.jsmergeNamespace, isBreakpointMap, resolveDefaultForBpHiếm — cascade logic
registry.jsregisterElement, getDef, factoryFor, defaults extractionKhi thêm field schema property mới
registerElements.jsEager glob nodes/*.vueKhông sửa
nodeFactory.jsComposite tree buildersThêm composite shortcut
Positioner.jsDrop indicator engineDrag-drop logic
cssShorthand.jsparseSides, formatSidesHiếm
mixins/nodeBase.jsprops, isSelected, mergedStyle/Config/SpecialsThêm mixin computed chung
mixins/nodeContainer.js+ isEmpty, isDropTarget, onDragOver
mixins/draggableNode.jsonMoveDragStart, onMoveDragEnd
stores/editor_v2/node.jsTree state + actions (add/move/remove/duplicate/changeX/applyTrait)Thêm action store
dnd.jsDrag session + Positioner lifecycle
editor.jsUI state (breakpoint, sidebar, dialogs)
components/editor_v2/nodes/XxxV2.vueElement SFC + meta exportThêm element
components/editor_v2/elements/NodeRenderer.vueSwitcher đọc registryHiếm
EdgeOverlays.vuePadding/margin SVG overlayHover UX
ElementToolbar.vueFloating toolbar
ElementDragV2.vueSidebar drag wrapper
IndicatorOverlay.vueDrop indicator UI
components/editor_v2/components/trait/fields/registry.jsFIELD_COMPONENTS, COMPONENT_BY_TYPEThêm field widget type
components/TraitField.vueGeneric field rendererHiếm
components/TraitWrapper.vueGroup label wrapper
components/TraitItemWrapper.vueField label wrapper
components/TraitAssetInput.vueDialog trigger
components/editor_v2/components/dialog/PaddingDialog.vue4-input padding editorTham khảo cho dialog mới
components/editor_v2/Trait.vueTrait panel containerLayout tab/sidebar
Header.vueTop bar với bp tabs
PageWrapper.vueEditor entry + canvas
SettingDialogs.vueMount tất cả dialog componentsThêm dialog mới

Naming conventions

PatternVí dụÝ nghĩa
useXxxStore()useNodeStore()Pinia store factory
XxxV2.vueHeadingV2.vueElement SFC (suffix V2 phân biệt với v1)
WkXxxWkInput, WkSelectwebcake-ui-kit components
wk-xxxwk-flex-blockCSS class prefix
wk-xxx--statewk-flex-block--drop-activeBEM modifier
wk-xxx__partwk-node-placeholder__textBEM element
data-node-id:data-node-id="nodeId"DOM data attr cho Positioner query
data-node-typedata-node-type="flex-section"DOM data attr
meta, def, field, attrTrait schema vocabulary
ctx, _lastCommittedInternal/dialog state

Anti-patterns — DON'T

  • <element :is> import trực tiếp element con — phá registry. Luôn qua <NodeRenderer>.
  • ❌ Hardcoded type string trong logic (vd if (type === 'flex-section')). Dùng getDef(type).rules.isRootOnly hoặc tương tự.
  • ❌ Mutate node.data.style[key] = v trực tiếp. Dùng changeStyle / applyTrait.
  • ❌ Index assignment vào mảng reactive (parent.data.nodes[0] = id). Dùng splice/push/reassign.
  • ❌ Import element SFC từ registry.js. Phá cycle rule.
  • data.props (đã xoá). Dùng style/config/specials.
  • ❌ Numeric breakpoint key (responsive[1440]). Dùng text key (responsive.laptop).
  • ❌ Default cho field complex value mà có key trùng bp name. Wrap qua { base: {...} }.