@effect.healthcare/messenger 0.5.0 → 0.6.0
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 +2 -0
- package/dist/messenger.d.ts +9 -2
- package/dist/messenger.js +17 -10
- package/package.json +7 -2
- package/.claude/CLAUDE.md +0 -88
- package/.claude/context/composition.md +0 -921
- package/.claude/context/data.md +0 -1036
- package/.claude/context/event.md +0 -686
- package/.claude/context/gesture.md +0 -614
- package/.claude/context/operation.md +0 -596
- package/.claude/context/state.md +0 -864
- package/.claude/context/testing.md +0 -876
- package/.claude/context/validation.md +0 -350
- package/.claude/context/workflow.md +0 -608
- package/.claude/settings.json +0 -7
- package/.mcp.json +0 -12
- package/dist/.gitkeep +0 -1
- package/src/index.ts +0 -19
- package/src/messenger.meta.ts +0 -139
- package/src/messenger.ts +0 -403
- package/src/messenger.validation.ts +0 -23
- package/web-dev-server.config.mjs +0 -8
package/.claude/context/event.md
DELETED
|
@@ -1,686 +0,0 @@
|
|
|
1
|
-
# Event
|
|
2
|
-
|
|
3
|
-
This document defines an implementation policy for **component events**, then illustrates how specifications are implemented by applying this policy.
|
|
4
|
-
|
|
5
|
-
This document is intentionally **events** only, but part of a set of implementation policies which together defines the policies which should be followed to transform specifications into an implementation which is predictable, maintainable, and production-ready at scale.
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
# How to Read This Policy
|
|
10
|
-
|
|
11
|
-
Rules in this policy fall into three strengths. Read each rule with its strength in mind:
|
|
12
|
-
|
|
13
|
-
- **Principles** are non-negotiable. They derive from framework correctness or TDD discipline and hold for every component. A conflict with a principle is a defect.
|
|
14
|
-
- **Templates** are invariant skeletons with explicit variation points. The skeleton applies to every component; specific steps activate or deactivate based on the component's declared classification (e.g., attribute-backed vs non-attribute, readonly vs writable, payload-bearing vs payload-free). Variation is part of the template, not a deviation from it.
|
|
15
|
-
- **Conventions** are canonical defaults that keep implementations readable across components. A second component may deviate when its shape genuinely warrants it; the deviation should be explained at review time, not forbidden by the policy.
|
|
16
|
-
|
|
17
|
-
When applying this policy to a new component, classify each rule before reconciling conflicts. A conflict with a principle is a defect; a conflict with a template means a variation point needs to be made explicit; a conflict with a convention is a documented deviation.
|
|
18
|
-
|
|
19
|
-
---
|
|
20
|
-
|
|
21
|
-
# 1) Event Contract Must Be Declared in the Specification
|
|
22
|
-
|
|
23
|
-
Each component specification must declare its public event contract under `Events`.
|
|
24
|
-
|
|
25
|
-
`Events` must be an object whose property names are the canonical event names.
|
|
26
|
-
|
|
27
|
-
Canonical event names must use lowerCamelCase and must begin with `on`.
|
|
28
|
-
|
|
29
|
-
Each event definition must include:
|
|
30
|
-
|
|
31
|
-
- `description`
|
|
32
|
-
|
|
33
|
-
An event definition may also include:
|
|
34
|
-
|
|
35
|
-
- `parameters`
|
|
36
|
-
|
|
37
|
-
Interpretation:
|
|
38
|
-
|
|
39
|
-
- `description` defines the semantic meaning of the event
|
|
40
|
-
- `parameters` defines the public runtime payload contract for the event
|
|
41
|
-
- if `parameters` is present, runtime payload must be expressed through a `detail` object
|
|
42
|
-
- if `parameters` is absent, the event emits no declared runtime payload
|
|
43
|
-
|
|
44
|
-
Example specification:
|
|
45
|
-
|
|
46
|
-
```json
|
|
47
|
-
"onpin": {
|
|
48
|
-
"description": "Dispatched when the component becomes pinned.",
|
|
49
|
-
"parameters": [
|
|
50
|
-
{
|
|
51
|
-
"name": "detail",
|
|
52
|
-
"type": "object",
|
|
53
|
-
"properties": {
|
|
54
|
-
"status": {
|
|
55
|
-
"type": "Status",
|
|
56
|
-
"values": ["pinned"]
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
]
|
|
61
|
-
}
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
This defines the public event contract that the implementation must honor.
|
|
65
|
-
|
|
66
|
-
# Template Variation Points
|
|
67
|
-
|
|
68
|
-
Events vary by payload presence:
|
|
69
|
-
|
|
70
|
-
- **Payload-bearing events** — declared with `parameters` in the specification. Runtime payload is delivered through `event.detail` and must match the declared parameter contract (Section 7). Verification under the testing policy requires three behavioral commit pairs: setter existence, handler trigger, and payload contract.
|
|
71
|
-
- **Payload-free events** — declared without `parameters`. They emit no runtime payload. Verification requires two behavioral commit pairs: setter existence and handler trigger.
|
|
72
|
-
|
|
73
|
-
The framework dispatch path (Section 6), the listener-replacement semantics (Section 5), and the accepted-outcome dispatch rule (Section 8) are invariant across both variants.
|
|
74
|
-
|
|
75
|
-
# 2) Events Must Represent Semantic Outcomes
|
|
76
|
-
|
|
77
|
-
This framework distinguishes:
|
|
78
|
-
|
|
79
|
-
- `Events`: the set of observable semantic outcomes exposed by the component
|
|
80
|
-
- `Subscription Properties`: the public component API surface used to assign event handlers
|
|
81
|
-
- `Runtime Dispatch`: the framework event path used to emit public events
|
|
82
|
-
- `Gestures`: the set of low-level user or runtime interactions that may trigger behavior
|
|
83
|
-
|
|
84
|
-
Policy:
|
|
85
|
-
|
|
86
|
-
- events describe what happened as a meaningful component outcome
|
|
87
|
-
- gestures describe what the user did or what low-level interaction occurred
|
|
88
|
-
- subscription properties provide the public assignment surface for handling declared events
|
|
89
|
-
- runtime dispatch provides the authoritative emission path for declared public events
|
|
90
|
-
- events must not be used to declare gestures
|
|
91
|
-
- events must not be used to declare state
|
|
92
|
-
- events must not be used to declare operations
|
|
93
|
-
|
|
94
|
-
Examples of valid semantic events:
|
|
95
|
-
|
|
96
|
-
- `onpin`
|
|
97
|
-
- `onunpin`
|
|
98
|
-
- `onshow`
|
|
99
|
-
- `onhide`
|
|
100
|
-
|
|
101
|
-
Examples that must not normally be modeled as component events:
|
|
102
|
-
|
|
103
|
-
- `onclick`
|
|
104
|
-
- `onhover`
|
|
105
|
-
- `onmousedown`
|
|
106
|
-
|
|
107
|
-
These belong to gesture-level behavior, not semantic outcome reporting.
|
|
108
|
-
|
|
109
|
-
# 3) Public Event Names Must Be Declared in Metadata
|
|
110
|
-
|
|
111
|
-
For every event declared in the specification, the implementation must define corresponding metadata in `<component>.meta.ts`.
|
|
112
|
-
|
|
113
|
-
This metadata defines the canonical event vocabulary for the component.
|
|
114
|
-
|
|
115
|
-
Policy:
|
|
116
|
-
|
|
117
|
-
- every declared public event must have a key in `Event`
|
|
118
|
-
- `Event` contains only declared public event names
|
|
119
|
-
- the implementation must use event metadata consistently as the canonical event vocabulary
|
|
120
|
-
|
|
121
|
-
Example pattern:
|
|
122
|
-
|
|
123
|
-
```typescript
|
|
124
|
-
export const Event = {
|
|
125
|
-
ON_HIDE: "onhide",
|
|
126
|
-
ON_SHOW: "onshow",
|
|
127
|
-
ON_PIN: "onpin",
|
|
128
|
-
ON_UNPIN: "onunpin",
|
|
129
|
-
} as const;
|
|
130
|
-
|
|
131
|
-
export type Event = (typeof Event)[keyof typeof Event];
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
This gives the implementation a stable, centralized event vocabulary that prevents string duplication and keeps naming consistent across metadata, subscription properties, and dispatch.
|
|
135
|
-
|
|
136
|
-
# 4) Event Subscription Must Be Exposed Through Properties
|
|
137
|
-
|
|
138
|
-
For every public event declared in the specification, the component class must expose a corresponding public subscription property.
|
|
139
|
-
|
|
140
|
-
Policy:
|
|
141
|
-
|
|
142
|
-
- every declared public event must have a corresponding public subscription property on the component class
|
|
143
|
-
- the public subscription property name must match the canonical event name
|
|
144
|
-
- the subscription property must store its assigned handler in a private handler slot
|
|
145
|
-
- assigning a subscription property must replace the previously assigned handler for that property
|
|
146
|
-
- assigning `null` must remove the current handler
|
|
147
|
-
|
|
148
|
-
Example pattern:
|
|
149
|
-
|
|
150
|
-
```typescript
|
|
151
|
-
private _onpin: EventListener | null = null;
|
|
152
|
-
|
|
153
|
-
public set onpin(handler: EventListener | null) {
|
|
154
|
-
this._onpin && this.removeEventListener(Event.ON_PIN, this._onpin);
|
|
155
|
-
|
|
156
|
-
this._onpin = handler;
|
|
157
|
-
|
|
158
|
-
this._onpin && this.addEventListener(Event.ON_PIN, this._onpin);
|
|
159
|
-
}
|
|
160
|
-
```
|
|
161
|
-
|
|
162
|
-
This gives the component a stable event subscription API:
|
|
163
|
-
|
|
164
|
-
```typescript
|
|
165
|
-
pin.onpin = (event) => console.log(event.detail.status);
|
|
166
|
-
pin.onpin = null;
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
# 5) Subscription Properties Must Replace Existing Listeners
|
|
170
|
-
|
|
171
|
-
Event subscription properties must behave as single-slot listener assignments.
|
|
172
|
-
|
|
173
|
-
Assignment replaces the previous handler.
|
|
174
|
-
|
|
175
|
-
Required sequence:
|
|
176
|
-
|
|
177
|
-
1. remove the previous listener
|
|
178
|
-
2. store the new handler
|
|
179
|
-
3. attach the new listener if non-null
|
|
180
|
-
|
|
181
|
-
Policy:
|
|
182
|
-
|
|
183
|
-
- a subscription property must manage at most one currently assigned handler
|
|
184
|
-
- reassigning a subscription property must replace the previous handler rather than accumulate listeners
|
|
185
|
-
- assigning `null` must leave no active listener for that subscription property
|
|
186
|
-
- listener replacement behavior must be implemented through normal DOM listener registration and removal
|
|
187
|
-
|
|
188
|
-
Example:
|
|
189
|
-
|
|
190
|
-
```typescript
|
|
191
|
-
public set onunpin(handler: EventListener | null) {
|
|
192
|
-
this._onunpin && this.removeEventListener(Event.ON_UNPIN, this._onunpin);
|
|
193
|
-
|
|
194
|
-
this._onunpin = handler;
|
|
195
|
-
|
|
196
|
-
this._onunpin && this.addEventListener(Event.ON_UNPIN, this._onunpin);
|
|
197
|
-
}
|
|
198
|
-
```
|
|
199
|
-
|
|
200
|
-
To remove the listener:
|
|
201
|
-
|
|
202
|
-
```typescript
|
|
203
|
-
pin.onunpin = null;
|
|
204
|
-
```
|
|
205
|
-
|
|
206
|
-
# 6) Events Must Be Dispatched Through the Framework Dispatch Path
|
|
207
|
-
|
|
208
|
-
Events must not manually call subscribed handlers.
|
|
209
|
-
|
|
210
|
-
They must be emitted through the framework dispatch helper so the event is dispatched through the component's standard event path.
|
|
211
|
-
|
|
212
|
-
Policy:
|
|
213
|
-
|
|
214
|
-
- public events must be dispatched through `_dispatchEvent(...)`
|
|
215
|
-
- the implementation must not manually invoke subscribed handlers as a substitute for event dispatch
|
|
216
|
-
- the dispatch helper is the authoritative runtime path for public event emission
|
|
217
|
-
- event name metadata from `Event` must be used as the dispatch vocabulary
|
|
218
|
-
|
|
219
|
-
Example:
|
|
220
|
-
|
|
221
|
-
```typescript
|
|
222
|
-
const event = { detail: { status } };
|
|
223
|
-
|
|
224
|
-
status === Status.PINNED && this._dispatchEvent(Event.ON_PIN, event);
|
|
225
|
-
status === Status.UNPINNED && this._dispatchEvent(Event.ON_UNPIN, event);
|
|
226
|
-
```
|
|
227
|
-
|
|
228
|
-
This ensures the implementation stays aligned with the DOM event model and keeps public event emission consistent across the component.
|
|
229
|
-
|
|
230
|
-
# 7) Event Payload Must Match the Specification Contract
|
|
231
|
-
|
|
232
|
-
For every event declared with `parameters` in the specification, the implementation must emit a runtime payload that matches that declared contract.
|
|
233
|
-
|
|
234
|
-
Event payload must be delivered through `detail`.
|
|
235
|
-
|
|
236
|
-
Policy:
|
|
237
|
-
|
|
238
|
-
- if an event declares `parameters`, the emitted runtime payload must match the declared parameter contract
|
|
239
|
-
- when runtime payload is emitted, it must be delivered through `detail`
|
|
240
|
-
- emitted payload values must conform to the values and types declared in the specification
|
|
241
|
-
- emitted payload must describe the accepted semantic outcome represented by the event
|
|
242
|
-
- the implementation must not emit undeclared runtime payload for a declared public event
|
|
243
|
-
|
|
244
|
-
Example dispatch:
|
|
245
|
-
|
|
246
|
-
```typescript
|
|
247
|
-
const event = { detail: { visibility } };
|
|
248
|
-
|
|
249
|
-
this._dispatchEvent(Event.ON_SHOW, event);
|
|
250
|
-
```
|
|
251
|
-
|
|
252
|
-
Consumer usage:
|
|
253
|
-
|
|
254
|
-
```typescript
|
|
255
|
-
pin.onshow = (event) => console.log(event.detail.visibility);
|
|
256
|
-
```
|
|
257
|
-
|
|
258
|
-
In this example, the emitted `detail.visibility` value must match the event contract declared in the specification for `onshow`.
|
|
259
|
-
|
|
260
|
-
# 8) Events Must Only Be Emitted After Accepted Transitions
|
|
261
|
-
|
|
262
|
-
Public events that represent semantic outcomes must be emitted only after the component has accepted the outcome they describe.
|
|
263
|
-
|
|
264
|
-
Policy:
|
|
265
|
-
|
|
266
|
-
- no event must be emitted for a no-op assignment
|
|
267
|
-
- no event must be emitted for a rejected transition
|
|
268
|
-
- no event must be emitted before the component accepts the semantic outcome
|
|
269
|
-
- when an event represents a state outcome, dispatch must occur after the authoritative mutation path has accepted and applied the transition
|
|
270
|
-
|
|
271
|
-
Example pattern:
|
|
272
|
-
|
|
273
|
-
```typescript
|
|
274
|
-
public set status(status: Status) {
|
|
275
|
-
status = Validate.status(status);
|
|
276
|
-
|
|
277
|
-
if (this._status === status) return;
|
|
278
|
-
|
|
279
|
-
this._status = status;
|
|
280
|
-
this.setAttribute(Attributes.STATUS, status);
|
|
281
|
-
|
|
282
|
-
const event = { detail: { status } };
|
|
283
|
-
|
|
284
|
-
status === Status.PINNED && this._dispatchEvent(Event.ON_PIN, event);
|
|
285
|
-
status === Status.UNPINNED && this._dispatchEvent(Event.ON_UNPIN, event);
|
|
286
|
-
}
|
|
287
|
-
```
|
|
288
|
-
|
|
289
|
-
In this example, the event is emitted only after validation succeeds, the guard allows the transition, and the canonical state has been updated to the accepted outcome.
|
|
290
|
-
|
|
291
|
-
# 9) Naming Must Be Predictably Transformed Across Layers
|
|
292
|
-
|
|
293
|
-
Public event naming must remain consistent across the specification, metadata, component API, and runtime dispatch.
|
|
294
|
-
|
|
295
|
-
Policy:
|
|
296
|
-
|
|
297
|
-
- the specification defines the canonical public event name
|
|
298
|
-
- metadata must define a corresponding `Event` key whose value is that canonical event name
|
|
299
|
-
- the component class must expose a public subscription property using that same canonical event name
|
|
300
|
-
- runtime dispatch must use the corresponding metadata value from `Event`
|
|
301
|
-
- naming must remain predictably traceable across all implementation layers
|
|
302
|
-
|
|
303
|
-
Transformation pattern:
|
|
304
|
-
|
|
305
|
-
| Layer | Example |
|
|
306
|
-
|---|---|
|
|
307
|
-
| Specification | `onpin` |
|
|
308
|
-
| Metadata | `Event.ON_PIN = "onpin"` |
|
|
309
|
-
| Component API | `set onpin(handler)` |
|
|
310
|
-
| Dispatch | `_dispatchEvent(Event.ON_PIN, ...)` |
|
|
311
|
-
|
|
312
|
-
This predictable transformation keeps the implementation directly traceable back to the specification and consistent with the framework's metadata-driven design.
|
|
313
|
-
|
|
314
|
-
---
|
|
315
|
-
|
|
316
|
-
# Policy Checklist
|
|
317
|
-
|
|
318
|
-
A compliant component must:
|
|
319
|
-
|
|
320
|
-
1. Declare all public events under `Events` in the specification.
|
|
321
|
-
2. Define each event using the required specification contract.
|
|
322
|
-
3. Use `description` for every declared event.
|
|
323
|
-
4. Use `parameters` only when the event emits runtime payload.
|
|
324
|
-
5. Express runtime payload through a `detail` object when payload is emitted.
|
|
325
|
-
6. Ensure events represent semantic outcomes rather than low-level gestures.
|
|
326
|
-
7. Avoid modeling gestures, state, or operations as events.
|
|
327
|
-
8. Define all declared public event names in `<component>.meta.ts`.
|
|
328
|
-
9. Use `Event` metadata as the canonical event vocabulary.
|
|
329
|
-
10. Expose a public subscription property for every declared public event.
|
|
330
|
-
11. Keep each subscription property name aligned with the canonical event name from the specification.
|
|
331
|
-
12. Type subscription property setters as `EventListener | null` to formally support handler removal.
|
|
332
|
-
13. Store each assigned subscription handler in a private handler slot typed `EventListener | null`.
|
|
333
|
-
14. Replace the previous listener when a subscription property is reassigned.
|
|
334
|
-
15. Remove the active listener when a subscription property is assigned `null`.
|
|
335
|
-
16. Dispatch public events through `_dispatchEvent(...)`.
|
|
336
|
-
17. Never manually invoke subscribed handlers as a substitute for event dispatch.
|
|
337
|
-
18. Emit runtime payload through `event.detail`.
|
|
338
|
-
19. Ensure emitted payload matches the specification contract for the event.
|
|
339
|
-
20. Emit only values and shapes declared by the specification.
|
|
340
|
-
21. Emit events only after the semantic outcome has been accepted.
|
|
341
|
-
22. Emit no event for no-op assignments.
|
|
342
|
-
23. Emit no event for rejected transitions.
|
|
343
|
-
24. Keep naming predictably aligned across specification, metadata, component API, and runtime dispatch.
|
|
344
|
-
|
|
345
|
-
---
|
|
346
|
-
|
|
347
|
-
# Policy Applied to Specification
|
|
348
|
-
|
|
349
|
-
Specification fragment:
|
|
350
|
-
|
|
351
|
-
```json
|
|
352
|
-
{
|
|
353
|
-
"Events": {
|
|
354
|
-
"onhide": {
|
|
355
|
-
"description": "Dispatched when pin 'visibility' state changes from 'visible' to 'hidden'.",
|
|
356
|
-
"parameters": [
|
|
357
|
-
{
|
|
358
|
-
"name": "detail",
|
|
359
|
-
"type": "object",
|
|
360
|
-
"description": "Contains the new visibility state of the component.",
|
|
361
|
-
"properties": {
|
|
362
|
-
"visibility": {
|
|
363
|
-
"type": "Visibility",
|
|
364
|
-
"values": ["hidden"]
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
]
|
|
369
|
-
},
|
|
370
|
-
"onshow": {
|
|
371
|
-
"description": "Dispatched when pin 'visibility' state changes from 'hidden' to 'visible'.",
|
|
372
|
-
"parameters": [
|
|
373
|
-
{
|
|
374
|
-
"name": "detail",
|
|
375
|
-
"type": "object",
|
|
376
|
-
"description": "Contains the new visibility state of the component.",
|
|
377
|
-
"properties": {
|
|
378
|
-
"visibility": {
|
|
379
|
-
"type": "Visibility",
|
|
380
|
-
"values": ["visible"]
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
]
|
|
385
|
-
},
|
|
386
|
-
"onpin": {
|
|
387
|
-
"description": "Dispatched when pin 'status' changes from 'unpinned' to 'pinned'.",
|
|
388
|
-
"parameters": [
|
|
389
|
-
{
|
|
390
|
-
"name": "detail",
|
|
391
|
-
"type": "object",
|
|
392
|
-
"description": "Contains the new status of the component.",
|
|
393
|
-
"properties": {
|
|
394
|
-
"status": {
|
|
395
|
-
"type": "Status",
|
|
396
|
-
"values": ["pinned"]
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
]
|
|
401
|
-
},
|
|
402
|
-
"onunpin": {
|
|
403
|
-
"description": "Dispatched when pin 'status' changes from 'pinned' to 'unpinned'.",
|
|
404
|
-
"parameters": [
|
|
405
|
-
{
|
|
406
|
-
"name": "detail",
|
|
407
|
-
"type": "object",
|
|
408
|
-
"description": "Contains the new status of the component.",
|
|
409
|
-
"properties": {
|
|
410
|
-
"status": {
|
|
411
|
-
"type": "Status",
|
|
412
|
-
"values": ["unpinned"]
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
]
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
```
|
|
421
|
-
|
|
422
|
-
How this specification is implemented:
|
|
423
|
-
|
|
424
|
-
- every declared event is represented in metadata as part of the canonical `Event` vocabulary
|
|
425
|
-
- every declared event is exposed through a corresponding public subscription property
|
|
426
|
-
- `onhide`
|
|
427
|
-
- subscription property typed `EventListener | null`
|
|
428
|
-
- dispatched by the `visibility` setter after accepting a transition to `Visibility.HIDDEN`
|
|
429
|
-
- payload: `detail.visibility = "hidden"`
|
|
430
|
-
- `onshow`
|
|
431
|
-
- subscription property typed `EventListener | null`
|
|
432
|
-
- dispatched by the `visibility` setter after accepting a transition to `Visibility.VISIBLE`
|
|
433
|
-
- payload: `detail.visibility = "visible"`
|
|
434
|
-
- `onpin`
|
|
435
|
-
- subscription property typed `EventListener | null`
|
|
436
|
-
- dispatched by the `status` setter after accepting a transition to `Status.PINNED`
|
|
437
|
-
- payload: `detail.status = "pinned"`
|
|
438
|
-
- `onunpin`
|
|
439
|
-
- subscription property typed `EventListener | null`
|
|
440
|
-
- dispatched by the `status` setter after accepting a transition to `Status.UNPINNED`
|
|
441
|
-
- payload: `detail.status = "unpinned"`
|
|
442
|
-
|
|
443
|
-
Implementation:
|
|
444
|
-
|
|
445
|
-
```typescript
|
|
446
|
-
private _onhide: EventListener | null = null;
|
|
447
|
-
private _onshow: EventListener | null = null;
|
|
448
|
-
private _onpin: EventListener | null = null;
|
|
449
|
-
private _onunpin: EventListener | null = null;
|
|
450
|
-
|
|
451
|
-
public set onhide(handler: EventListener | null) {
|
|
452
|
-
this._onhide && this.removeEventListener(Event.ON_HIDE, this._onhide);
|
|
453
|
-
this._onhide = handler;
|
|
454
|
-
this._onhide && this.addEventListener(Event.ON_HIDE, this._onhide);
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
public set onshow(handler: EventListener | null) {
|
|
458
|
-
this._onshow && this.removeEventListener(Event.ON_SHOW, this._onshow);
|
|
459
|
-
this._onshow = handler;
|
|
460
|
-
this._onshow && this.addEventListener(Event.ON_SHOW, this._onshow);
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
public set onpin(handler: EventListener | null) {
|
|
464
|
-
this._onpin && this.removeEventListener(Event.ON_PIN, this._onpin);
|
|
465
|
-
this._onpin = handler;
|
|
466
|
-
this._onpin && this.addEventListener(Event.ON_PIN, this._onpin);
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
public set onunpin(handler: EventListener | null) {
|
|
470
|
-
this._onunpin && this.removeEventListener(Event.ON_UNPIN, this._onunpin);
|
|
471
|
-
this._onunpin = handler;
|
|
472
|
-
this._onunpin && this.addEventListener(Event.ON_UNPIN, this._onunpin);
|
|
473
|
-
}
|
|
474
|
-
```
|
|
475
|
-
|
|
476
|
-
Dispatch from within the authoritative state setters:
|
|
477
|
-
|
|
478
|
-
```typescript
|
|
479
|
-
public set visibility(visibility: Visibility) {
|
|
480
|
-
visibility = visibility ?? Visibility.VISIBLE;
|
|
481
|
-
visibility = Validate.visibility(visibility);
|
|
482
|
-
|
|
483
|
-
if (this._visibility === visibility) return;
|
|
484
|
-
|
|
485
|
-
this._visibility = visibility;
|
|
486
|
-
visibility === Visibility.VISIBLE && this.removeAttribute(Attributes.VISIBILITY);
|
|
487
|
-
visibility === Visibility.HIDDEN && this.setAttribute(Attributes.VISIBILITY, visibility);
|
|
488
|
-
|
|
489
|
-
const event = { detail: { visibility } };
|
|
490
|
-
|
|
491
|
-
visibility === Visibility.HIDDEN && this._dispatchEvent(Event.ON_HIDE, event);
|
|
492
|
-
visibility === Visibility.VISIBLE && this._dispatchEvent(Event.ON_SHOW, event);
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
public set status(status: Status) {
|
|
496
|
-
status = Validate.status(status);
|
|
497
|
-
|
|
498
|
-
if (this._status === status) return;
|
|
499
|
-
|
|
500
|
-
this._status = status;
|
|
501
|
-
this.setAttribute(Attributes.STATUS, status);
|
|
502
|
-
|
|
503
|
-
const event = { detail: { status } };
|
|
504
|
-
|
|
505
|
-
status === Status.PINNED && this._dispatchEvent(Event.ON_PIN, event);
|
|
506
|
-
status === Status.UNPINNED && this._dispatchEvent(Event.ON_UNPIN, event);
|
|
507
|
-
}
|
|
508
|
-
```
|
|
509
|
-
|
|
510
|
-
How the implementation applies the policy:
|
|
511
|
-
|
|
512
|
-
- the specification defines the public event contract under `Events`
|
|
513
|
-
- metadata defines the canonical event vocabulary
|
|
514
|
-
- the component class exposes each declared event as a public subscription property with the same canonical name
|
|
515
|
-
- subscription properties are typed `EventListener | null` to formally support both handler assignment and handler removal
|
|
516
|
-
- subscription properties manage a single handler slot — reassignment replaces, `null` removes
|
|
517
|
-
- events are dispatched through `_dispatchEvent(...)`, not by manually invoking handlers
|
|
518
|
-
- payload is delivered through `detail` and matches the values declared in the specification
|
|
519
|
-
- events are emitted only after the authoritative state setter has validated, guarded, mutated, and reflected the accepted transition
|
|
520
|
-
- naming is predictably traceable: specification → `Event` metadata → subscription property → `_dispatchEvent` call
|
|
521
|
-
|
|
522
|
-
---
|
|
523
|
-
|
|
524
|
-
# Component Event Specification Schema
|
|
525
|
-
|
|
526
|
-
JSON Schema:
|
|
527
|
-
|
|
528
|
-
```json
|
|
529
|
-
{
|
|
530
|
-
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
531
|
-
"$id": "https://scalable.software/schema/component-events.schema.json",
|
|
532
|
-
"title": "Component Events",
|
|
533
|
-
"description": "Schema for the Events object in a component specification.",
|
|
534
|
-
"type": "object",
|
|
535
|
-
"propertyNames": {
|
|
536
|
-
"type": "string",
|
|
537
|
-
"pattern": "^on[a-z][a-zA-Z0-9]*$",
|
|
538
|
-
"description": "Canonical event name in lowerCamelCase beginning with 'on', for example: onpin, onunpin, onshow, onhide."
|
|
539
|
-
},
|
|
540
|
-
"additionalProperties": {
|
|
541
|
-
"type": "object",
|
|
542
|
-
"description": "An event definition.",
|
|
543
|
-
"required": ["description"],
|
|
544
|
-
"additionalProperties": false,
|
|
545
|
-
"properties": {
|
|
546
|
-
"description": {
|
|
547
|
-
"type": "string",
|
|
548
|
-
"minLength": 1,
|
|
549
|
-
"description": "Human-readable description of the semantic outcome represented by the event."
|
|
550
|
-
},
|
|
551
|
-
"parameters": {
|
|
552
|
-
"type": "array",
|
|
553
|
-
"description": "Ordered list of runtime payload parameters for the event. When present, runtime payload must be expressed through a detail object.",
|
|
554
|
-
"minItems": 1,
|
|
555
|
-
"items": {
|
|
556
|
-
"type": "object",
|
|
557
|
-
"required": ["name", "type"],
|
|
558
|
-
"additionalProperties": false,
|
|
559
|
-
"properties": {
|
|
560
|
-
"name": {
|
|
561
|
-
"type": "string",
|
|
562
|
-
"const": "detail",
|
|
563
|
-
"description": "The public runtime payload channel for component events. Event payload must be exposed through detail."
|
|
564
|
-
},
|
|
565
|
-
"type": {
|
|
566
|
-
"type": "string",
|
|
567
|
-
"const": "object",
|
|
568
|
-
"description": "Event payload type. Runtime payload for component events must be expressed as an object."
|
|
569
|
-
},
|
|
570
|
-
"description": {
|
|
571
|
-
"type": "string",
|
|
572
|
-
"minLength": 1,
|
|
573
|
-
"description": "Human-readable description of the detail payload."
|
|
574
|
-
},
|
|
575
|
-
"properties": {
|
|
576
|
-
"type": "object",
|
|
577
|
-
"description": "The object properties emitted through event.detail.",
|
|
578
|
-
"minProperties": 1,
|
|
579
|
-
"propertyNames": {
|
|
580
|
-
"type": "string",
|
|
581
|
-
"pattern": "^[a-z][a-zA-Z0-9]*$",
|
|
582
|
-
"description": "Canonical property name in lowerCamelCase, for example: status, visibility."
|
|
583
|
-
},
|
|
584
|
-
"additionalProperties": {
|
|
585
|
-
"type": "object",
|
|
586
|
-
"required": ["type"],
|
|
587
|
-
"additionalProperties": false,
|
|
588
|
-
"properties": {
|
|
589
|
-
"type": {
|
|
590
|
-
"type": "string",
|
|
591
|
-
"minLength": 1,
|
|
592
|
-
"description": "Declared type of the detail property, for example: Status, Visibility, string, boolean."
|
|
593
|
-
},
|
|
594
|
-
"values": {
|
|
595
|
-
"type": "array",
|
|
596
|
-
"minItems": 1,
|
|
597
|
-
"description": "Allowed values for this emitted property when the event contract constrains the outcome to a subset of the value domain."
|
|
598
|
-
},
|
|
599
|
-
"description": {
|
|
600
|
-
"type": "string",
|
|
601
|
-
"minLength": 1,
|
|
602
|
-
"description": "Human-readable description of the emitted property."
|
|
603
|
-
}
|
|
604
|
-
}
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
}
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
}
|
|
612
|
-
}
|
|
613
|
-
```
|
|
614
|
-
|
|
615
|
-
Example JSON:
|
|
616
|
-
|
|
617
|
-
```json
|
|
618
|
-
{
|
|
619
|
-
"Events": {
|
|
620
|
-
"onhide": {
|
|
621
|
-
"description": "Dispatched when pin 'visibility' state changes from 'visible' to 'hidden'.",
|
|
622
|
-
"parameters": [
|
|
623
|
-
{
|
|
624
|
-
"name": "detail",
|
|
625
|
-
"type": "object",
|
|
626
|
-
"description": "Contains the new visibility state of the component.",
|
|
627
|
-
"properties": {
|
|
628
|
-
"visibility": {
|
|
629
|
-
"type": "Visibility",
|
|
630
|
-
"values": ["hidden"]
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
]
|
|
635
|
-
},
|
|
636
|
-
"onshow": {
|
|
637
|
-
"description": "Dispatched when pin 'visibility' state changes from 'hidden' to 'visible'.",
|
|
638
|
-
"parameters": [
|
|
639
|
-
{
|
|
640
|
-
"name": "detail",
|
|
641
|
-
"type": "object",
|
|
642
|
-
"description": "Contains the new visibility state of the component.",
|
|
643
|
-
"properties": {
|
|
644
|
-
"visibility": {
|
|
645
|
-
"type": "Visibility",
|
|
646
|
-
"values": ["visible"]
|
|
647
|
-
}
|
|
648
|
-
}
|
|
649
|
-
}
|
|
650
|
-
]
|
|
651
|
-
},
|
|
652
|
-
"onpin": {
|
|
653
|
-
"description": "Dispatched when pin 'status' changes from 'unpinned' to 'pinned'.",
|
|
654
|
-
"parameters": [
|
|
655
|
-
{
|
|
656
|
-
"name": "detail",
|
|
657
|
-
"type": "object",
|
|
658
|
-
"description": "Contains the new status of the component.",
|
|
659
|
-
"properties": {
|
|
660
|
-
"status": {
|
|
661
|
-
"type": "Status",
|
|
662
|
-
"values": ["pinned"]
|
|
663
|
-
}
|
|
664
|
-
}
|
|
665
|
-
}
|
|
666
|
-
]
|
|
667
|
-
},
|
|
668
|
-
"onunpin": {
|
|
669
|
-
"description": "Dispatched when pin 'status' changes from 'pinned' to 'unpinned'.",
|
|
670
|
-
"parameters": [
|
|
671
|
-
{
|
|
672
|
-
"name": "detail",
|
|
673
|
-
"type": "object",
|
|
674
|
-
"description": "Contains the new status of the component.",
|
|
675
|
-
"properties": {
|
|
676
|
-
"status": {
|
|
677
|
-
"type": "Status",
|
|
678
|
-
"values": ["unpinned"]
|
|
679
|
-
}
|
|
680
|
-
}
|
|
681
|
-
}
|
|
682
|
-
]
|
|
683
|
-
}
|
|
684
|
-
}
|
|
685
|
-
}
|
|
686
|
-
```
|