kreftjs 0.0.17-beta → 0.0.20-beta

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.
@@ -0,0 +1,330 @@
1
+ # الفصل الثاني: لغة Kreft
2
+
3
+ ملف `.kreft` له ثلاثة أجزاء:
4
+
5
+ 1. **السكربت:** كل ما هو خارج `<view>` و`<style>`. كل سطر يبدأ بكلمة مفتاحية.
6
+ 2. **`<view>`:** HTML المكوّن.
7
+ 3. **`<style>`** (اختياري): CSS. أضف `scoped` ليسري على هذا المكوّن فقط.
8
+
9
+ ## الكلمات المفتاحية
10
+
11
+ | الكلمة | مثال | المعنى |
12
+ |---|---|---|
13
+ | `state` | `state count = 0` | قيمة قابلة للتغيّر. عند تغيّرها تتحدث الصفحة. |
14
+ | `derive` | `derive double = count * 2` | قيمة تُحسب من قيم أخرى وتتحدث وحدها. |
15
+ | `action` | `action add(n) { count += n }` | دالة تستدعيها من الصفحة أو من أفعال أخرى. |
16
+ | `when` | `when count > 10 { … }` | ينفّذ الكتلة كلما صار الشرط صحيحًا. |
17
+ | `watch` | `watch count { … }` | ينفّذ الكتلة كلما تغيّرت القيمة. داخلها `value` الجديدة و`old` السابقة. |
18
+ | `on` | `on ready { … }` | دورة الحياة: `start` و`ready` و`update` و`show` و`hide` و`destroy`. |
19
+ | `prop` | `prop start = 0` | قيمة يمررها الأب. ما بعد `=` هو الافتراضي. |
20
+ | `event` | `event save, remove` | أحداث يرسلها المكوّن لأبيه بـ `send('save', data)`. |
21
+ | `use` | `use cart` | يستعمل مخزنًا مشتركًا (store) أو منطقًا مشتركًا من ملف `.js`. |
22
+ | `share` / `receive` | `share theme = 'dark'` | يعطي قيمة لكل ما بداخل المكوّن / يأخذ قيمة شاركها أب. |
23
+ | `import` | `import Card from './Card.kreft'` | يستعمل مكوّنًا آخر أو وحدة JavaScript. |
24
+ | `saved` | `saved todos = []` | كـ `state` لكن محفوظة في المتصفح (localStorage). |
25
+ | `load` | `load users = api.get('/api/users')` | يجلب بيانات ويعطيك `users.data` و`users.loading` و`users.error` و`users.reload()`. |
26
+
27
+ داخل السكربت تستعمل الأسماء **مباشرة**: اكتب `count++` وليس `this.count++`.
28
+
29
+ ## state: الحالة
30
+
31
+ ```html
32
+ state count = 0
33
+
34
+ <view>
35
+ <button @click="count++">النقر: {count}</button>
36
+ </view>
37
+ ```
38
+
39
+ كل تغيير على `state` يحدّث الجزء المتأثر فقط من الصفحة، لا الصفحة كلها. التغييرات العميقة تُرصد أيضًا: `todos.push(...)` و`todo.done = true`.
40
+
41
+ ## derive: القيم المشتقة
42
+
43
+ `derive` قيمة تُحسب من قيم أخرى. هي ليست دالة تستدعيها: تُعيد حساب نفسها تلقائيًا كلما تغيّرت القيم التي تقرؤها.
44
+
45
+ ```html
46
+ state price = 10
47
+ state qty = 2
48
+
49
+ derive total = price * qty // 20
50
+ derive label = `الإجمالي: ${total} $` // يستعمل derive آخر
51
+
52
+ action more() { qty++ } // يتحدّث total وlabel وحدهما
53
+
54
+ <view>
55
+ <p>{label}</p>
56
+ <button @click={more}>+1</button>
57
+ </view>
58
+ ```
59
+
60
+ - اكتب `{total}` وليس `{total()}`.
61
+ - لا تعيّن قيمة لـ `derive`. غيّر الـ `state` الذي يقرؤه.
62
+ - يمكنه استعمال مشتقات أخرى.
63
+
64
+ ## action: الأفعال
65
+
66
+ الفعل الذي يستخدم `await` يصبح غير متزامن (async) تلقائيًا:
67
+
68
+ ```html
69
+ state users = []
70
+ state busy = false
71
+
72
+ action async_example() {
73
+ busy = true
74
+ users = await api.get('/api/users')
75
+ busy = false
76
+ }
77
+ ```
78
+
79
+ ## when و watch و on
80
+
81
+ ```html
82
+ state todos = []
83
+ derive remaining = todos.filter((t) => !t.done).length
84
+
85
+ // ينفّذ عندما يصير الشرط صحيحًا
86
+ when todos.length && remaining === 0 {
87
+ bus.send('toast', 'أنجزت كل المهام!')
88
+ }
89
+
90
+ // ينفّذ عند كل تغيّر
91
+ watch remaining {
92
+ document.title = `(${value}) مهام`
93
+ }
94
+
95
+ on ready { console.log('المكوّن على الشاشة') }
96
+ on destroy { console.log('سيُزال المكوّن: نظّف هنا') }
97
+ ```
98
+
99
+ متى أستعمل أيًّا منها؟
100
+
101
+ - **`derive`** لحساب قيمة ستعرضها.
102
+ - **`watch`** لتنفيذ عمل جانبي عند التغيّر: حفظ، أو استدعاء API، أو كتابة في `document.title`.
103
+ - **`when`** عندما يهمك لحظة تحوّل الشرط إلى صحيح.
104
+ - **`action`** دالة تستدعيها أنت بنفسك.
105
+
106
+ ## مثال كامل
107
+
108
+ ```html
109
+ <!-- TodoPage.kreft -->
110
+ state todos = []
111
+ state text = ''
112
+
113
+ derive remaining = todos.filter((t) => !t.done).length
114
+
115
+ action add() {
116
+ if (!text.trim()) return
117
+ todos.push({ id: Date.now(), text, done: false })
118
+ text = ''
119
+ }
120
+
121
+ action clearDone() {
122
+ todos = todos.filter((t) => !t.done)
123
+ }
124
+
125
+ when todos.length && remaining === 0 {
126
+ bus.send('toast', 'All done!')
127
+ }
128
+
129
+ <view>
130
+ <form @submit.prevent={add}>
131
+ <input k-model="text" placeholder="مهمة جديدة">
132
+ </form>
133
+ <label k-each="todo in todos" k-key="todo.id">
134
+ <input type="checkbox" k-model="todo.done"> {todo.text}
135
+ </label>
136
+ <p>{remaining} متبقية · <button @click={clearDone}>احذف المنجز</button></p>
137
+ </view>
138
+ ```
139
+
140
+ ## ما يجب أن تعرفه
141
+
142
+ - سطر لا يبدأ بكلمة مفتاحية هو JavaScript عادي (`let timer`, `const max = 10`). تستعمله أفعالك لكن الصفحة (`view`) لا تراه. استعمل `state` أو `derive` أو `action` لكل ما تحتاجه الصفحة.
143
+ - كل دوال Kreft متاحة في السكربت: `bus` و`store` و`signal` و`watch` و`tick` و`send` و`props` و`$route` و`$router`.
144
+ - رسائل الخطأ تسمي الملف والسطر، مثل: `"state" needs a name, like: state count = 0 (Bad.kreft, line 3)`.
145
+
146
+ ## دورة الحياة
147
+
148
+ | الخطاف | يعمل |
149
+ |---|---|
150
+ | `on start` | قبل أن يظهر المكوّن. مناسب للبيانات الأولى. |
151
+ | `on ready` | المكوّن على الشاشة. |
152
+ | `on update` | بعد تغيّر الصفحة. |
153
+ | `on show` / `on hide` | مكوّن [محتفَظ به](03-components-and-state.md) يعود / يُوضع جانبًا. |
154
+ | `on destroy` | يُزال المكوّن. نظّف هنا. |
155
+
156
+ ## القوالب (Templates)
157
+
158
+ ### عرض القيم
159
+
160
+ ```html
161
+ <h1>أهلًا {name}</h1>
162
+ <p>{count} عنصر · المجموع {total.toFixed(2)}</p>
163
+ <p>{isAdmin ? 'مدير' : 'مستخدم'}</p>
164
+ ```
165
+
166
+ القيم تُهرَّب (escaped) دائمًا فهي آمنة للعرض. لعرض `{` حرفيًا اكتب `\{`. وفي القوالب اكتب `{a < b}` مع مسافة، لأن `<` متبوعة بحرف مباشرة تبدأ وسمًا.
167
+
168
+ ### الخصائص (Attributes)
169
+
170
+ ```html
171
+ <img src={user.photo} alt={user.name}> <!-- القيمة كما هي -->
172
+ <button disabled={count === 0}>تصفير</button> <!-- false تزيل الخاصية -->
173
+ <div class="card {active ? 'on' : ''}"> <!-- بناء نص -->
174
+ <a href="/users/{user.id}">الملف الشخصي</a>
175
+ ```
176
+
177
+ `:class` و`:style` يقبلان كائنات أيضًا: `:class="{ done: todo.done }"`.
178
+
179
+ ### الأحداث
180
+
181
+ ```html
182
+ <button @click={add}>إضافة</button>
183
+ <button @click="add(5)">أضف 5</button>
184
+ <input @input="search($event.target.value)">
185
+ <form @submit.prevent={save}>
186
+ <input @keyup.enter={send}>
187
+ ```
188
+
189
+ المعدِّلات: `.prevent` و`.stop` و`.self` و`.once` و`.capture` و`.passive`. المفاتيح: `.enter` و`.esc` و`.space` و`.tab` و`.up` و`.down` و`.ctrl` و`.shift` و`.alt` و`.meta` و`.exact`.
190
+
191
+ ### التوجيهات (Directives)
192
+
193
+ | التوجيه | ماذا يفعل |
194
+ |---|---|
195
+ | `k-if` / `k-elif` / `k-else` | يضيف العنصر أو يزيله. |
196
+ | `k-each="item in list"` + `k-key="item.id"` | يكرر العنصر. أيضًا `(item, i) in list` و`(value, key) in object` و`n in 10`. |
197
+ | `k-model="name"` | ربط ثنائي الاتجاه لـ input وtextarea وselect وcheckbox وradio. المعدِّلات: `.trim` `.number` `.lazy`. |
198
+ | `k-show` | يخفي بـ CSS بدل أن يزيل. |
199
+ | `k-text` / `k-html` | يضبط نصًا أو HTML. `k-html` يُنظَّف من السكربتات لكن اربط HTML تثق به فقط. |
200
+ | `k-ref="box"` | يعطيك العنصر: `state box = null` يستقبله. |
201
+ | `k-skip` | يترك العنصر كما هو (لعينات الشيفرة التي فيها `{ }`). |
202
+ | `k-once` | يرسم العنصر مرة واحدة ولا يحدّثه. |
203
+ | `k-memo="[a, b]"` | يحدّث العنصر فقط إذا تغيّرت `a` أو `b`. |
204
+ | `k-animate="fade"` | يحرّك العنصر عند مجيئه وذهابه. |
205
+ | `<template k-if>` / `<template k-each>` | يجمّع عناصر دون غلاف. |
206
+ | `<kreft-component :is="name">` | يعرض مكوّنًا يُختار أثناء التشغيل. |
207
+ | `<kreft-portal to="body">` | يرسم المحتوى في مكان آخر (نوافذ منبثقة، تنبيهات). |
208
+
209
+ ### الحلقات
210
+
211
+ ```html
212
+ <ul>
213
+ {for user in users key user.id}
214
+ <li>{user.name}</li>
215
+ {empty}
216
+ <li>لا يوجد مستخدمون بعد</li>
217
+ {/for}
218
+ </ul>
219
+ ```
220
+
221
+ - `key user.id` اختياري؛ يمكّن Kreft من الاحتفاظ بالصفوف الصحيحة ونقلها.
222
+ - `{empty}` اختياري ويظهر عند فراغ القائمة.
223
+ - `{for (user, i) in users}` يعطي الفهرس، و`{for (value, key) in object}` يمر على كائن، و`{for n in 5}` يعدّ من 1 إلى 5.
224
+ - `k-each` على عنصر يفعل الشيء نفسه: `<li k-each="user in users" k-key="user.id">`.
225
+
226
+ ### الحركات (Animations)
227
+
228
+ ```html
229
+ <p k-if="open" k-animate="fade">مرحبًا</p>
230
+ <div k-show="menu" k-animate="slide">…</div>
231
+
232
+ <ul>
233
+ {for item in items key item.id animate slide}
234
+ <li>{item.name}</li>
235
+ {/for}
236
+ </ul>
237
+ ```
238
+
239
+ الجاهز: `fade` و`slide` و`scale` و`pop`. وحركتك الخاصة هي CSS بهذه الفئات (استبدل `name`):
240
+
241
+ | الفئة | متى |
242
+ |---|---|
243
+ | `name-in` | طوال وقت الدخول (ضع `transition` هنا). |
244
+ | `name-in-from` | اللحظة الأولى: من أين يأتي. |
245
+ | `name-out` | طوال وقت الخروج. |
246
+ | `name-out-to` | إلى أين يذهب. |
247
+ | `name-move` | عنصر قائمة ينتقل إلى مكانه الجديد. |
248
+
249
+ ```css
250
+ .drop-in, .drop-out { transition: all .3s ease }
251
+ .drop-in-from { opacity: 0; transform: translateY(-30px) }
252
+ .drop-out-to { opacity: 0; transform: translateX(60px) }
253
+ ```
254
+
255
+ - ينتظر Kreft أطول `transition` أو `animation` قبل أن يزيل العنصر.
256
+ - `k-animate.after` يدع العنصر القديم يخرج قبل أن يدخل الجديد.
257
+ - لا شيء يتحرك عند فتح الصفحة أول مرة؛ أضف `.first` ليتحرك حينها أيضًا.
258
+
259
+ ### صفحات أسرع
260
+
261
+ Kreft يغيّر أصلًا النص أو الخاصية التي تغيّرت فقط. للصفحات الكبيرة جدًا:
262
+
263
+ ```html
264
+ <footer k-once>فُتحت في {openedAt}</footer>
265
+
266
+ {for row in rows key row.id}
267
+ <tr k-memo="[row.id === selected]" class={row.id === selected ? 'on' : ''}>…</tr>
268
+ {/for}
269
+ ```
270
+
271
+ مع `k-memo`، اختيار صف آخر يعيد رسم صفين فقط لا كل الصفوف.
272
+
273
+ ### توجيهاتك الخاصة
274
+
275
+ ```js
276
+ const app = Kreftjs.app('App.kreft')
277
+
278
+ app.directive('focus', {
279
+ ready(el, binding) { el.focus() },
280
+ update(el, binding) {},
281
+ destroy(el, binding) {},
282
+ })
283
+
284
+ app.mount('#app')
285
+ ```
286
+
287
+ ```html
288
+ <input k-focus>
289
+ <div k-tooltip:top={message}></div> <!-- binding.arg = 'top', binding.value = message -->
290
+ ```
291
+
292
+ ## استخدام Kreft من JavaScript
293
+
294
+ يمكنك كتابة المكوّنات ككائنات عادية. القواعد نفسها: القيم حالة، والـ getters قيم مشتقة، والدوال أفعال.
295
+
296
+ ```js
297
+ Kreftjs.app({
298
+ count: 0,
299
+ get double() { return this.count * 2 },
300
+ add() { this.count++ },
301
+ template: `<button @click={add}>{count} × 2 = {double}</button>`,
302
+ }).mount('#app')
303
+ ```
304
+
305
+ | JavaScript | يقابل في `.kreft` |
306
+ |---|---|
307
+ | `Kreftjs.state({ … })` | `state` + `derive` + `action` معًا، للشيفرة خارج المكوّنات. |
308
+ | `Kreftjs.signal(0)` | قيمة واحدة تُقرأ وتُكتب بـ `.value`. |
309
+ | `Kreftjs.derived(() => …)` | `derive` |
310
+ | `Kreftjs.watch(source, (value, old) => …)` | `watch` |
311
+ | `Kreftjs.effect(() => …)` | يعمل مجددًا كلما تغيّر ما يقرؤه. |
312
+ | `Kreftjs.tick()` | ينتظر حتى تتحدث الصفحة. |
313
+
314
+ ```js
315
+ const price = Kreftjs.signal(10)
316
+ const qty = Kreftjs.signal(2)
317
+ const total = Kreftjs.derived(() => price.value * qty.value)
318
+
319
+ console.log(total.value) // 20
320
+ qty.value = 3
321
+ console.log(total.value) // 30
322
+ ```
323
+
324
+ وهناك `Kreftjs.render(view, data)` يحوّل قالبًا وبيانات إلى نص HTML، مع عمل كل التوجيهات وتهريب كل القيمة. استعمله لرسائل البريد ونسخ الطباعة.
325
+
326
+ ## تمارين
327
+
328
+ 1. اكتب عدّادًا مع زري زيادة ونقصان، وقيمة مشتقة `double`، و`watch` يطبع القيمة عند تغيّرها.
329
+ 2. اكتب قائمة مهام تحفظ نفسها عند إعادة التحميل (تلميح: `saved` بدل `state`).
330
+ 3. اكتب مكوّن بحث يرشّح قائمة أسماء وهو تكتب، باستعمال `derive` و`k-model`.
@@ -0,0 +1,258 @@
1
+ # الفصل الثالث: المكوّنات ومشاركة الحالة
2
+
3
+ ## استخدام مكوّن
4
+
5
+ سجّله مرة واحدة بمساره، أو استورده حيث تحتاجه:
6
+
7
+ ```js
8
+ Kreftjs.app('App.kreft')
9
+ .component('UserCard', 'components/UserCard.kreft')
10
+ .mount('#app')
11
+ ```
12
+
13
+ ```html
14
+ import UserCard from './UserCard.kreft'
15
+
16
+ <view>
17
+ <user-card name="سارة أحمد" age={30}></user-card>
18
+ </view>
19
+ ```
20
+
21
+ في الصفحة (`view`) اكتب أسماء المكوّنات بنمط **kebab-case** (`<user-card>`).
22
+
23
+ ## Props: من الأب إلى الابن
24
+
25
+ ```html
26
+ <!-- UserCard.kreft -->
27
+ prop name
28
+ prop age = 0
29
+ prop admin = false
30
+
31
+ <view>
32
+ <div class="card">{name} ({age}) <b k-if="admin">مدير</b></div>
33
+ </view>
34
+ ```
35
+
36
+ ```html
37
+ <user-card name="سارة أحمد" age={30} admin></user-card>
38
+ ```
39
+
40
+ نوع القيمة الافتراضية يحدد كيف تُقرأ الخصائص النصية: `age="30"` تصير الرقم `30`، و`admin` بلا قيمة تصير `true`. الابن لا يستطيع تغيير prop؛ أرسل حدثًا بدلًا من ذلك.
41
+
42
+ ## الأحداث: من الابن إلى الأب
43
+
44
+ ```html
45
+ <!-- TodoItem.kreft -->
46
+ prop todo
47
+ event remove
48
+
49
+ <view>
50
+ <li>{todo.text} <button @click="send('remove', todo.id)">✕</button></li>
51
+ </view>
52
+ ```
53
+
54
+ ```html
55
+ <!-- الأب -->
56
+ action removeTodo(id) { todos = todos.filter((t) => t.id !== id) }
57
+
58
+ <view>
59
+ <todo-item k-each="t in todos" k-key="t.id" todo={t} @remove={removeTodo}></todo-item>
60
+ </view>
61
+ ```
62
+
63
+ ## k-model على مكوّن
64
+
65
+ ```html
66
+ <!-- Toggle.kreft -->
67
+ prop modelValue = false
68
+ event update:modelValue
69
+
70
+ <view>
71
+ <button @click="send('update:modelValue', !modelValue)">{modelValue ? 'مفعّل' : 'معطّل'}</button>
72
+ </view>
73
+ ```
74
+
75
+ ```html
76
+ <toggle k-model="darkMode"></toggle>
77
+ ```
78
+
79
+ النماذج المسمّاة: `k-model:title="pageTitle"` يستعمل `prop title` والحدث `update:title`.
80
+
81
+ ## الفتحات (Slots)
82
+
83
+ ```html
84
+ <!-- Card.kreft -->
85
+ prop items = []
86
+
87
+ <view>
88
+ <div class="card">
89
+ <header><slot name="header">عنوان افتراضي</slot></header>
90
+ <slot></slot>
91
+ <li k-each="item in items"><slot name="item" item={item}>{item}</slot></li>
92
+ </div>
93
+ </view>
94
+ ```
95
+
96
+ ```html
97
+ <card items={users}>
98
+ <template k-slot:header>المستخدمون</template>
99
+ <p>محتوى الجسم</p>
100
+ <template k-slot:item="{ item }"><b>{item.name}</b></template>
101
+ </card>
102
+ ```
103
+
104
+ ## الأنماط (Styles)
105
+
106
+ - `<style>` يسري على الصفحة كلها.
107
+ - `<style scoped>` يسري على هذا المكوّن فقط.
108
+ - `.parent :deep(.child)` يصل إلى المكوّنات الأبناء من CSS محصور.
109
+ - الخصائص مثل `class` التي تضعها على وسم المكوّن تنتقل إلى عنصره الجذر.
110
+
111
+ ## الاحتفاظ والانتظار (Keep و Wait)
112
+
113
+ **Keep** يضع المكوّن جانبًا بدل أن يزيله. النص المكتوب وموضع التمرير والحالة كلها تبقى حين يعود:
114
+
115
+ ```html
116
+ <kreft-component :is="tab" keep></kreft-component>
117
+ <kreft-view keep></kreft-view>
118
+ <kreft-view keep="5"></kreft-view> <!-- احتفظ بخمسة على الأكثر؛ الأقدم استعمالًا يذهب -->
119
+ ```
120
+
121
+ ```html
122
+ on show { timer.start() } <!-- عاد (وكذلك أول مرة) -->
123
+ on hide { timer.stop() } <!-- وُضع جانبًا -->
124
+ ```
125
+
126
+ **Wait** يعرض بديلًا إلى أن يجهز كل ما بداخله (المكوّنات الكسولة، وكل `load`، وكل `on start` ينتظر):
127
+
128
+ ```html
129
+ <kreft-wait>
130
+ <user-profile id={5}></user-profile>
131
+ <p k-waiting>جارٍ التحميل…</p>
132
+ <p k-failed="error">تعذر التحميل: {error.message}</p>
133
+ </kreft-wait>
134
+ ```
135
+
136
+ ## مشاركة الحالة
137
+
138
+ | الحاجة | الأداة |
139
+ |---|---|
140
+ | من الأب إلى الابن | `prop` |
141
+ | من الابن إلى الأب | `event` + `send()` أو `k-model` |
142
+ | مكوّنات كثيرة في أي مكان | المخازن: `Kreftjs.store()` + `use` |
143
+ | أب إلى كل ما بداخله | `share` / `receive` |
144
+ | رسائل "حدث شيء ما" | `bus.send()` / `bus.on()` |
145
+
146
+ ### المخازن (Stores)
147
+
148
+ أنشئ المخزن مرة واحدة:
149
+
150
+ ```js
151
+ Kreftjs.store('cart', {
152
+ items: [],
153
+ get count() { return this.items.length },
154
+ get total() { return this.items.reduce((s, p) => s + p.price, 0) },
155
+ add(product) { this.items.push(product) },
156
+ })
157
+ ```
158
+
159
+ استعمله في أي ملف `.kreft`. كل المكوّنات ترى البيانات نفسها:
160
+
161
+ ```html
162
+ use cart
163
+
164
+ <view>
165
+ <span>السلة: {cart.count} · {cart.total}</span>
166
+ <button @click="cart.add(product)">أضف</button>
167
+ </view>
168
+ ```
169
+
170
+ لكل مخزن أيضًا `$patch` (يغيّر عدة قيم دفعة واحدة) و`$reset()` (يعود للبداية) و`$watch(fn)` (يعمل عند كل تغيير ويتوقف وحده حين يزول المكوّن).
171
+
172
+ ### المنطق المشترك
173
+
174
+ المنطق الذي تحتاجه مكوّنات كثيرة يوضع في ملف `.js` كدالة. يمكنه استعمال `Kreftjs.state` و`Kreftjs.watch` و`Kreftjs.onDestroy` وغيرها:
175
+
176
+ ```js
177
+ // logic/mouse.js
178
+ export function trackMouse() {
179
+ const pos = Kreftjs.state({ x: 0, y: 0 })
180
+ const move = (e) => { pos.x = e.clientX; pos.y = e.clientY }
181
+ window.addEventListener('pointermove', move)
182
+ Kreftjs.onDestroy(() => window.removeEventListener('pointermove', move))
183
+ return pos
184
+ }
185
+ ```
186
+
187
+ ```html
188
+ import { trackMouse } from '../logic/mouse.js'
189
+ use mouse = trackMouse()
190
+
191
+ <view><p>{mouse.x}, {mouse.y}</p></view>
192
+ ```
193
+
194
+ ### share و receive
195
+
196
+ ```html
197
+ <!-- Layout.kreft -->
198
+ state settings = { theme: 'dark', lang: 'ar' }
199
+ share settings = settings
200
+
201
+ <!-- أي مكوّن بداخله مهما كان عمقه -->
202
+ receive settings
203
+
204
+ <view><p>السمة: {settings.theme}</p></view>
205
+ ```
206
+
207
+ شارك كائن `state` فيتحدث كل المستقبِلين حين يتغير. و`Kreftjs.app(…).share('key', value)` يشارك مع التطبيق كله.
208
+
209
+ ### ناقل الأحداث (Event bus)
210
+
211
+ ```html
212
+ <!-- الإرسال -->
213
+ action save() { bus.send('toast', 'تم الحفظ!') }
214
+
215
+ <!-- الاستماع -->
216
+ state message = ''
217
+ on ready { bus.on('toast', (text) => (message = text)) }
218
+ ```
219
+
220
+ المستمعون المضافون في `on start` أو `on ready` يُزالون تلقائيًا حين يزول المكوّن. والآخرون يبقون حتى تستدعي `bus.off('toast', fn)`.
221
+
222
+ ## الحفظ في المتصفح: saved
223
+
224
+ اكتب `saved` بدل `state` فتُحفظ القيمة في localStorage، وتنجو من إعادة التحميل وإغلاق المتصفح.
225
+
226
+ ```html
227
+ saved todos = []
228
+ saved theme = 'light'
229
+ saved token = '' as 'auth.token' <!-- اختر مفتاح التخزين بنفسك -->
230
+ ```
231
+
232
+ - كل تغيير يُحفظ وحده، حتى العميق منه: `todos.push(…)` و`todo.done = true`.
233
+ - المفتاح الافتراضي `<Component>.<name>`، مثل `Home.todos`. استعمل `as '…'` لتشارك قيمة واحدة بين مكوّنات.
234
+ - تغيير في تبويب آخر من الموقع نفسه يظهر في هذا التبويب أيضًا.
235
+
236
+ المخزن يحتفظ بقيمه البسيطة إذا أضفت `saved`:
237
+
238
+ ```js
239
+ Kreftjs.store('cart', { items: [] }, { saved: true }) // أو { saved: ['items'] } لبعض الحقول
240
+ ```
241
+
242
+ ومن JavaScript:
243
+
244
+ ```js
245
+ const theme = Kreftjs.saved('theme', 'light') // signal: theme.value = 'dark' تُحفظ
246
+ Kreftjs.storage.set('user', { name: 'سارة' })
247
+ Kreftjs.storage.get('user')
248
+ Kreftjs.storage.remove('user')
249
+ Kreftjs.storage.clear() // يزيل ما حفظه Kreft فقط
250
+ ```
251
+
252
+ > **تحذير:** localStorage يخص متصفحًا واحدًا وموقعًا واحدًا، وسعته نحو 5 ميغابايت، ويقرؤه أي شخص يستعمل ذلك المتصفح وأي سكربت يعمل في صفحتك. لا تخزّن فيه كلمات مرور أو أسرارًا طويلة الأمد.
253
+
254
+ ## تمارين
255
+
256
+ 1. اصنع مكوّن `Rating` له `prop value` وحدث `update:value`، واستعمله بـ `k-model`.
257
+ 2. اصنع مخزن `toast` وعرض رسائل من أي مكوّن، بدل الـ bus.
258
+ 3. اصنع مكوّن `Modal` بفتحات `header` و`footer`، يظهر بـ `kreft-portal`.