๐ฏ Selective UI โ ARCHITECTURE
Version 1.3 | MIT License | CRV-UI Engine
"Render less. Control more."
๐ 1. CRV-UI ENGINE โ OVERALL ARCHITECTURE
graph TB
subgraph PUBLIC_API
A["SelectiveUIGlobal
GLOBAL_SEUI singleton"]
B[bind]
C[find]
D[destroy]
E[rebind]
F[effector]
G[registerPlugin]
H[unregisterPlugin]
A --> B
A --> C
A --> D
A --> E
A --> F
A --> G
A --> H
end
subgraph CORE_ORCHESTRATOR
SEL["Selective
Lifecycle FSM"]
EAO["ElementAdditionObserver
MutationObserver auto-rebind"]
BQ["bindedQueries Map
selector -> options"]
PLG["Plugin registry
id -> SelectivePlugin"]
SEL --> EAO
SEL --> BQ
SEL --> PLG
end
subgraph COMPONENT_LAYER
SB["SelectBox
UI coordinator"]
PH[PlaceHolder]
DIR[Directive]
SRCH[SearchBox]
POP[Popup]
ACC[AccessoryBox]
EFF[Effector]
REF[Refresher]
SB --> PH
SB --> DIR
SB --> SRCH
SB --> POP
SB --> ACC
SB --> EFF
SB --> REF
end
subgraph MODEL_ADAPTER_RENDER
MM["ModelManager
DOM -> Models"]
MA["MixedAdapter
Group + Option"]
RV["VirtualRecyclerView"]
GV[GroupView]
OV[OptionView]
MM --> MA
MA --> RV
RV --> GV
RV --> OV
end
subgraph SERVICES
SC["SearchController"]
SO["SelectObserver"]
DO["DatasetObserver"]
RSZO["ResizeObserver"]
end
subgraph UTILITIES
LIBS["Libs utilities"]
CBS["CallbackScheduler"]
IEVT["iEvents"]
FW["Fenwick Tree"]
end
B --> SEL
C --> SEL
D --> SEL
E --> SEL
SEL --> SB
SB --> MM
SB --> SC
SB --> SO
SB --> DO
RV --> RSZO
RV --> FW
SEL --> LIBS
MM --> CBS
SEL --> IEVT
style A fill:#667eea,stroke:#333,stroke-width:3px,color:#fff
style SEL fill:#764ba2,stroke:#333,stroke-width:2px,color:#fff
style SB fill:#f093fb,stroke:#333,stroke-width:2px,color:#fff
style MM fill:#4facfe,stroke:#333,stroke-width:2px,color:#fff
style RV fill:#43e97b,stroke:#333,stroke-width:2px,color:#fff
style FW fill:#fa709a,stroke:#333,stroke-width:2px,color:#fff
๐ CRV-UI Engine:
โข Contract-Driven: All communication occurs through TypeScript interfaces โ views do not depend on concrete implementations
โข Retained DOM: DOM nodes are created once and updated incrementally, without Virtual DOM or diffing
โข Virtualized Rendering: Fenwick Tree + windowing for 10k+ items with O(log n) height updates
โข GLOBAL_SEUI singleton: The new version uses globalThis.GLOBAL_SEUI to prevent duplicate script tag loading ()
๐ v1.3 Changes so with v1.2:
โข Public API currently routed through globalThis.GLOBAL_SEUI singleton instead of module-level SECLASS
โข Added registerPlugin / unregisterPlugin to the Public API
โข Official FSM Lifecycle: NEW โ INITIALIZED โ MOUNTED โ UPDATED โ DESTROYED
โข SelectBox now composes VirtualRecyclerView (virtual scroll) or RecyclerView
โข Fenwick Tree replaces the previous simple scroll calculation
๐๏ธ 2. CLASS DIAGRAM (Core Hierarchy)
classDiagram
class Lifecycle {
#state: LifecycleState
-hooks: Map~HookName, Set~Callback~~
+init()
+mount()
+update()
+destroy()
+on(hook, fn) this
+off(hook, fn) this
+is(state) boolean
+getState() LifecycleState
#emit(hook, prevState)
}
class Selective {
-EAObserver: ElementAdditionObserver
-bindedQueries: Map~string, SelectiveOptions~
-plugins: Map~string, SelectivePlugin~
+bind(query, options)
+find(query) SelectiveActionApi
+destroy(target)
+rebind(query, options)
+Observer()
+registerPlugin(plugin)
+unregisterPlugin(id)
+getPlugin(id)
-applySelectBox(el, options) boolean
-destroyAll()
-destroyByQuery(query)
-destroyElement(el)
-buildGetSetAction(...)
-buildFuntionAction(...)
}
class SelectBox {
-element: HTMLSelectElement
-selective: Selective
+container: ContainerRuntime
-modelManager: ModelManager
-searchController: SearchController
-selectObserver: SelectObserver
-datasetObserver: DatasetObserver
+mount()
+deInit()
+destroy()
+on(hook, fn)
+getAction() SelectBoxAction
}
class ModelManager {
-privModelList: MixedItem[]
-privAdapterHandle: TAdapter
-privRecyclerViewHandle: RecyclerViewContract
-options: SelectiveOptions
+setupAdapter(cls)
+setupRecyclerView(cls)
+createModelResources(data) Models
+load(viewEl, adapterOpt, recyclerOpt)
+replace(modelData)
+updateModel(modelData)
+refresh(isUpdate)
+triggerChanging(event) Promise
+triggerChanged(event) Promise
+getResources()
+skipEvent(value)
}
class Adapter {
+items: TItem[]
+adapterKey: string
+isSkipEvent: boolean
+recyclerView: any
+onViewHolder(item, viewer, pos)
+onPropChanging(prop, cb)
+onPropChanged(prop, cb)
+changeProp(prop) Promise
+changingProp(prop) Promise
+setItems(items) Promise
+syncFromSource(items) Promise
+viewHolder(parent, item) TViewer
+updateRecyclerView(parent)
+updateData(items)
}
class MixedAdapter {
+isMultiple: boolean
+options: SelectiveOptions
+groups: GroupModel[]
+flatOptions: OptionModel[]
-currentHighlightIndex: number
-selectedItemSingle: OptionModel
+getSelectedItems() OptionModel[]
+highlightNext()
+highlightPrev()
+onVisibilityChanged(cb)
+checkAll()
+uncheckAll()
}
class RecyclerView {
+viewElement: HTMLDivElement
+adapter: TAdapter
+setAdapter(adapter)
+clear()
+render()
+refresh(isUpdate)
+destroy()
}
class VirtualRecyclerView {
-opts: VirtualOptions
-PadTop: HTMLDivElement
-ItemsHost: HTMLDivElement
-PadBottom: HTMLDivElement
-fenwick: Fenwick
-heightCache: number[]
-created: Map~int, HTMLElement~
+configure(opts)
+setAdapter(adapter)
+refresh(isUpdate)
+ensureRendered(index, opt)
+scrollToIndex(index)
+suspend()
+resume()
+refreshItem()
+dispose()
-updateWindowInternal()
-mountRange(start, end)
-unmountOutside(start, end)
}
class Fenwick {
-bit: number[]
-stackNum: number
+initialize(n)
+reset(n)
+add(i, delta)
+sum(i) number
+rangeSum(l, r) number
+buildFrom(arr)
+lowerBoundPrefix(target) number
}
class Model {
+targetElement: TTarget
+options: TOptions
+view: TView
+position: number
+isInit: boolean
+isRemoved: boolean
+value: string
+updateTarget(el)
+destroy()
}
class Effector {
+element: HTMLElement
+isAnimating: boolean
+setElement(query)
+expand(config)
+collapse(config)
+resize(config)
+swipe(config)
+cancel()
}
Lifecycle <|-- Selective
Lifecycle <|-- SelectBox
Lifecycle <|-- ModelManager
Lifecycle <|-- Adapter
Lifecycle <|-- RecyclerView
Lifecycle <|-- Model
Lifecycle <|-- Fenwick
Adapter <|-- MixedAdapter
RecyclerView <|-- VirtualRecyclerView
Selective --> SelectBox : creates
SelectBox --> ModelManager : owns
ModelManager --> MixedAdapter : instantiates
ModelManager --> RecyclerView : or VirtualRecyclerView
VirtualRecyclerView --> Fenwick : uses
SelectBox --> Effector : uses
๐ Actual hierarchy:
โข Lifecycle is the common base class for all components โ strict FSM with Setโbased hooks
โข Selective is the main orchestrator: manages bindings, plugins, and the ElementAdditionObserver
โข SelectBox is the root UI component: composes all components and services
โข ModelManager headless: DOM โ GroupModel/OptionModel โ Adapter โ RecyclerView
โข MixedAdapter xแปญ lรฝ heterogeneous list (group + option), navigation, visibility, selection
โข VirtualRecyclerView + Fenwick: virtualization vแปi dynamic heights, adaptive estimator
๐ 3. LIFECYCLE FSM (Finite State Machine)
stateDiagram-v2
[*] --> NEW: Constructor
NEW --> INITIALIZED: init()
note right of INITIALIZED
bindedQueries = new Map()
plugins = new Map()
hooks containers created
end note
INITIALIZED --> MOUNTED: mount()
note right of MOUNTED
First successful bind()
Active bound elements exist
Ready for interactions
end note
MOUNTED --> UPDATED: update()
UPDATED --> UPDATED: update() repeatable
note right of UPDATED
Triggered by:
- bind() when already mounted
- destroy() partial teardown
- rebind() re-registration
- Observer() new element detected
end note
MOUNTED --> DESTROYED: destroyAll()
UPDATED --> DESTROYED: destroyAll()
INITIALIZED --> DESTROYED: destroyAll()
note right of DESTROYED
bindedQueries cleared
plugins cleared
EAObserver disconnected
All hooks cleared
end note
DESTROYED --> [*]
๐ FSM Guards (Idempotency):
โข init(): noโop if the state is not NEW
โข mount(): only transitions INITIALIZED โ MOUNTED; otherwise a noโop
โข update(): allowed in MOUNTED and UPDATED states; always emits onUpdate
โข destroy(): idempotent if already DESTROYED; clears all hooks
โข Hook exceptions are caught by handleHookError() โ without breaking the lifecycle flow
โ๏ธ 4. BINDING FLOW (bind โ applySelectBox โ mount)
sequenceDiagram
participant User
participant API as GLOBAL_SEUI
participant SEL as Selective
participant SB as SelectBox
participant MM as ModelManager
participant MA as MixedAdapter
participant RV as VirtualRecyclerView
participant CBS as CallbackScheduler
User->>API: bind(".my-select", options)
API->>SEL: bind(query, options)
SEL->>SEL: mergeConfig(defaults, options)
SEL->>SEL: bindedQueries.set(query, merged)
SEL->>SEL: getElements(query) -> HTMLSelectElement[]
loop For each select
SEL->>SEL: applySelectBox(element, options)
SEL->>SEL: generate SEID values
SEL->>SEL: buildConfig merge dataset
SEL->>SB: new SelectBox
SB->>SB: onMount wireMouseUp
SEL->>SB: mount
SB->>MM: new ModelManager
MM->>MM: setupAdapter MixedAdapter
MM->>MM: setupRecyclerView VirtualRecyclerView
MM->>MM: createModelResources
MM->>MA: new MixedAdapter
MM->>RV: new VirtualRecyclerView
RV->>MA: setAdapter
MA->>RV: set recyclerView reference
RV->>RV: build scaffold
RV->>RV: attachScrollListener and ResizeObserver
RV->>RV: refresh initial render
SB-->>SEL: getAction ActionAPI
SEL->>SEL: save bindMap
end
SEL->>SEL: mount if first bind
SEL->>CBS: run doneToken
CBS-->>User: on.load callbacks
SEL-->>User: binding complete
๐ Binding Flow Details:
โข SEID: unique 8-character ID per instance โ used for seui-{SEID}-optionlist, seui-{SEID}-placeholder
โข buildConfig: merges dataset attributes into options (medium priority)
โข onMount hook: wires mouseup on the view to trigger toggle()
โข VirtualRecyclerView: scaffold 3 nodes (PadTop, ItemsHost, PadBottom) + ResizeObserver
โข on.load callbacks: invoked asynchronously via CallbackScheduler after all elements are processed
๐ 5. DATA FLOW: DOM โ Models โ Adapter โ Views
graph LR
subgraph INPUT_SOURCES
A["HTML select
option / optgroup"]
B["setData or reload
JSON Array"]
C["AJAX Response
fetch + dataTransform"]
end
subgraph MODEL_MANAGER
D["createModelResources
DOM to GroupModel / OptionModel"]
D --> E["updateModel
Diff + reconcile in-place"]
E --> F["replace
Full dataset swap"]
end
subgraph MODELS
G["GroupModel
label items visible"]
H["OptionModel
value text selected visible highlighted"]
G --> H
end
subgraph MIXED_ADAPTER
I["flatOptions
flattened navigation index"]
J["groups
group model refs"]
K["Visibility aggregation
visibleCount totalCount"]
L["Selection tracking
selectedItem"]
end
subgraph RECYCLER_RENDERING
M["VirtualRecyclerView
windowing start-end"]
M --> N["Fenwick Tree
O(log n) prefix sums"]
N --> O["mountRange start end
visible window only"]
O --> P["GroupView OptionView
DOM nodes"]
end
subgraph SEARCH_CONTROLLER
Q["SearchController
filter + AJAX"]
Q --> R["updateModel
visibility flags"]
R --> S["adapter.onVisibilityChanged
refreshItem"]
end
A --> D
B --> F
C --> Q
D --> G
D --> H
G --> I
H --> I
I --> M
Q --> R
S --> M
style A fill:#e3f2fd,stroke:#1976d2
style B fill:#e3f2fd,stroke:#1976d2
style C fill:#e3f2fd,stroke:#1976d2
style M fill:#43e97b,stroke:#333,stroke-width:2px
style N fill:#fa709a,stroke:#333,stroke-width:2px,color:#fff
style Q fill:#fff3e0,stroke:#f57c00
๐ New Data Flow compared to v1.2:
โข ModelManager replaces direct DOM manipulation โ headless, test-friendly
โข updateModel(): The diff strategy preserves model instances (keyed by value::text), avoiding destruction/recreation
โข MixedAdapter.flatOptions[]: flattened index for keyboard navigation independent of group structure
โข onVisibilityChanged: when search filter changes โ VirtualRecyclerView.refreshItem() โ rebuild Fenwick
โข Fenwick Tree: dynamic heights with adaptive estimator (running average of measured items)
๐ 6. VIRTUAL SCROLL + FENWICK TREE
graph TB
subgraph INITIALIZATION
A[setAdapter called] --> B["Build scaffold nodes
PadTop / ItemsHost / PadBottom"]
B --> C["Probe index 0 height
seed estimateItemHeight"]
C --> D["buildFrom arr
Fenwick O(n log n)"]
D --> E["scheduleUpdateWindow
requestAnimationFrame"]
end
subgraph SCROLL_EVENTS
F["scroll event
passive listener"] --> G["scheduleUpdateWindow
rAF debounced"]
H["ResizeObserver
ItemsHost mutations"] --> I["measureVisibleAndUpdate
rAF debounced"]
end
subgraph WINDOW_UPDATE
G --> J[updateWindowInternal]
J --> K["containerTopInScroll
relative offset"]
K --> L["findFirstVisibleIndex
fenwick lowerBoundPrefix"]
L --> M["Compute start end
overscan estimate"]
M --> N{Window changed}
N -->|No| O[Skip update]
N -->|Yes| P["mountRange start end"]
P --> Q["unmountOutside start end"]
Q --> R["measureVisibleAndUpdate
updateHeightAt log n"]
R --> S["Update PadTop
Update PadBottom"]
S --> T["Anchor correction
prevent scroll jump"]
end
subgraph FENWICK
U["add i delta
log n update"]
V["sum i
log n prefix"]
W["lowerBoundPrefix
log n search"]
X["rangeSum l r
log n"]
Y["buildFrom arr
n log n"]
end
subgraph SUSPEND_RESUME
Z["suspend
cancel rAF and scroll listener
disconnect ResizeObserver"]
AA["resume
reattach and scheduleUpdateWindow"]
end
I --> R
U -.-> V
V -.-> W
style A fill:#667eea,stroke:#333,stroke-width:2px,color:#fff
style J fill:#764ba2,stroke:#333,stroke-width:2px,color:#fff
style P fill:#43e97b,stroke:#333,stroke-width:2px
style U fill:#fa709a,stroke:#333,stroke-width:2px,color:#fff
style Z fill:#ffd89b,stroke:#333,stroke-width:2px
๐ Advanced Virtual Scroll compared to v1.2:
โข Fenwick Tree: simple formula replacement โ supports dynamic/variable heights
โข Adaptive estimator: uses running average of measured items instead of fixed estimateItemHeight
โข Anchor correction: maintains scroll position stability when heights change after measurement
โข Suspend/Resume: batch operations can pause virtualization to avoid feedback loops
โข Re-entrancy guard: the updating flag prevents recursive calls to updateWindowInternal
๐ฏ Fenwick Tree Formulas:
- offsetTopOf(i) = fenwick.sum(i) โ prefix sum of heights[0..i-1]
- findFirstVisible(scrollTop) = fenwick.lowerBoundPrefix(scrollTop)
- overscanPx = opts.overscan ร getEstimate()
- adaptiveEstimate = measuredSum / measuredCount (khi measuredCount > 0)
- totalHeight(count) = fenwick.sum(count)
- windowHeight(s,e) = fenwick.rangeSum(s+1, e+1)
๐จ 7. UI COMPONENT STRUCTURE
graph TD
A[.seui-container
SelectBox root wrapper] --> B[.seui-view
PlaceHolder + Directive]
A --> C[.seui-panel / Popup
dropdown container]
B --> D[.seui-placeholder
selected value display]
B --> E[.seui-directive
arrow + caret indicator]
B --> F[.seui-accessorybox
multi-select chips]
D --> D1[Text / Image
selected display]
F --> F1[Chip per selected item]
F1 --> F2[Remove button per chip]
C --> G[.seui-option-handle
SearchBox + SelectAll]
C --> H[.seui-optionlist
RecyclerView host]
C --> I[.seui-empty-state
EmptyState component]
C --> J[.seui-loading-state
LoadingState component]
G --> G1[.seui-search
search input + clear btn]
G --> G2[.seui-selectall
Select All / Deselect All]
H --> K[VirtualRecyclerView scaffold]
K --> K1[seui-virtual-pad-top]
K --> K2[seui-virtual-items
ItemsHost]
K --> K3[seui-virtual-pad-bottom]
K2 --> L[.seui-group
GroupView]
K2 --> M[.seui-option
OptionView]
L --> L1[Group label]
L --> L2[Nested .seui-option items]
M --> M1[Checkbox / Radio]
M --> M2[Image optional]
M --> M3[Label text]
M --> M4[highlighted / selected states]
style A fill:#667eea,stroke:#333,stroke-width:3px,color:#fff
style B fill:#f093fb,stroke:#333,stroke-width:2px
style C fill:#4facfe,stroke:#333,stroke-width:2px
style K fill:#43e97b,stroke:#333,stroke-width:2px
style K2 fill:#fa709a,stroke:#333,stroke-width:2px,color:#fff
๐ Component Actual hierarchy:
โข seui-container: SelectBox root โ wraps native <select> (hidden)
โข PlaceHolder: Display the selected value, image, or "no selection" text
โข Directive: arrow/caret indicator, not seui-arrow anymore
โข AccessoryBox: chips display for multi-select (separate component)
โข VirtualRecyclerView scaffold: PadTop + ItemsHost + PadBottom โ not in v1.2
โข EmptyState / LoadingState: separate into popup sub-components
๐ 8. SEARCH CONTROLLER + AJAX FLOW
sequenceDiagram
participant User
participant SB as SearchBox
participant SC as SearchController
participant MM as ModelManager
participant MA as MixedAdapter
participant RV as VirtualRecyclerView
participant Server
User->>SB: Type in search input
SB->>SC: search(query)
SC->>SC: Check ajax config
alt AJAX configured
SC->>MA: changingProp("select") - pre-change
SC->>MM: Show LoadingState
SC->>Server: fetch(url + params)
Server-->>SC: JSON response
SC->>SC: dataTransform(response) โ normalized data
SC->>MM: replace(normalizedData)
MM->>MA: syncFromSource(models)
MA->>MA: setItems(items) โ changingProp + changeProp
MA->>RV: onPropChanged("items") โ render()
RV->>RV: refresh(false) โ full rebuild
SC->>MA: changeProp("selected") - post-change
else No AJAX โ local filter
SC->>MM: updateModel with filtered data
MM->>MA: updateData(newModels)
MA->>MA: Update visibility flags on OptionModels
MA->>MA: onVisibilityChanged callbacks
MA->>RV: refreshItem() - hard reset window
RV->>RV: suspend โ resetState โ rebuildFenwick โ resume
end
RV-->>User: Updated dropdown display
User->>SB: Press Enter / Arrow keys
SB->>SC: Emit navigation event
SC->>MA: highlightNext() / highlightPrev()
MA->>MA: Update currentHighlightIndex
MA->>RV: ensureRendered(index, scrollIntoView)
RV->>RV: mountIndexOnce + scrollToIndex
๐ Search / AJAX detailed:
โข Local filter: update OptionModel.visible โ onVisibilityChanged โ VirtualRecyclerView.refreshItem()
โข refreshItem(): suspend + resetState + rebuildFenwick + resume โ hard reset completely
โข AJAX: dataTransform(response) normalize to standard format before inserting into ModelManager
โข Keyboard navigation: MixedAdapter.flatOptions[] flat index โ ensureRendered() ensures item is mounted
โก 9. EVENT SYSTEM + PLUGIN SYSTEM
graph TB
subgraph CALLBACK_SCHEDULER
A[CallbackScheduler]
A --> B["executeStored Map
key -> callback array"]
A --> C["timerRunner Map
key -> debounce timer"]
B --> D["Callback 1 ... Callback N"]
C --> E["setTimeout debounce"]
end
subgraph USER_EVENT_REGISTRATION
F["on.load callback"]
G["on.beforeShow / on.show"]
H["on.beforeChange / on.change"]
I["on.beforeClose / on.close"]
J["on.search"]
end
subgraph IEVENTS_FLOW
K["iEvents buildEventToken"]
K --> L["token.isContinue"]
K --> M["callback.stopPropagation"]
K --> N["callback.cancel"]
L --> O["Stop iteration across elements"]
end
subgraph ADAPTER_PROPERTY_PIPELINE
P["changingProp propName
pre-change subscribers"]
Q["changeProp propName
post-change subscribers"]
P --> R["select property before user selection"]
Q --> S["selected property after user selection"]
Q --> T["selected internal"]
Q --> U["items after setItems or sync"]
end
subgraph LIFECYCLE_HOOKS
V["Lifecycle hooks
onInit onMount onUpdate onDestroy"]
V --> W["Set callback per hook"]
W --> X["Deduplicated by reference"]
X --> Y["Insertion order execution"]
Y --> Z["handleHookError catches exceptions"]
end
subgraph PLUGIN_HOOKS
AA["SelectivePlugin interface"]
AA --> AB["onBind context"]
AA --> AC["onOpen context"]
AA --> AD["onClose context"]
AA --> AE["onChange context"]
AA --> AF["init destroy onDestroy"]
AB --> AG["PluginContext
selectBox options adapter recycler viewTags actions"]
end
F --> A
G --> A
H --> A
I --> A
J --> A
style A fill:#fa709a,stroke:#333,stroke-width:2px,color:#fff
style K fill:#43e97b,stroke:#333,stroke-width:2px
style P fill:#667eea,stroke:#333,stroke-width:2px,color:#fff
style V fill:#764ba2,stroke:#333,stroke-width:2px,color:#fff
style AA fill:#4facfe,stroke:#333,stroke-width:2px
๐ Actual Event System:
โข CallbackScheduler: debounce per-key โ each callback has its own timer
โข iEvents + EventToken: flow control is like DOM events โ stopPropagation stops iteration through elements
โข Adapter Property Pipeline: 2-phase (changingProp / changeProp) for "select", "selected", "items"
โข Plugin System (new in v1.3): SelectivePlugin interface with hooks onBind/onOpen/onClose/onChange
โข PluginContext: passes selectBox, adapter, recycler, viewTags, actions to every plugin hook
๐งน 10. MEMORY MANAGEMENT & CLEANUP
graph TB
subgraph "BINDING PHASE"
A[bind called] --> B[applySelectBox
generates SEID]
B --> C[Mount DOM wrapper
hide native select]
C --> D[Wire events via Lifecycle hooks]
D --> E[setBinderMap element โ BinderMap]
E --> F[getBindedCommand.push query]
end
subgraph "OBSERVER AUTO-BIND"
G[ElementAdditionObserver
MutationObserver on body] --> H{New select added?}
H -->|Matches query| I[applySelectBox
auto-bind new element]
H -->|No match| J[Skip]
I --> K[Selective.update]
end
subgraph "PARTIAL DESTROY"
L[destroy query] --> M[destroyByQuery]
M --> N[For each element
destroyElement]
N --> O[Disconnect EAObserver temporarily]
O --> P[selectBox.deInit
sub-component teardown]
P --> Q[DOM unwrap
restore native select]
Q --> R[removeBinderMap]
R --> S[Reconnect EAObserver
if bindings remain]
S --> T[selectBox.destroy
lifecycle teardown]
T --> U[Selective.update]
end
subgraph "GLOBAL DESTROY"
V[destroy null] --> W[destroyAll]
W --> X[destroyByQuery ร all queries]
X --> Y[bindedQueries.clear]
Y --> Z[plugins forEach โ destroy + onDestroy]
Z --> AA[EAObserver.disconnect]
AA --> AB[super.destroy โ DESTROYED state]
end
subgraph "VIRTUAL RECYCLER CLEANUP"
AC[VirtualRecyclerView.destroy] --> AD[cancelFrames rAF]
AD --> AE[removeScrollListener]
AE --> AF[resizeObs.disconnect]
AF --> AG[created.forEach remove]
AG --> AH[PadTop/ItemsHost/PadBottom.remove]
AH --> AI[super.destroy]
end
style A fill:#43e97b,stroke:#333,stroke-width:2px
style L fill:#fa709a,stroke:#333,stroke-width:2px,color:#fff
style V fill:#f44336,stroke:#333,stroke-width:2px,color:#fff
style G fill:#667eea,stroke:#333,stroke-width:2px,color:#fff
style AC fill:#ff9800,stroke:#333,stroke-width:2px
๐ Detailed Memory Management:
โข EAObserver disconnect: Pause in destroyElement to avoid re-bind when the DOM changes
โข deInit() vs destroy(): deInit is SelectBox-specific cleanup, destroy is Lifecycle teardown
โข Plugin teardown: calls both plugin.destroy() and plugin.onDestroy()
โข VirtualRecyclerView: cancel rAF, remove scroll listener, disconnect ResizeObserver, remove scaffold nodes
โข BinderMap: removed from WeakMap after destroy โ GC can reclaim references
โ๏ธ 11. CONFIGURATION HIERARCHY
graph TB
A[iStorage.defaultConfig
Priority 3 - Lowest] --> D[Libs.mergeConfig]
B[element.dataset data-*
Priority 2 - Medium
buildConfig] --> D
C[bind options arg
Priority 1 - Highest] --> D
D --> E[Final SelectiveOptions]
subgraph "SelectiveOptions = DefaultConfig + IDs"
F[SEID unique 8-char]
G[SEID_LIST seui-SEID-optionlist]
H[SEID_HOLDER seui-SEID-placeholder]
end
E --> F
E --> G
E --> H
subgraph "DefaultConfig Categories"
I[Visual
width, height, panelHeight
imageMode, imagePosition]
J[Behavior
multiple, searchable, autoclose
disabled, readonly, visible]
K[Selection
selectall, maxSelected
keepSelected]
L[Text / i18n
placeholder, textLoading
textNotFound, textSelectAll]
M[Performance
virtualScroll, animationtime
delaysearchtime, overscan
estimateItemHeight]
N[AJAX
ajax.url, ajax.method
ajax.headers, ajax.data
ajax.dataTransform]
O[Events / on.*
load, beforeShow, show
beforeChange, change
beforeClose, close, search]
P[Plugin hooks
passed via PluginContext]
end
E --> I
E --> J
E --> K
E --> L
E --> M
E --> N
E --> O
style A fill:#e3f2fd,stroke:#1976d2
style B fill:#fff3e0,stroke:#f57c00
style C fill:#e8f5e9,stroke:#388e3c
style E fill:#667eea,stroke:#333,stroke-width:3px,color:#fff
๐ Config Priority (high โ low):
1. bind() options โ highest priority, direct API call
2. data-* attributes โ HTML element dataset, medium
3. defaultConfig โ fallback defaults from iStorage
โข SEID, SEID_LIST, SEID_HOLDER: generated at bind time, not in defaultConfig
โข VirtualScroll options: overscan, estimateItemHeight, dynamicHeights, adaptiveEstimate is new
๐ฆ 12. MODULE SYSTEM & BUILD OUTPUT
graph LR
subgraph SOURCE
A["src/ts/index.ts
ESM entry"]
B["src/ts/global.ts
UMD global entry"]
end
subgraph BUILD_ROLLUP
C[Tree Shaking]
D[Minification]
E[CSS extraction]
F[Source maps]
end
subgraph OUTPUT_DIST
G["selective-ui.esm.js"]
GM["selective-ui.esm.min.js"]
H["selective-ui.umd.js"]
NM["selective-ui.min.js"]
I["selective-ui.css"]
O["selective-ui.min.css"]
end
subgraph USAGE
subgraph ESM_ENV
J["ES Module
import bind from 'selective-ui'"]
end
subgraph BROWSER_ENV
K["Browser Global
window.SelectiveUI"]
M["GLOBAL_SEUI
globalThis.GLOBAL_SEUI"]
end
subgraph STYLE
L["CSS
link selective-ui.min.css"]
end
end
A --> C
B --> C
C --> G
C --> GM
C --> H
C --> NM
D --> GM
D --> NM
E --> I
E --> O
F --> G
F --> H
G --> J
GM --> J
H --> K
NM --> K
NM --> M
I --> L
O --> L
style G fill:#43e97b,stroke:#333,stroke-width:2px
style GM fill:#43e97b,stroke:#333,stroke-width:2px
style H fill:#667eea,stroke:#333,stroke-width:2px,color:#fff
style NM fill:#667eea,stroke:#333,stroke-width:2px,color:#fff
style I fill:#fa709a,stroke:#333,stroke-width:2px,color:#fff
style O fill:#fa709a,stroke:#333,stroke-width:2px,color:#fff
๐ Build Outputs:
โข ESM (index.ts): module-level SECLASS, tree-shakeable, used for bundlers
โข UMD (global.ts): globalThis.GLOBAL_SEUI pattern โ prevents duplicate loading when including multiple times
โข Brotli .br files: pre-compressed for CDN serving, reduces ~70% compared to raw
โข TypeScript declarations (.d.ts): full type safety for consumers
๐ฏ Usage Examples:
- ES Module:
import { bind, find, destroy } from 'selective-ui'
- Browser UMD:
window.GLOBAL_SEUI.bind(".my-select", options)
- Plugin:
registerPlugin({ id: "myPlugin", onBind(ctx) {...} })
- CDN:
https://cdn.jsdelivr.net/npm/selective-ui/dist/selective-ui.min.js