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.
- package/README.md +20 -0
- package/bin/build.js +13 -2
- package/bin/electron.js +61 -2
- package/bin/kreftjs.js +5 -3
- package/book/ar/01-getting-started.md +169 -0
- package/book/ar/02-language.md +330 -0
- package/book/ar/03-components-and-state.md +258 -0
- package/book/ar/04-router-data-forms.md +333 -0
- package/book/ar/05-arabic-rtl.md +175 -0
- package/book/ar/06-backend.md +247 -0
- package/book/ar/07-auth-and-security.md +157 -0
- package/book/ar/08-background-work.md +250 -0
- package/book/ar/09-testing-and-deploy.md +234 -0
- package/book/ar/10-electron-desktop.md +258 -0
- package/book/ar/11-advanced-blog.md +370 -0
- package/book/ar/12-advanced-shop.md +426 -0
- package/book/ar/13-advanced-ui.md +440 -0
- package/book/ar/14-advanced-ops.md +167 -0
- package/book/ar/15-appendix.md +81 -0
- package/book/ar/README.md +62 -0
- package/book/kreft-book-ar.docx +0 -0
- package/docs/README.md +43 -8
- package/kreftjs.d.ts +1 -1
- package/kreftjs.js +436 -74
- package/package.json +4 -3
- package/templates/app/frontend/errors/ErrorMonitor.kreft +2 -2
- package/templates/app/frontend/kreftjs.d.ts +1 -1
- package/templates/app/frontend/kreftjs.js +436 -74
- package/templates/app/frontend/pages/Dashboard.kreft +1 -1
- package/templates/errors/ErrorMonitor.kreft +1 -1
|
@@ -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`.
|