kreftjs 0.0.19-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 CHANGED
@@ -65,6 +65,7 @@ The app icon is `build/icon.png`, and `ELECTRON_MENU=show|auto|hide` controls th
65
65
 
66
66
  | I want to… | Read |
67
67
  |---|---|
68
+ | Learn Kreft in Arabic, from basics to advanced tested examples | [كتاب Kreft بالعربية](book/ar/README.md) |
68
69
  | Build my first page | [Your first app](docs/README.md#your-first-app) and [Components](docs/README.md#components-and-the-kreft-file) |
69
70
  | Fetch data and handle forms | [Fetching data](docs/README.md#fetching-data), [Forms](docs/README.md#forms-and-validation) |
70
71
  | Add an API and a database | [Routing](docs/README.md#routing), [Models](docs/README.md#models-and-the-database), [Migrations](docs/README.md#migrations-seeders-and-factories) |
package/bin/build.js CHANGED
@@ -34,12 +34,13 @@ const SERVER_DIRS = new Set();
34
34
  const SKIP = new Set(['node_modules', 'dist', 'dist-electron', '.git', '.vscode', '.idea', 'package.json', 'package-lock.json', '.DS_Store']);
35
35
 
36
36
  module.exports = function build(args) {
37
- let folder = null, out = null, base = null, prerender = null, urlsFile = null, htaccess = true;
37
+ let folder = null, out = null, base = null, prerender = null, urlsFile = null, htaccess = true, minify = true;
38
38
  const include = [];
39
39
  for (let i = 0; i < args.length; i++) {
40
40
  if (args[i] === '--out' || args[i] === '-o') out = args[++i];
41
41
  else if (args[i] === '--base') base = args[++i];
42
42
  else if (args[i] === '--no-htaccess') htaccess = false;
43
+ else if (args[i] === '--no-minify') minify = false; // keep kreftjs.js readable (for debugging)
43
44
  else if (args[i] === '--include') include.push(args[++i]); // another folder to copy in, like the docs
44
45
  else if (args[i] === '--prerender') prerender = args[i + 1] && !args[i + 1].startsWith('-') && args[i + 1].includes('/') ? args[++i].split(',') : [];
45
46
  else if (args[i] === '--urls') urlsFile = args[++i]; // a file with more addresses to prerender (pages from your data)
@@ -154,7 +155,17 @@ ${indent(components.join('\n\n'))}
154
155
  }
155
156
  }
156
157
  }
157
- fs.copyFileSync(path.join(__dirname, '..', 'kreftjs.js'), path.join(dist, 'kreftjs.js'));
158
+ // the framework file is minified when esbuild is installed (about 40% smaller); without it, it is copied as it is
159
+ const runtimeSrc = path.join(__dirname, '..', 'kreftjs.js'), runtimeOut = path.join(dist, 'kreftjs.js');
160
+ const esb = minify ? deps.loadEsbuild(root) : null;
161
+ let minified = false;
162
+ if (esb) {
163
+ try {
164
+ fs.writeFileSync(runtimeOut, esb.transformSync(fs.readFileSync(runtimeSrc, 'utf8'), { minify: true, target: 'es2020', legalComments: 'none' }).code);
165
+ minified = true;
166
+ } catch (_) { /* fall back to the plain copy */ }
167
+ }
168
+ if (!minified) fs.copyFileSync(runtimeSrc, runtimeOut);
158
169
  for (const dir of include) {
159
170
  const from = path.resolve(dir);
160
171
  if (!fs.existsSync(from)) fail(`--include: ${dir} does not exist.`);
package/bin/kreftjs.js CHANGED
@@ -165,6 +165,7 @@ function help() {
165
165
  --prerender [paths] Also save every page as ready HTML (needs: npm i -D jsdom)
166
166
  --include <folder> Also copy this folder into the output (for example docs)
167
167
  --no-htaccess Do not write dist/.htaccess (page-address rules for Apache and Hostinger)
168
+ --no-minify Copy kreftjs.js as it is (by default it is minified when esbuild is installed)
168
169
  kreft add <packages...> Install npm packages with strict security gatekeeper (alias: kreft install, kreft i)
169
170
  -D, --dev Install as devDependency
170
171
  -y, --yes Auto-confirm unverified community packages in CI/CD
@@ -0,0 +1,169 @@
1
+ # الفصل الأول: البداية
2
+
3
+ ## ما هو Kreft؟
4
+
5
+ Kreft (يُكتب `kreftjs`) إطار عمل صغير، تفاعلي (reactive)، له لغة مكوّنات خاصة به: ملفات `.kreft`. تكتب في الملف الواحد حالتك وقيمك المشتقة وأفعالك بكلمات بسيطة، ثم تصف الصفحة في `<view>`. ويأتي مع واجهة خلفية كاملة على Node.js (مسارات، ومتحكمات، ونماذج، وترحيلات، وطوابير، وبريد)، فيغطي المشروع الواحد التطبيق بأكمله.
6
+
7
+ > **ملاحظة:** الإصدار الحالي تجريبي (`0.0.x-beta`). كل ما في هذا الكتاب جُرِّب على الإصدار الذي صدر معه، لكن واجهات الإطار قد تتغير بين الإصدارات التجريبية. راجع `kreft --version` وملف `docs/README.md` (بالإنجليزية) عند الشك.
8
+
9
+ لماذا قد تختاره؟
10
+
11
+ - **لغة واحدة للمكوّن:** HTML ومنطق وتنسيق في ملف واحد، بلا ديكورات ولا وحدات (modules) تتعلمها.
12
+ - **بدون إعداد:** `kreft new` ثم `kreft dev`. لا ملف إعداد لأداة تجميع.
13
+ - **واجهة خلفية جاهزة:** تسجيل دخول، وتحقق من البريد، واستعادة كلمة المرور، ولوحة إدارة، ومصادقة ثنائية.
14
+ - **تطبيقات سطح المكتب:** `kreft new --electron` ثم تبني مثبّتات لـ Windows وmacOS وLinux.
15
+ - **دعم RTL من الأساس:** تبديل اللغة واتجاه الصفحة من دالة واحدة (مهم لنا نحن العرب، وله فصل كامل).
16
+
17
+ ## التثبيت
18
+
19
+ المتطلبات: Node.js (يُفضَّل 22 أو أحدث) وnpm.
20
+
21
+ ```bash
22
+ npm install -g kreftjs
23
+ kreft --version # مثال: 0.0.19-beta
24
+ ```
25
+
26
+ للتحقق من أن كل شيء سليم:
27
+
28
+ ```bash
29
+ kreft about
30
+ ```
31
+
32
+ وللحصول على تلوين الملفات وملفات المقتطفات (snippets) في VS Code أو Cursor:
33
+
34
+ ```bash
35
+ kreft vscode
36
+ ```
37
+
38
+ ## أول تطبيق
39
+
40
+ ```bash
41
+ kreft new my-app # يسألك: JavaScript أم TypeScript؟
42
+ cd my-app
43
+ npm run dev
44
+ ```
45
+
46
+ افتح العنوان الذي يطبعه الطرفية (عادة `http://localhost:8000`؛ وإن كان المنفذ مشغولًا يُستخدم التالي).
47
+
48
+ خيارات مفيدة عند الإنشاء:
49
+
50
+ ```bash
51
+ kreft new shop --db mysql --ts # TypeScript وقاعدة MySQL
52
+ kreft new site --ui tailwind --no-auth # Tailwind بدون صفحات تسجيل الدخول
53
+ kreft new desk --electron # مع دعم تطبيقات سطح المكتب
54
+ kreft new app-name --ui bootstrap --no-auth
55
+ ```
56
+
57
+ > **تنبيه:** اكتب قيم الخيارات كما هي بالضبط: `bootstrap` و`tailwind` و`bulma` و`pico`. أي قيمة أخرى توقف الأمر مع رسالة تعرض القيم المسموحة.
58
+
59
+ يأتي المشروع الجديد (إن لم تستعمل `--no-auth`) بصفحات الدخول والتسجيل واستعادة كلمة المرور وتأكيد البريد والملف الشخصي ولوحة الإدارة. وعند تشغيل `npm run dev` تُنشأ الجداول ومستخدم تجريبي:
60
+
61
+ - البريد: `test@example.com`
62
+ - كلمة المرور: `password`
63
+
64
+ > **تحذير:** هذا المستخدم للتطوير المحلي فقط. احذفه أو لا تشغّل `--seed` أبدًا على قاعدة بيانات حقيقية.
65
+
66
+ رسائل البريد (تأكيد، استعادة) تُطبع في الطرفية إلى أن تضبط `MAIL_URL`.
67
+
68
+ ## أصغر تطبيق ممكن
69
+
70
+ يمكنك استعمال Kreft بلا مشروع كامل. التطبيق صفحة HTML وملفات `.kreft`:
71
+
72
+ ```html
73
+ <!-- index.html -->
74
+ <div id="app"></div>
75
+ <script src="kreftjs.js"></script>
76
+ <script>
77
+ Kreftjs.app('App.kreft').mount('#app')
78
+ </script>
79
+ ```
80
+
81
+ ```html
82
+ <!-- App.kreft -->
83
+ state name = 'العالم'
84
+
85
+ <view>
86
+ <h1>مرحبًا يا {name}</h1>
87
+ <input k-model="name">
88
+ </view>
89
+ ```
90
+
91
+ > **تحذير:** ملفات `.kreft` تحتاج خادمًا. المتصفحات ترفض تحميل الملفات من `file://`، فافتح التطبيق دائمًا عبر `npm run dev` أو أي خادم.
92
+
93
+ ## بنية المشروع
94
+
95
+ ينشئ `kreft new` مجلدين لا يختلطان أبدًا: `frontend/` هو ما يصل المتصفح، و`backend/` هو الخادم ولا يمكن الوصول إليه عبر HTTP.
96
+
97
+ ```text
98
+ my-app/
99
+ ├── frontend/
100
+ │ ├── index.html نقطة الدخول
101
+ │ ├── App.kreft المكوّن الجذر وحاوية الموجِّه
102
+ │ ├── routes/web.kreft مسارات الصفحات
103
+ │ ├── layouts/ app.kreft (بعد الدخول)، guest.kreft (صفحات الدخول)
104
+ │ ├── pages/ ملف .kreft لكل شاشة
105
+ │ ├── components/ مكوّنات قابلة لإعادة الاستخدام
106
+ │ ├── ui.css نظام التصميم
107
+ │ └── tests/ اختبارات المكوّنات
108
+ ├── backend/
109
+ │ ├── config/ app.js, auth.js, database.js, redis.js, session.js
110
+ │ ├── app/
111
+ │ │ ├── Http/ Controllers, Middleware, Requests, Resources
112
+ │ │ ├── Models/ User, PersonalAccessToken
113
+ │ │ └── Auth/ مساعدات الجلسات
114
+ │ ├── database/ migrations, seeders, factories
115
+ │ └── routes/ api.js, auth.js, console.js
116
+ ├── .env الأسرار والإعدادات (لا ترفعه إلى Git أبدًا)
117
+ ├── package.json
118
+ └── kreftjs.config.json خيارات سطر الأوامر
119
+ ```
120
+
121
+ لماذا الفصل مهم؟
122
+
123
+ - `kreft dev` و`kreft build` يقدّمان `frontend/` فقط. أما `backend/` وملفات قاعدة البيانات و`.env` فلا عنوان ويب لها.
124
+ - على macOS وLinux يمنح `kreft new` المجلد `backend/` والملف `.env` صلاحيات يقرؤها المالك فقط.
125
+ - ملفات مثل `.env` و`*.db` و`*.pem` و`package.json` مرفوضة حتى لو طُلبت بالاسم.
126
+
127
+ ## إضافة مكتبة CSS
128
+
129
+ أي مكتبة تعمل. أضف `<link>` في `index.html`، أو ثبّتها واستورد ملفها من ملف `.kreft`:
130
+
131
+ ```html
132
+ import 'bootstrap/dist/css/bootstrap.min.css' // npm i bootstrap
133
+ import './theme.css' // ملفك الخاص بجانب هذا الملف
134
+ ```
135
+
136
+ ## حزم npm في المتصفح
137
+
138
+ ```bash
139
+ kreft add dayjs nanoid lodash
140
+ kreft add -D esbuild # مرة واحدة لكل مشروع: يجهّز الحزم للمتصفح
141
+ ```
142
+
143
+ ```html
144
+ import dayjs from 'dayjs'
145
+ import { nanoid } from 'nanoid'
146
+
147
+ state id = nanoid()
148
+
149
+ <view>
150
+ <p>{dayjs().format('YYYY-MM-DD')}</p>
151
+ </view>
152
+ ```
153
+
154
+ الأمر `kreft add` نسخة أكثر أمانًا من `npm install`: يمنع سكربتات التثبيت افتراضيًا، ويتحقق من أخطاء الكتابة المتعمدة في أسماء الحزم (typosquatting)، وينبّه إلى الحزم التي نُشرت قبل أقل من 14 يومًا، ثم يشغّل `npm audit`.
155
+
156
+ ## أوامر ستستخدمها كل يوم
157
+
158
+ | الأمر | وظيفته |
159
+ |---|---|
160
+ | `kreft new my-app` | ينشئ مشروعًا |
161
+ | `kreft dev` | خادم التطوير مع إعادة تحميل فورية |
162
+ | `kreft build` | يصرّف التطبيق إلى ملفات عادية في `dist/` |
163
+ | `kreft serve dist` | يشغّل التطبيق المبني للمستخدمين الحقيقيين |
164
+ | `kreft list` | يعرض كل أوامر نسختك |
165
+ | `kreft help` | خيارات الأوامر الرئيسية |
166
+ | `kreft tinker` | طرفية تفاعلية مع نماذجك (`await User.count()`) |
167
+ | `kreft update` | يحدّث الحزم وملف `kreftjs.js` |
168
+
169
+ تدريب سريع: أنشئ مشروعًا، وغيّر كلمة في `frontend/pages/` وراقب الصفحة تتبدل في المتصفح دون إعادة تحميل وبدون ضياع الحالة. هذه هي إعادة التحميل الفورية (hot reload).
@@ -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`.