@summa-ui/surah-picker 0.1.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/dist/index.cjs ADDED
@@ -0,0 +1,1116 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ BookIcon: () => BookIcon,
24
+ ChevronDownIcon: () => ChevronDownIcon,
25
+ Popup: () => Popup,
26
+ SURAH_LIST: () => SURAH_LIST,
27
+ SurahFieldPicker: () => SurahFieldPicker,
28
+ SurahRangePicker: () => SurahRangePicker,
29
+ SurahWheelPicker: () => SurahWheelPicker,
30
+ defaultLabels: () => defaultLabels,
31
+ getAyatCount: () => getAyatCount,
32
+ getSurah: () => getSurah,
33
+ resolveLabels: () => resolveLabels
34
+ });
35
+ module.exports = __toCommonJS(index_exports);
36
+
37
+ // src/core/SurahWheelPicker.tsx
38
+ var import_react = require("react");
39
+
40
+ // src/data/quran.ts
41
+ var SURAH_LIST = [
42
+ { number: 1, name: "Al-Fatihah", arabic: "\u0627\u0644\u0641\u0627\u062A\u062D\u0629", ayatCount: 7 },
43
+ { number: 2, name: "Al-Baqarah", arabic: "\u0627\u0644\u0628\u0642\u0631\u0629", ayatCount: 286 },
44
+ { number: 3, name: "Ali Imran", arabic: "\u0622\u0644 \u0639\u0645\u0631\u0627\u0646", ayatCount: 200 },
45
+ { number: 4, name: "An-Nisa", arabic: "\u0627\u0644\u0646\u0633\u0627\u0621", ayatCount: 176 },
46
+ { number: 5, name: "Al-Maidah", arabic: "\u0627\u0644\u0645\u0627\u0626\u062F\u0629", ayatCount: 120 },
47
+ { number: 6, name: "Al-An'am", arabic: "\u0627\u0644\u0623\u0646\u0639\u0627\u0645", ayatCount: 165 },
48
+ { number: 7, name: "Al-A'raf", arabic: "\u0627\u0644\u0623\u0639\u0631\u0627\u0641", ayatCount: 206 },
49
+ { number: 8, name: "Al-Anfal", arabic: "\u0627\u0644\u0623\u0646\u0641\u0627\u0644", ayatCount: 75 },
50
+ { number: 9, name: "At-Taubah", arabic: "\u0627\u0644\u062A\u0648\u0628\u0629", ayatCount: 129 },
51
+ { number: 10, name: "Yunus", arabic: "\u064A\u0648\u0646\u0633", ayatCount: 109 },
52
+ { number: 11, name: "Hud", arabic: "\u0647\u0648\u062F", ayatCount: 123 },
53
+ { number: 12, name: "Yusuf", arabic: "\u064A\u0648\u0633\u0641", ayatCount: 111 },
54
+ { number: 13, name: "Ar-Ra'd", arabic: "\u0627\u0644\u0631\u0639\u062F", ayatCount: 43 },
55
+ { number: 14, name: "Ibrahim", arabic: "\u0625\u0628\u0631\u0627\u0647\u064A\u0645", ayatCount: 52 },
56
+ { number: 15, name: "Al-Hijr", arabic: "\u0627\u0644\u062D\u062C\u0631", ayatCount: 99 },
57
+ { number: 16, name: "An-Nahl", arabic: "\u0627\u0644\u0646\u062D\u0644", ayatCount: 128 },
58
+ { number: 17, name: "Al-Isra", arabic: "\u0627\u0644\u0625\u0633\u0631\u0627\u0621", ayatCount: 111 },
59
+ { number: 18, name: "Al-Kahf", arabic: "\u0627\u0644\u0643\u0647\u0641", ayatCount: 110 },
60
+ { number: 19, name: "Maryam", arabic: "\u0645\u0631\u064A\u0645", ayatCount: 98 },
61
+ { number: 20, name: "Ta-Ha", arabic: "\u0637\u0647", ayatCount: 135 },
62
+ { number: 21, name: "Al-Anbiya", arabic: "\u0627\u0644\u0623\u0646\u0628\u064A\u0627\u0621", ayatCount: 112 },
63
+ { number: 22, name: "Al-Hajj", arabic: "\u0627\u0644\u062D\u062C", ayatCount: 78 },
64
+ { number: 23, name: "Al-Mu'minun", arabic: "\u0627\u0644\u0645\u0624\u0645\u0646\u0648\u0646", ayatCount: 118 },
65
+ { number: 24, name: "An-Nur", arabic: "\u0627\u0644\u0646\u0648\u0631", ayatCount: 64 },
66
+ { number: 25, name: "Al-Furqan", arabic: "\u0627\u0644\u0641\u0631\u0642\u0627\u0646", ayatCount: 77 },
67
+ { number: 26, name: "Ash-Shu'ara", arabic: "\u0627\u0644\u0634\u0639\u0631\u0627\u0621", ayatCount: 227 },
68
+ { number: 27, name: "An-Naml", arabic: "\u0627\u0644\u0646\u0645\u0644", ayatCount: 93 },
69
+ { number: 28, name: "Al-Qasas", arabic: "\u0627\u0644\u0642\u0635\u0635", ayatCount: 88 },
70
+ { number: 29, name: "Al-Ankabut", arabic: "\u0627\u0644\u0639\u0646\u0643\u0628\u0648\u062A", ayatCount: 69 },
71
+ { number: 30, name: "Ar-Rum", arabic: "\u0627\u0644\u0631\u0648\u0645", ayatCount: 60 },
72
+ { number: 31, name: "Luqman", arabic: "\u0644\u0642\u0645\u0627\u0646", ayatCount: 34 },
73
+ { number: 32, name: "As-Sajdah", arabic: "\u0627\u0644\u0633\u062C\u062F\u0629", ayatCount: 30 },
74
+ { number: 33, name: "Al-Ahzab", arabic: "\u0627\u0644\u0623\u062D\u0632\u0627\u0628", ayatCount: 73 },
75
+ { number: 34, name: "Saba", arabic: "\u0633\u0628\u0623", ayatCount: 54 },
76
+ { number: 35, name: "Fatir", arabic: "\u0641\u0627\u0637\u0631", ayatCount: 45 },
77
+ { number: 36, name: "Ya Sin", arabic: "\u064A\u0633", ayatCount: 83 },
78
+ { number: 37, name: "As-Saffat", arabic: "\u0627\u0644\u0635\u0627\u0641\u0627\u062A", ayatCount: 182 },
79
+ { number: 38, name: "Sad", arabic: "\u0635", ayatCount: 88 },
80
+ { number: 39, name: "Az-Zumar", arabic: "\u0627\u0644\u0632\u0645\u0631", ayatCount: 75 },
81
+ { number: 40, name: "Ghafir", arabic: "\u063A\u0627\u0641\u0631", ayatCount: 85 },
82
+ { number: 41, name: "Fussilat", arabic: "\u0641\u0635\u0644\u062A", ayatCount: 54 },
83
+ { number: 42, name: "Ash-Shura", arabic: "\u0627\u0644\u0634\u0648\u0631\u0649", ayatCount: 53 },
84
+ { number: 43, name: "Az-Zukhruf", arabic: "\u0627\u0644\u0632\u062E\u0631\u0641", ayatCount: 89 },
85
+ { number: 44, name: "Ad-Dukhan", arabic: "\u0627\u0644\u062F\u062E\u0627\u0646", ayatCount: 59 },
86
+ { number: 45, name: "Al-Jathiyah", arabic: "\u0627\u0644\u062C\u0627\u062B\u064A\u0629", ayatCount: 37 },
87
+ { number: 46, name: "Al-Ahqaf", arabic: "\u0627\u0644\u0623\u062D\u0642\u0627\u0641", ayatCount: 35 },
88
+ { number: 47, name: "Muhammad", arabic: "\u0645\u062D\u0645\u062F", ayatCount: 38 },
89
+ { number: 48, name: "Al-Fath", arabic: "\u0627\u0644\u0641\u062A\u062D", ayatCount: 29 },
90
+ { number: 49, name: "Al-Hujurat", arabic: "\u0627\u0644\u062D\u062C\u0631\u0627\u062A", ayatCount: 18 },
91
+ { number: 50, name: "Qaf", arabic: "\u0642", ayatCount: 45 },
92
+ { number: 51, name: "Adh-Dhariyat", arabic: "\u0627\u0644\u0630\u0627\u0631\u064A\u0627\u062A", ayatCount: 60 },
93
+ { number: 52, name: "At-Tur", arabic: "\u0627\u0644\u0637\u0648\u0631", ayatCount: 49 },
94
+ { number: 53, name: "An-Najm", arabic: "\u0627\u0644\u0646\u062C\u0645", ayatCount: 62 },
95
+ { number: 54, name: "Al-Qamar", arabic: "\u0627\u0644\u0642\u0645\u0631", ayatCount: 55 },
96
+ { number: 55, name: "Ar-Rahman", arabic: "\u0627\u0644\u0631\u062D\u0645\u0646", ayatCount: 78 },
97
+ { number: 56, name: "Al-Waqi'ah", arabic: "\u0627\u0644\u0648\u0627\u0642\u0639\u0629", ayatCount: 96 },
98
+ { number: 57, name: "Al-Hadid", arabic: "\u0627\u0644\u062D\u062F\u064A\u062F", ayatCount: 29 },
99
+ { number: 58, name: "Al-Mujadilah", arabic: "\u0627\u0644\u0645\u062C\u0627\u062F\u0644\u0629", ayatCount: 22 },
100
+ { number: 59, name: "Al-Hashr", arabic: "\u0627\u0644\u062D\u0634\u0631", ayatCount: 24 },
101
+ { number: 60, name: "Al-Mumtahanah", arabic: "\u0627\u0644\u0645\u0645\u062A\u062D\u0646\u0629", ayatCount: 13 },
102
+ { number: 61, name: "As-Saff", arabic: "\u0627\u0644\u0635\u0641", ayatCount: 14 },
103
+ { number: 62, name: "Al-Jumu'ah", arabic: "\u0627\u0644\u062C\u0645\u0639\u0629", ayatCount: 11 },
104
+ { number: 63, name: "Al-Munafiqun", arabic: "\u0627\u0644\u0645\u0646\u0627\u0641\u0642\u0648\u0646", ayatCount: 11 },
105
+ { number: 64, name: "At-Taghabun", arabic: "\u0627\u0644\u062A\u063A\u0627\u0628\u0646", ayatCount: 18 },
106
+ { number: 65, name: "At-Talaq", arabic: "\u0627\u0644\u0637\u0644\u0627\u0642", ayatCount: 12 },
107
+ { number: 66, name: "At-Tahrim", arabic: "\u0627\u0644\u062A\u062D\u0631\u064A\u0645", ayatCount: 12 },
108
+ { number: 67, name: "Al-Mulk", arabic: "\u0627\u0644\u0645\u0644\u0643", ayatCount: 30 },
109
+ { number: 68, name: "Al-Qalam", arabic: "\u0627\u0644\u0642\u0644\u0645", ayatCount: 52 },
110
+ { number: 69, name: "Al-Haqqah", arabic: "\u0627\u0644\u062D\u0627\u0642\u0629", ayatCount: 52 },
111
+ { number: 70, name: "Al-Ma'arij", arabic: "\u0627\u0644\u0645\u0639\u0627\u0631\u062C", ayatCount: 44 },
112
+ { number: 71, name: "Nuh", arabic: "\u0646\u0648\u062D", ayatCount: 28 },
113
+ { number: 72, name: "Al-Jinn", arabic: "\u0627\u0644\u062C\u0646", ayatCount: 28 },
114
+ { number: 73, name: "Al-Muzzammil", arabic: "\u0627\u0644\u0645\u0632\u0645\u0644", ayatCount: 20 },
115
+ { number: 74, name: "Al-Muddaththir", arabic: "\u0627\u0644\u0645\u062F\u062B\u0631", ayatCount: 56 },
116
+ { number: 75, name: "Al-Qiyamah", arabic: "\u0627\u0644\u0642\u064A\u0627\u0645\u0629", ayatCount: 40 },
117
+ { number: 76, name: "Al-Insan", arabic: "\u0627\u0644\u0625\u0646\u0633\u0627\u0646", ayatCount: 31 },
118
+ { number: 77, name: "Al-Mursalat", arabic: "\u0627\u0644\u0645\u0631\u0633\u0644\u0627\u062A", ayatCount: 50 },
119
+ { number: 78, name: "An-Naba", arabic: "\u0627\u0644\u0646\u0628\u0623", ayatCount: 40 },
120
+ { number: 79, name: "An-Nazi'at", arabic: "\u0627\u0644\u0646\u0627\u0632\u0639\u0627\u062A", ayatCount: 46 },
121
+ { number: 80, name: "Abasa", arabic: "\u0639\u0628\u0633", ayatCount: 42 },
122
+ { number: 81, name: "At-Takwir", arabic: "\u0627\u0644\u062A\u0643\u0648\u064A\u0631", ayatCount: 29 },
123
+ { number: 82, name: "Al-Infitar", arabic: "\u0627\u0644\u0627\u0646\u0641\u0637\u0627\u0631", ayatCount: 19 },
124
+ { number: 83, name: "Al-Mutaffifin", arabic: "\u0627\u0644\u0645\u0637\u0641\u0641\u064A\u0646", ayatCount: 36 },
125
+ { number: 84, name: "Al-Inshiqaq", arabic: "\u0627\u0644\u0627\u0646\u0634\u0642\u0627\u0642", ayatCount: 25 },
126
+ { number: 85, name: "Al-Buruj", arabic: "\u0627\u0644\u0628\u0631\u0648\u062C", ayatCount: 22 },
127
+ { number: 86, name: "At-Tariq", arabic: "\u0627\u0644\u0637\u0627\u0631\u0642", ayatCount: 17 },
128
+ { number: 87, name: "Al-A'la", arabic: "\u0627\u0644\u0623\u0639\u0644\u0649", ayatCount: 19 },
129
+ { number: 88, name: "Al-Ghashiyah", arabic: "\u0627\u0644\u063A\u0627\u0634\u064A\u0629", ayatCount: 26 },
130
+ { number: 89, name: "Al-Fajr", arabic: "\u0627\u0644\u0641\u062C\u0631", ayatCount: 30 },
131
+ { number: 90, name: "Al-Balad", arabic: "\u0627\u0644\u0628\u0644\u062F", ayatCount: 20 },
132
+ { number: 91, name: "Ash-Shams", arabic: "\u0627\u0644\u0634\u0645\u0633", ayatCount: 15 },
133
+ { number: 92, name: "Al-Lail", arabic: "\u0627\u0644\u0644\u064A\u0644", ayatCount: 21 },
134
+ { number: 93, name: "Ad-Dhuha", arabic: "\u0627\u0644\u0636\u062D\u0649", ayatCount: 11 },
135
+ { number: 94, name: "Ash-Sharh", arabic: "\u0627\u0644\u0634\u0631\u062D", ayatCount: 8 },
136
+ { number: 95, name: "At-Tin", arabic: "\u0627\u0644\u062A\u064A\u0646", ayatCount: 8 },
137
+ { number: 96, name: "Al-Alaq", arabic: "\u0627\u0644\u0639\u0644\u0642", ayatCount: 19 },
138
+ { number: 97, name: "Al-Qadr", arabic: "\u0627\u0644\u0642\u062F\u0631", ayatCount: 5 },
139
+ { number: 98, name: "Al-Bayyinah", arabic: "\u0627\u0644\u0628\u064A\u0646\u0629", ayatCount: 8 },
140
+ { number: 99, name: "Az-Zalzalah", arabic: "\u0627\u0644\u0632\u0644\u0632\u0644\u0629", ayatCount: 8 },
141
+ { number: 100, name: "Al-Adiyat", arabic: "\u0627\u0644\u0639\u0627\u062F\u064A\u0627\u062A", ayatCount: 11 },
142
+ { number: 101, name: "Al-Qari'ah", arabic: "\u0627\u0644\u0642\u0627\u0631\u0639\u0629", ayatCount: 11 },
143
+ { number: 102, name: "At-Takathur", arabic: "\u0627\u0644\u062A\u0643\u0627\u062B\u0631", ayatCount: 8 },
144
+ { number: 103, name: "Al-Asr", arabic: "\u0627\u0644\u0639\u0635\u0631", ayatCount: 3 },
145
+ { number: 104, name: "Al-Humazah", arabic: "\u0627\u0644\u0647\u0645\u0632\u0629", ayatCount: 9 },
146
+ { number: 105, name: "Al-Fil", arabic: "\u0627\u0644\u0641\u064A\u0644", ayatCount: 5 },
147
+ { number: 106, name: "Quraysh", arabic: "\u0642\u0631\u064A\u0634", ayatCount: 4 },
148
+ { number: 107, name: "Al-Ma'un", arabic: "\u0627\u0644\u0645\u0627\u0639\u0648\u0646", ayatCount: 7 },
149
+ { number: 108, name: "Al-Kawthar", arabic: "\u0627\u0644\u0643\u0648\u062B\u0631", ayatCount: 3 },
150
+ { number: 109, name: "Al-Kafirun", arabic: "\u0627\u0644\u0643\u0627\u0641\u0631\u0648\u0646", ayatCount: 6 },
151
+ { number: 110, name: "An-Nasr", arabic: "\u0627\u0644\u0646\u0635\u0631", ayatCount: 3 },
152
+ { number: 111, name: "Al-Masad", arabic: "\u0627\u0644\u0645\u0633\u062F", ayatCount: 5 },
153
+ { number: 112, name: "Al-Ikhlas", arabic: "\u0627\u0644\u0625\u062E\u0644\u0627\u0635", ayatCount: 4 },
154
+ { number: 113, name: "Al-Falaq", arabic: "\u0627\u0644\u0641\u0644\u0642", ayatCount: 5 },
155
+ { number: 114, name: "An-Nas", arabic: "\u0627\u0644\u0646\u0627\u0633", ayatCount: 6 }
156
+ ];
157
+ function getSurah(number) {
158
+ return SURAH_LIST.find((s) => s.number === number);
159
+ }
160
+ function getAyatCount(surahNumber) {
161
+ return getSurah(surahNumber)?.ayatCount ?? 0;
162
+ }
163
+
164
+ // src/labels.ts
165
+ var defaultLabels = {
166
+ surah: "Surah",
167
+ ayah: "Ayah",
168
+ subtitle: "Select surah and ayah",
169
+ from: "From",
170
+ to: "To",
171
+ fromSurah: "Start surah",
172
+ fromAyah: "Start ayah",
173
+ toSurah: "End surah",
174
+ toAyah: "End ayah",
175
+ select: "Select",
176
+ cancel: "Cancel"
177
+ };
178
+ function resolveLabels(partial) {
179
+ return { ...defaultLabels, ...partial };
180
+ }
181
+
182
+ // src/core/SurahWheelPicker.tsx
183
+ var import_jsx_runtime = require("react/jsx-runtime");
184
+ function pad2(n) {
185
+ return String(n).padStart(2, "0");
186
+ }
187
+ function vibrate() {
188
+ try {
189
+ if (navigator.vibrate) navigator.vibrate(10);
190
+ } catch {
191
+ }
192
+ }
193
+ var visibleCount = 8;
194
+ var itemHeight = 36;
195
+ var halfItemHeight = itemHeight * 0.5;
196
+ var itemAngle = 360 / visibleCount;
197
+ var radius = itemHeight / Math.tan(itemAngle * Math.PI / 180);
198
+ var containerHeight = Math.round(radius * 2 + itemHeight * 0.25);
199
+ var quarterCount = visibleCount >> 2;
200
+ var RESISTANCE = 0.3;
201
+ var STOP_VELOCITY = 0.5;
202
+ var WHEEL_SETTLE_MS = 90;
203
+ var MIN_FLICK_VELOCITY = 2;
204
+ var VIB_THROTTLE_MS = 30;
205
+ var WHEEL_NOTCH_PX = 100;
206
+ var WHEEL_LINE_PX = 40;
207
+ var easeOutCubic = (p) => Math.pow(p - 1, 3) + 1;
208
+ var clamp = (v, min, max) => Math.max(min, Math.min(v, max));
209
+ function SurahWheelPicker({ value, onChange, nameSurah = "surah_number", nameAyat = "ayat", physics, loop = false, chrome = true, ariaSurahLabel, ariaAyatLabel, labels: labelsProp }) {
210
+ const labels = resolveLabels(labelsProp);
211
+ const surahLabel = ariaSurahLabel ?? labels.surah;
212
+ const ayatLabel = ariaAyatLabel ?? labels.ayah;
213
+ const friction = physics?.friction ?? 5;
214
+ const maxVelocity = physics?.maxVelocity ?? 120;
215
+ const snapMs = physics?.snapMs ?? 120;
216
+ const boundIndex = (norm, len) => liveRef.current.loop ? (norm % len + len) % len : clamp(norm, 0, len - 1);
217
+ const [surah, setSurah] = (0, import_react.useState)(value?.surah ?? 3);
218
+ const [ayat, setAyat] = (0, import_react.useState)(value?.ayat ?? 3);
219
+ const surahScrollRef = (0, import_react.useRef)(0);
220
+ const ayatScrollRef = (0, import_react.useRef)(0);
221
+ const surahContainerRef = (0, import_react.useRef)(null);
222
+ const ayatContainerRef = (0, import_react.useRef)(null);
223
+ const surahWheelRef = (0, import_react.useRef)(null);
224
+ const ayatWheelRef = (0, import_react.useRef)(null);
225
+ const surahHighlightRef = (0, import_react.useRef)(null);
226
+ const ayatHighlightRef = (0, import_react.useRef)(null);
227
+ const surahMoveId = (0, import_react.useRef)(0);
228
+ const ayatMoveId = (0, import_react.useRef)(0);
229
+ const surahDragging = (0, import_react.useRef)(false);
230
+ const ayatDragging = (0, import_react.useRef)(false);
231
+ const motionRef = (0, import_react.useRef)(false);
232
+ const lastVibRef = (0, import_react.useRef)(0);
233
+ const wheelTimerSurah = (0, import_react.useRef)(0);
234
+ const wheelTimerAyat = (0, import_react.useRef)(0);
235
+ const wheelAccumSurah = (0, import_react.useRef)(0);
236
+ const wheelAccumAyat = (0, import_react.useRef)(0);
237
+ const wheelTargetSurah = (0, import_react.useRef)(null);
238
+ const wheelTargetAyat = (0, import_react.useRef)(null);
239
+ const activeSurah = SURAH_LIST.find((s) => s.number === surah) ?? SURAH_LIST[2];
240
+ const ayatCount = getAyatCount(surah);
241
+ const surahValRef = (0, import_react.useRef)(surah);
242
+ const ayatValRef = (0, import_react.useRef)(ayat);
243
+ (0, import_react.useEffect)(() => {
244
+ surahValRef.current = surah;
245
+ }, [surah]);
246
+ (0, import_react.useEffect)(() => {
247
+ ayatValRef.current = ayat;
248
+ }, [ayat]);
249
+ const surahOptions = SURAH_LIST;
250
+ const ayatOptions = Array.from({ length: ayatCount }, (_, i) => i + 1);
251
+ const liveRef = (0, import_react.useRef)({ loop, friction, maxVelocity, snapMs, ayatLen: ayatOptions.length });
252
+ liveRef.current = { loop, friction, maxVelocity, snapMs, ayatLen: ayatOptions.length };
253
+ const onChangeRef = (0, import_react.useRef)(onChange);
254
+ onChangeRef.current = onChange;
255
+ const ayatLenOf = (isSurah) => isSurah ? surahOptions.length : liveRef.current.ayatLen;
256
+ (0, import_react.useEffect)(() => {
257
+ if (ayat > ayatCount || ayat < 1) {
258
+ setAyat(1);
259
+ ayatValRef.current = 1;
260
+ onChange({ surah, ayat: 1 });
261
+ }
262
+ }, [surah, ayatCount]);
263
+ (0, import_react.useEffect)(() => {
264
+ if (!value) return;
265
+ if (value.surah === surahValRef.current && value.ayat === ayatValRef.current) return;
266
+ cancelAll();
267
+ surahValRef.current = value.surah;
268
+ ayatValRef.current = value.ayat;
269
+ setSurah(value.surah);
270
+ setAyat(value.ayat);
271
+ }, [value?.surah, value?.ayat]);
272
+ const scrollTo = (0, import_react.useCallback)((isSurah, scroll) => {
273
+ const wheelRef = isSurah ? surahWheelRef : ayatWheelRef;
274
+ const highlightRef = isSurah ? surahHighlightRef : ayatHighlightRef;
275
+ const len = ayatLenOf(isSurah);
276
+ if (wheelRef.current) {
277
+ wheelRef.current.childNodes.forEach((node) => {
278
+ const li = node;
279
+ const idx = Number(li.dataset.index);
280
+ let d = (idx - scroll) % len;
281
+ d = (d % len + len) % len;
282
+ if (d > len / 2) d -= len;
283
+ const cache = li;
284
+ if (Math.abs(d) > quarterCount) {
285
+ if (cache._w !== "h") {
286
+ cache._w = "h";
287
+ li.style.visibility = "hidden";
288
+ }
289
+ } else {
290
+ const t = `rotateX(${-itemAngle * d}deg) translateZ(${radius}px)`;
291
+ if (cache._w !== t) {
292
+ cache._w = t;
293
+ li.style.visibility = "visible";
294
+ li.style.transform = t;
295
+ }
296
+ }
297
+ });
298
+ }
299
+ if (highlightRef.current) {
300
+ highlightRef.current.style.transform = `translateY(${-scroll * itemHeight}px)`;
301
+ }
302
+ return scroll;
303
+ }, [surahOptions.length, ayatOptions.length]);
304
+ const cancelSurah = () => cancelAnimationFrame(surahMoveId.current);
305
+ const cancelAyat = () => cancelAnimationFrame(ayatMoveId.current);
306
+ const cancelAll = () => {
307
+ cancelSurah();
308
+ cancelAyat();
309
+ motionRef.current = false;
310
+ };
311
+ const animateScroll = (isSurah, start, end, duration, onComplete) => {
312
+ if (start === end || duration === 0) {
313
+ scrollTo(isSurah, start);
314
+ return;
315
+ }
316
+ motionRef.current = true;
317
+ const startTime = performance.now();
318
+ const dist = end - start;
319
+ const ref = isSurah ? surahScrollRef : ayatScrollRef;
320
+ const moveRef = isSurah ? surahMoveId : ayatMoveId;
321
+ const tick = (now) => {
322
+ const t = performance.now();
323
+ const elapsed = (t - startTime) / 1e3;
324
+ if (elapsed < duration) {
325
+ const p = easeOutCubic(elapsed / duration);
326
+ ref.current = scrollTo(isSurah, start + p * dist);
327
+ moveRef.current = requestAnimationFrame(tick);
328
+ } else {
329
+ motionRef.current = false;
330
+ if (isSurah) cancelSurah();
331
+ else cancelAyat();
332
+ ref.current = scrollTo(isSurah, end);
333
+ onComplete?.();
334
+ }
335
+ };
336
+ requestAnimationFrame(tick);
337
+ };
338
+ const selectByScroll = (isSurah, scroll) => {
339
+ const len = ayatLenOf(isSurah);
340
+ const norm = Math.round(scroll);
341
+ const bounded = boundIndex(norm, len);
342
+ if (isSurah) {
343
+ surahScrollRef.current = scrollTo(true, bounded);
344
+ const sel = surahOptions[surahScrollRef.current];
345
+ if (sel) {
346
+ const n = sel.number;
347
+ if (n !== surahValRef.current) {
348
+ vibrate();
349
+ surahValRef.current = n;
350
+ ayatValRef.current = 1;
351
+ setSurah(n);
352
+ setAyat(1);
353
+ onChangeRef.current({ surah: n, ayat: 1 });
354
+ }
355
+ }
356
+ } else {
357
+ ayatScrollRef.current = scrollTo(false, bounded);
358
+ const n = bounded + 1;
359
+ if (n !== ayatValRef.current) {
360
+ vibrate();
361
+ ayatValRef.current = n;
362
+ setAyat(n);
363
+ onChangeRef.current({ surah: surahValRef.current, ayat: n });
364
+ }
365
+ }
366
+ };
367
+ const throttledVibrate = () => {
368
+ const now = performance.now();
369
+ if (now - lastVibRef.current > VIB_THROTTLE_MS) {
370
+ lastVibRef.current = now;
371
+ vibrate();
372
+ }
373
+ };
374
+ const calcVelocity = (yList) => {
375
+ if (yList.length < 2) return 0;
376
+ const clampV = (v) => clamp(v, -liveRef.current.maxVelocity, liveRef.current.maxVelocity);
377
+ const last = yList[yList.length - 1];
378
+ const pts = yList.filter(([_, t]) => last[1] - t <= 120);
379
+ if (pts.length < 2) {
380
+ const [sy, st] = yList[yList.length - 2];
381
+ const [ey, et] = last;
382
+ const dt = (et - st) / 1e3;
383
+ if (dt <= 0) return 0;
384
+ return clampV((sy - ey) / itemHeight / dt);
385
+ }
386
+ const n = pts.length;
387
+ const t0 = pts[0][1] / 1e3;
388
+ let sx = 0, sy2 = 0, sxx = 0, sxy = 0;
389
+ for (const [y, t] of pts) {
390
+ const x = t / 1e3 - t0;
391
+ sx += x;
392
+ sy2 += y;
393
+ sxx += x * x;
394
+ sxy += x * y;
395
+ }
396
+ const denom = n * sxx - sx * sx;
397
+ if (denom === 0) return 0;
398
+ const slope = (n * sxy - sx * sy2) / denom;
399
+ return clampV(-slope / itemHeight);
400
+ };
401
+ const commitNotch = (isSurah, notch) => {
402
+ const len = ayatLenOf(isSurah);
403
+ const bounded = boundIndex(notch, len);
404
+ if (isSurah) {
405
+ const sel = surahOptions[bounded];
406
+ if (sel && sel.number !== surahValRef.current) {
407
+ surahValRef.current = sel.number;
408
+ ayatValRef.current = 1;
409
+ setSurah(sel.number);
410
+ setAyat(1);
411
+ onChangeRef.current({ surah: sel.number, ayat: 1 });
412
+ }
413
+ } else {
414
+ const n = bounded + 1;
415
+ if (n !== ayatValRef.current) {
416
+ ayatValRef.current = n;
417
+ setAyat(n);
418
+ onChangeRef.current({ surah: surahValRef.current, ayat: n });
419
+ }
420
+ }
421
+ };
422
+ const fling = (isSurah, velocity) => {
423
+ const scrollRef = isSurah ? surahScrollRef : ayatScrollRef;
424
+ const moveRef = isSurah ? surahMoveId : ayatMoveId;
425
+ const len = ayatLenOf(isSurah);
426
+ const { friction: friction2, snapMs: snapMs2, loop: loop2 } = liveRef.current;
427
+ let v = velocity;
428
+ let last = performance.now();
429
+ let lastNotch = Math.round(scrollRef.current);
430
+ motionRef.current = true;
431
+ const step = (_rafNow) => {
432
+ const now = performance.now();
433
+ const dt = clamp((now - last) / 1e3, 0, 0.05);
434
+ last = now;
435
+ v *= Math.exp(-friction2 * dt);
436
+ if (Math.abs(v) < STOP_VELOCITY) {
437
+ const target = loop2 ? Math.round(scrollRef.current) : clamp(Math.round(scrollRef.current), 0, len - 1);
438
+ animateScroll(isSurah, scrollRef.current, target, snapMs2 / 1e3, () => selectByScroll(isSurah, target));
439
+ return;
440
+ }
441
+ let next = scrollRef.current + v * dt;
442
+ if (!loop2 && (next < 0 || next > len - 1)) {
443
+ next = clamp(next, 0, len - 1);
444
+ v = 0;
445
+ }
446
+ scrollRef.current = scrollTo(isSurah, next);
447
+ const notch = Math.round(scrollRef.current);
448
+ if (notch !== lastNotch) {
449
+ lastNotch = notch;
450
+ throttledVibrate();
451
+ commitNotch(isSurah, notch);
452
+ }
453
+ moveRef.current = requestAnimationFrame(step);
454
+ };
455
+ moveRef.current = requestAnimationFrame(step);
456
+ };
457
+ const scrollByStep = (isSurah, step) => {
458
+ const ref = isSurah ? surahScrollRef : ayatScrollRef;
459
+ const start = ref.current;
460
+ const len = ayatLenOf(isSurah);
461
+ let end = Math.round(start + step);
462
+ if (!liveRef.current.loop) end = clamp(end, 0, len - 1);
463
+ const dist = Math.abs(end - start);
464
+ if (dist === 0) return;
465
+ const duration = Math.sqrt(dist / 5);
466
+ cancelAll();
467
+ animateScroll(isSurah, start, end, duration, () => selectByScroll(isSurah, ref.current));
468
+ };
469
+ (0, import_react.useEffect)(() => {
470
+ const si = surahOptions.findIndex((s) => s.number === surah);
471
+ const ai = ayat - 1;
472
+ surahScrollRef.current = scrollTo(true, si);
473
+ ayatScrollRef.current = scrollTo(false, ai);
474
+ }, []);
475
+ (0, import_react.useEffect)(() => {
476
+ if (motionRef.current || surahDragging.current) return;
477
+ const si = surahOptions.findIndex((s) => s.number === surah);
478
+ if (si !== -1) {
479
+ surahScrollRef.current = scrollTo(true, si);
480
+ }
481
+ }, [surah, scrollTo, surahOptions]);
482
+ (0, import_react.useEffect)(() => {
483
+ if (motionRef.current || ayatDragging.current) return;
484
+ ayatScrollRef.current = scrollTo(false, ayat - 1);
485
+ }, [ayat, ayatCount, scrollTo]);
486
+ const makeDrag = (isSurah) => {
487
+ const containerRef = isSurah ? surahContainerRef : ayatContainerRef;
488
+ const draggingRef = isSurah ? surahDragging : ayatDragging;
489
+ const scrollRef = isSurah ? surahScrollRef : ayatScrollRef;
490
+ const wheelTimerRef = isSurah ? wheelTimerSurah : wheelTimerAyat;
491
+ const yList = [];
492
+ let startY = 0;
493
+ let touchScroll = 0;
494
+ let isClick = true;
495
+ let lastTick = 0;
496
+ const onDown = (e) => {
497
+ draggingRef.current = true;
498
+ const y = (e instanceof MouseEvent ? e.clientY : e.touches?.[0]?.clientY) || 0;
499
+ startY = y;
500
+ yList.length = 0;
501
+ yList.push([y, Date.now()]);
502
+ touchScroll = scrollRef.current;
503
+ lastTick = Math.round(touchScroll);
504
+ isClick = true;
505
+ window.clearTimeout(wheelTimerRef.current);
506
+ cancelAll();
507
+ };
508
+ const onMove = (e) => {
509
+ if (!draggingRef.current) return;
510
+ if (e.cancelable) e.preventDefault();
511
+ const y = (e instanceof MouseEvent ? e.clientY : e.touches?.[0]?.clientY) || 0;
512
+ if (isClick && Math.abs(y - startY) > 5) isClick = false;
513
+ yList.push([y, Date.now()]);
514
+ if (yList.length > 5) yList.shift();
515
+ const delta = (startY - y) / itemHeight;
516
+ let next = touchScroll + delta;
517
+ const len = ayatLenOf(isSurah);
518
+ if (!liveRef.current.loop) {
519
+ if (next < 0) next *= RESISTANCE;
520
+ else if (next > len - 1) next = len - 1 + (next - (len - 1)) * RESISTANCE;
521
+ }
522
+ scrollRef.current = scrollTo(isSurah, next);
523
+ const notch = Math.round(scrollRef.current);
524
+ if (notch !== lastTick) {
525
+ lastTick = notch;
526
+ throttledVibrate();
527
+ commitNotch(isSurah, notch);
528
+ }
529
+ };
530
+ const onUp = (e) => {
531
+ if (!draggingRef.current) return;
532
+ draggingRef.current = false;
533
+ if (isClick) {
534
+ const y = (e instanceof MouseEvent ? e.clientY : e.changedTouches?.[0]?.clientY) || 0;
535
+ const container = containerRef.current;
536
+ if (container) {
537
+ const top = container.getBoundingClientRect().top;
538
+ const clickY = y - top;
539
+ const quarter = visibleCount >> 2;
540
+ let pos = 0;
541
+ const degToRad = Math.PI / 180;
542
+ for (let i = quarter - 1; i >= -quarter + 1; --i) {
543
+ const segLen = itemHeight * Math.cos(i * itemAngle * degToRad);
544
+ const start = pos;
545
+ pos += segLen;
546
+ if (clickY >= start && clickY <= pos) {
547
+ const steps = (quarter - i - 1) * -1;
548
+ if (steps !== 0) scrollByStep(isSurah, steps);
549
+ return;
550
+ }
551
+ }
552
+ }
553
+ return;
554
+ }
555
+ cancelAll();
556
+ const live = liveRef.current;
557
+ const len = ayatLenOf(isSurah);
558
+ if (!live.loop && (scrollRef.current < 0 || scrollRef.current > len - 1)) {
559
+ const t = clamp(scrollRef.current, 0, len - 1);
560
+ animateScroll(isSurah, scrollRef.current, t, live.snapMs / 1e3, () => selectByScroll(isSurah, t));
561
+ return;
562
+ }
563
+ const velocity = calcVelocity(yList);
564
+ if (Math.abs(velocity) < MIN_FLICK_VELOCITY) {
565
+ const t = live.loop ? Math.round(scrollRef.current) : clamp(Math.round(scrollRef.current), 0, len - 1);
566
+ animateScroll(isSurah, scrollRef.current, t, live.snapMs / 1e3, () => selectByScroll(isSurah, t));
567
+ return;
568
+ }
569
+ fling(isSurah, velocity);
570
+ };
571
+ return { onDown, onMove, onUp };
572
+ };
573
+ const surahDrag = (0, import_react.useRef)(null);
574
+ const ayatDrag = (0, import_react.useRef)(null);
575
+ (0, import_react.useEffect)(() => {
576
+ const sDrag = makeDrag(true);
577
+ const aDrag = makeDrag(false);
578
+ surahDrag.current = sDrag;
579
+ ayatDrag.current = aDrag;
580
+ const sEl = surahContainerRef.current;
581
+ const aEl = ayatContainerRef.current;
582
+ if (sEl) {
583
+ sEl.addEventListener("touchstart", sDrag.onDown, { passive: false });
584
+ sEl.addEventListener("touchmove", sDrag.onMove, { passive: false });
585
+ sEl.addEventListener("touchend", sDrag.onUp);
586
+ sEl.addEventListener("mousedown", sDrag.onDown);
587
+ document.addEventListener("mousemove", sDrag.onMove);
588
+ document.addEventListener("mouseup", sDrag.onUp);
589
+ }
590
+ if (aEl) {
591
+ aEl.addEventListener("touchstart", aDrag.onDown, { passive: false });
592
+ aEl.addEventListener("touchmove", aDrag.onMove, { passive: false });
593
+ aEl.addEventListener("touchend", aDrag.onUp);
594
+ aEl.addEventListener("mousedown", aDrag.onDown);
595
+ document.addEventListener("mousemove", aDrag.onMove);
596
+ document.addEventListener("mouseup", aDrag.onUp);
597
+ }
598
+ const onWheel = (e, isSurah) => {
599
+ if (e.cancelable) e.preventDefault();
600
+ cancelAll();
601
+ motionRef.current = true;
602
+ const timerRef = isSurah ? wheelTimerSurah : wheelTimerAyat;
603
+ const accRef = isSurah ? wheelAccumSurah : wheelAccumAyat;
604
+ const targetRef = isSurah ? wheelTargetSurah : wheelTargetAyat;
605
+ window.clearTimeout(timerRef.current);
606
+ const ref = isSurah ? surahScrollRef : ayatScrollRef;
607
+ const live = liveRef.current;
608
+ const len = ayatLenOf(isSurah);
609
+ let target = targetRef.current ?? Math.round(ref.current);
610
+ const unit = e.deltaMode === 1 ? WHEEL_LINE_PX : e.deltaMode === 2 ? 400 : 1;
611
+ accRef.current += e.deltaY * unit / WHEEL_NOTCH_PX;
612
+ let guard = 0;
613
+ while (Math.abs(accRef.current) >= 1 && guard++ < 8) {
614
+ const dir = Math.sign(accRef.current);
615
+ const t = target + dir;
616
+ if (!live.loop && (t < 0 || t > len - 1)) {
617
+ accRef.current = 0;
618
+ break;
619
+ }
620
+ accRef.current -= dir;
621
+ target = t;
622
+ throttledVibrate();
623
+ commitNotch(isSurah, t);
624
+ }
625
+ targetRef.current = target;
626
+ animateScroll(isSurah, ref.current, target, live.snapMs / 1e3, void 0);
627
+ timerRef.current = window.setTimeout(() => {
628
+ targetRef.current = null;
629
+ const t = live.loop ? Math.round(ref.current) : clamp(Math.round(ref.current), 0, len - 1);
630
+ animateScroll(isSurah, ref.current, t, live.snapMs / 1e3, () => selectByScroll(isSurah, t));
631
+ }, WHEEL_SETTLE_MS);
632
+ };
633
+ const sWheel = (e) => onWheel(e, true);
634
+ const aWheel = (e) => onWheel(e, false);
635
+ sEl?.addEventListener("wheel", sWheel, { passive: false });
636
+ aEl?.addEventListener("wheel", aWheel, { passive: false });
637
+ return () => {
638
+ window.clearTimeout(wheelTimerSurah.current);
639
+ window.clearTimeout(wheelTimerAyat.current);
640
+ cancelAll();
641
+ sEl?.removeEventListener("touchstart", sDrag.onDown);
642
+ sEl?.removeEventListener("touchmove", sDrag.onMove);
643
+ sEl?.removeEventListener("touchend", sDrag.onUp);
644
+ sEl?.removeEventListener("mousedown", sDrag.onDown);
645
+ document.removeEventListener("mousemove", sDrag.onMove);
646
+ document.removeEventListener("mouseup", sDrag.onUp);
647
+ aEl?.removeEventListener("touchstart", aDrag.onDown);
648
+ aEl?.removeEventListener("touchmove", aDrag.onMove);
649
+ aEl?.removeEventListener("touchend", aDrag.onUp);
650
+ aEl?.removeEventListener("mousedown", aDrag.onDown);
651
+ document.removeEventListener("mousemove", aDrag.onMove);
652
+ document.removeEventListener("mouseup", aDrag.onUp);
653
+ sEl?.removeEventListener("wheel", sWheel);
654
+ aEl?.removeEventListener("wheel", aWheel);
655
+ };
656
+ }, []);
657
+ const onKeySurah = (e) => {
658
+ if (e.key === "ArrowDown") {
659
+ e.preventDefault();
660
+ scrollByStep(true, 1);
661
+ }
662
+ if (e.key === "ArrowUp") {
663
+ e.preventDefault();
664
+ scrollByStep(true, -1);
665
+ }
666
+ };
667
+ const onKeyAyat = (e) => {
668
+ if (e.key === "ArrowDown") {
669
+ e.preventDefault();
670
+ scrollByStep(false, 1);
671
+ }
672
+ if (e.key === "ArrowUp") {
673
+ e.preventDefault();
674
+ scrollByStep(false, -1);
675
+ }
676
+ };
677
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: chrome ? "rounded-[20px] bg-surface border border-border p-4" : "", children: [
678
+ chrome ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
679
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex flex-col sm:flex-row sm:items-center justify-between gap-3 mb-3", children: [
680
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
681
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "text-[18px] font-bold leading-none tracking-tight text-foreground", children: labels.surah }),
682
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "text-xs text-muted mt-1", children: labels.subtitle })
683
+ ] }),
684
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rounded-xl px-4 py-2.5 text-[13px] font-medium min-w-[170px] text-center border-2 border-accent text-foreground w-full sm:w-auto", children: [
685
+ pad2(surah),
686
+ " ",
687
+ activeSurah.name,
688
+ " : ",
689
+ ayat
690
+ ] })
691
+ ] }),
692
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { type: "hidden", name: nameSurah, value: String(surah) }),
693
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { type: "hidden", name: nameAyat, value: String(ayat) })
694
+ ] }) : null,
695
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "relative flex gap-2 rounded-2xl bg-accent/5 border border-border p-2 overflow-hidden", style: { perspective: "2000px" }, children: [
696
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
697
+ "div",
698
+ {
699
+ ref: surahContainerRef,
700
+ tabIndex: 0,
701
+ role: "listbox",
702
+ "aria-label": surahLabel,
703
+ onKeyDown: onKeySurah,
704
+ className: "flex-1 min-w-0 relative overflow-hidden focus:outline-none focus-visible:ring-2 focus-visible:ring-accent rounded-xl",
705
+ style: { height: containerHeight, maskImage: "linear-gradient(to bottom, transparent 0%, black 20%, black 80%, transparent 100%)" },
706
+ children: [
707
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
708
+ "ul",
709
+ {
710
+ ref: surahWheelRef,
711
+ className: "absolute top-1/2 left-0 w-full h-0 m-0 p-0 list-none",
712
+ style: { transformStyle: "preserve-3d", willChange: "transform", backfaceVisibility: "hidden" },
713
+ children: SURAH_LIST.map((s, idx) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
714
+ "li",
715
+ {
716
+ "data-index": idx,
717
+ className: "absolute top-0 left-0 w-full flex items-center px-3 cursor-pointer select-none text-[13px]",
718
+ style: {
719
+ top: -halfItemHeight,
720
+ height: itemHeight,
721
+ lineHeight: `${itemHeight}px`,
722
+ visibility: "hidden"
723
+ },
724
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: s.number === surah ? "text-foreground font-bold" : "text-muted", children: [
725
+ pad2(s.number),
726
+ " ",
727
+ s.name
728
+ ] })
729
+ },
730
+ s.number
731
+ ))
732
+ }
733
+ ),
734
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "pointer-events-none absolute top-1/2 inset-x-2 -translate-y-1/2 h-9 rounded-xl border-2 border-accent z-10" })
735
+ ]
736
+ }
737
+ ),
738
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
739
+ "div",
740
+ {
741
+ ref: ayatContainerRef,
742
+ tabIndex: 0,
743
+ role: "listbox",
744
+ "aria-label": ayatLabel,
745
+ onKeyDown: onKeyAyat,
746
+ className: "flex-1 min-w-0 sm:flex-none sm:w-[88px] relative overflow-hidden shrink-0 border-l border-border focus:outline-none focus-visible:ring-2 focus-visible:ring-accent rounded-xl",
747
+ style: { height: containerHeight, maskImage: "linear-gradient(to bottom, transparent 0%, black 20%, black 80%, transparent 100%)" },
748
+ children: [
749
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
750
+ "ul",
751
+ {
752
+ ref: ayatWheelRef,
753
+ className: "absolute top-1/2 left-0 w-full h-0 m-0 p-0 list-none",
754
+ style: { transformStyle: "preserve-3d", willChange: "transform", backfaceVisibility: "hidden" },
755
+ children: ayatOptions.map((n, idx) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
756
+ "li",
757
+ {
758
+ "data-index": idx,
759
+ className: "absolute top-0 left-0 w-full flex items-center justify-center cursor-pointer select-none text-[13px]",
760
+ style: {
761
+ top: -halfItemHeight,
762
+ height: itemHeight,
763
+ lineHeight: `${itemHeight}px`,
764
+ visibility: "hidden"
765
+ },
766
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: n === ayat ? "text-foreground font-bold" : "text-muted", children: n })
767
+ },
768
+ n
769
+ ))
770
+ }
771
+ ),
772
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "pointer-events-none absolute top-1/2 inset-x-2 -translate-y-1/2 h-9 rounded-xl border-2 border-accent z-10" })
773
+ ]
774
+ }
775
+ )
776
+ ] }),
777
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "sr-only", "aria-hidden": true, children: [
778
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { ref: surahHighlightRef }),
779
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { ref: ayatHighlightRef })
780
+ ] })
781
+ ] });
782
+ }
783
+
784
+ // src/core/SurahRangePicker.tsx
785
+ var import_react2 = require("react");
786
+ var import_jsx_runtime2 = require("react/jsx-runtime");
787
+ var DEFAULT_RANGE = {
788
+ from: { surah: 1, ayat: 1 },
789
+ to: { surah: 2, ayat: 5 }
790
+ };
791
+ var cmpPos = (a, b) => a.surah !== b.surah ? a.surah - b.surah : a.ayat - b.ayat;
792
+ function SurahRangePicker({
793
+ value,
794
+ labels: labelsProp,
795
+ onChange,
796
+ physics,
797
+ loop = false,
798
+ nameFromSurah = "from_surah_number",
799
+ nameFromAyat = "from_ayat",
800
+ nameToSurah = "to_surah_number",
801
+ nameToAyat = "to_ayat"
802
+ }) {
803
+ const labels = resolveLabels(labelsProp);
804
+ const [from, setFrom] = (0, import_react2.useState)(value?.from ?? DEFAULT_RANGE.from);
805
+ const [to, setTo] = (0, import_react2.useState)(value?.to ?? DEFAULT_RANGE.to);
806
+ const emit = (next) => {
807
+ setFrom(next.from);
808
+ setTo(next.to);
809
+ onChange(next);
810
+ };
811
+ const handleFrom = (f) => {
812
+ emit({ from: f, to: cmpPos(f, to) > 0 ? f : to });
813
+ };
814
+ const handleTo = (next) => {
815
+ emit({ from: cmpPos(from, next) > 0 ? next : from, to: next });
816
+ };
817
+ const nameOf = (n) => getSurah(n)?.name ?? String(n);
818
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "rounded-[20px] bg-surface border border-border p-4", children: [
819
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-col sm:flex-row sm:items-center justify-between gap-3 mb-3", children: [
820
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
821
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "text-[18px] font-bold leading-none tracking-tight text-foreground", children: labels.surah }),
822
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "text-xs text-muted mt-1", children: labels.subtitle })
823
+ ] }),
824
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "rounded-xl px-4 py-2.5 text-[13px] font-medium min-w-[170px] text-center border-2 border-accent text-foreground w-full sm:w-auto", children: [
825
+ nameOf(from.surah),
826
+ ":",
827
+ from.ayat,
828
+ " - ",
829
+ nameOf(to.surah),
830
+ ":",
831
+ to.ayat
832
+ ] })
833
+ ] }),
834
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("input", { type: "hidden", name: nameFromSurah, value: String(from.surah) }),
835
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("input", { type: "hidden", name: nameFromAyat, value: String(from.ayat) }),
836
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("input", { type: "hidden", name: nameToSurah, value: String(to.surah) }),
837
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("input", { type: "hidden", name: nameToAyat, value: String(to.ayat) }),
838
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "space-y-4", children: [
839
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "space-y-2", children: [
840
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h3", { className: "text-sm font-semibold text-foreground", children: labels.from }),
841
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
842
+ SurahWheelPicker,
843
+ {
844
+ value: from,
845
+ onChange: handleFrom,
846
+ physics,
847
+ loop,
848
+ chrome: false,
849
+ ariaSurahLabel: labels.fromSurah,
850
+ ariaAyatLabel: labels.fromAyah
851
+ }
852
+ )
853
+ ] }),
854
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "space-y-2", children: [
855
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h3", { className: "text-sm font-semibold text-foreground", children: labels.to }),
856
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
857
+ SurahWheelPicker,
858
+ {
859
+ value: to,
860
+ onChange: handleTo,
861
+ physics,
862
+ loop,
863
+ chrome: false,
864
+ ariaSurahLabel: labels.toSurah,
865
+ ariaAyatLabel: labels.toAyah
866
+ }
867
+ )
868
+ ] })
869
+ ] })
870
+ ] });
871
+ }
872
+
873
+ // src/core/SurahFieldPicker.tsx
874
+ var import_react4 = require("react");
875
+
876
+ // src/ui/Popup.tsx
877
+ var import_react3 = require("react");
878
+ var import_react_dom = require("react-dom");
879
+ var import_jsx_runtime3 = require("react/jsx-runtime");
880
+ var useIsoLayoutEffect = typeof window !== "undefined" ? import_react3.useLayoutEffect : import_react3.useEffect;
881
+ var GAP = 8;
882
+ function clamp2(v, min, max) {
883
+ return Math.max(min, Math.min(v, max));
884
+ }
885
+ function place(anchor, popW, popH, placement) {
886
+ const vw = window.innerWidth;
887
+ const vh = window.innerHeight;
888
+ if (placement === "right") {
889
+ let x2 = anchor.right + GAP;
890
+ if (x2 + popW > vw) x2 = anchor.left - popW - GAP;
891
+ const y2 = clamp2(
892
+ anchor.top + anchor.height / 2 - popH / 2,
893
+ GAP,
894
+ Math.max(GAP, vh - popH - GAP)
895
+ );
896
+ return { x: Math.max(GAP, x2), y: y2 };
897
+ }
898
+ let y = anchor.bottom + GAP;
899
+ if (y + popH > vh) y = anchor.top - popH - GAP;
900
+ const x = clamp2(anchor.left, GAP, Math.max(GAP, vw - popW - GAP));
901
+ return { x, y: Math.max(GAP, y) };
902
+ }
903
+ function Popup({
904
+ open,
905
+ onClose,
906
+ anchorRef,
907
+ placement = "bottom",
908
+ label,
909
+ className,
910
+ children
911
+ }) {
912
+ const popRef = (0, import_react3.useRef)(null);
913
+ const [pos, setPos] = (0, import_react3.useState)(null);
914
+ useIsoLayoutEffect(() => {
915
+ if (!open) {
916
+ setPos(null);
917
+ return;
918
+ }
919
+ const anchor = anchorRef.current;
920
+ const pop = popRef.current;
921
+ if (!anchor || !pop || typeof window === "undefined") return;
922
+ const doPlace = () => {
923
+ const el = popRef.current;
924
+ const a = anchorRef.current;
925
+ if (!el || !a) return;
926
+ setPos(place(a.getBoundingClientRect(), el.offsetWidth, el.offsetHeight, placement));
927
+ };
928
+ doPlace();
929
+ window.addEventListener("resize", doPlace);
930
+ window.addEventListener("scroll", doPlace, true);
931
+ return () => {
932
+ window.removeEventListener("resize", doPlace);
933
+ window.removeEventListener("scroll", doPlace, true);
934
+ };
935
+ }, [open, anchorRef, placement, children]);
936
+ (0, import_react3.useEffect)(() => {
937
+ if (!open || typeof document === "undefined") return;
938
+ const onKey = (e) => {
939
+ if (e.key === "Escape") onClose();
940
+ };
941
+ const onPointer = (e) => {
942
+ const t = e.target;
943
+ if (popRef.current?.contains(t)) return;
944
+ if (anchorRef.current?.contains(t)) return;
945
+ onClose();
946
+ };
947
+ document.addEventListener("keydown", onKey);
948
+ document.addEventListener("pointerdown", onPointer);
949
+ return () => {
950
+ document.removeEventListener("keydown", onKey);
951
+ document.removeEventListener("pointerdown", onPointer);
952
+ };
953
+ }, [open, onClose, anchorRef]);
954
+ (0, import_react3.useEffect)(() => {
955
+ if (!open && typeof document !== "undefined") {
956
+ const active = document.activeElement;
957
+ if (active && popRef.current?.contains(active)) {
958
+ anchorRef.current?.focus();
959
+ }
960
+ }
961
+ }, [open, anchorRef]);
962
+ if (!open || typeof document === "undefined") return null;
963
+ return (0, import_react_dom.createPortal)(
964
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
965
+ "div",
966
+ {
967
+ ref: popRef,
968
+ role: "dialog",
969
+ "aria-label": label,
970
+ "aria-modal": "false",
971
+ className: `sp-popup ${className ?? ""}`,
972
+ style: pos ? { left: pos.x, top: pos.y, visibility: "visible" } : { left: 0, top: 0, visibility: "hidden" },
973
+ children
974
+ }
975
+ ),
976
+ document.body
977
+ );
978
+ }
979
+
980
+ // src/icons/icons.tsx
981
+ var import_jsx_runtime4 = require("react/jsx-runtime");
982
+ function ChevronDownIcon({ className }) {
983
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
984
+ "svg",
985
+ {
986
+ className,
987
+ width: "16",
988
+ height: "16",
989
+ viewBox: "0 0 24 24",
990
+ fill: "none",
991
+ stroke: "currentColor",
992
+ strokeWidth: "2",
993
+ strokeLinecap: "round",
994
+ strokeLinejoin: "round",
995
+ "aria-hidden": "true",
996
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("polyline", { points: "6 9 12 15 18 9" })
997
+ }
998
+ );
999
+ }
1000
+ function BookIcon({ className }) {
1001
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1002
+ "svg",
1003
+ {
1004
+ className,
1005
+ width: "16",
1006
+ height: "16",
1007
+ viewBox: "0 0 24 24",
1008
+ fill: "none",
1009
+ stroke: "currentColor",
1010
+ strokeWidth: "2",
1011
+ strokeLinecap: "round",
1012
+ strokeLinejoin: "round",
1013
+ "aria-hidden": "true",
1014
+ children: [
1015
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M4 19.5A2.5 2.5 0 0 1 6.5 17H20" }),
1016
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z" })
1017
+ ]
1018
+ }
1019
+ );
1020
+ }
1021
+
1022
+ // src/core/SurahFieldPicker.tsx
1023
+ var import_jsx_runtime5 = require("react/jsx-runtime");
1024
+ function SurahFieldPicker({
1025
+ value,
1026
+ onChange,
1027
+ physics,
1028
+ loop = false,
1029
+ placeholder,
1030
+ className,
1031
+ popoverClassName,
1032
+ icon,
1033
+ placement = "right",
1034
+ labels: labelsProp,
1035
+ nameSurah = "surah_number",
1036
+ nameAyat = "ayat"
1037
+ }) {
1038
+ const labels = resolveLabels(labelsProp);
1039
+ const [committed, setCommitted] = (0, import_react4.useState)(value ?? { surah: 3, ayat: 3 });
1040
+ const [draft, setDraft] = (0, import_react4.useState)(committed);
1041
+ const [open, setOpen] = (0, import_react4.useState)(false);
1042
+ const fieldRef = (0, import_react4.useRef)(null);
1043
+ const current = value ?? committed;
1044
+ const text = placeholder && !value ? placeholder : `${getSurah(current.surah)?.name ?? current.surah}:${current.ayat}`;
1045
+ const handleOpenChange = (next) => {
1046
+ if (next) setDraft(current);
1047
+ setOpen(next);
1048
+ };
1049
+ const handleSelect = () => {
1050
+ setCommitted(draft);
1051
+ onChange(draft);
1052
+ setOpen(false);
1053
+ };
1054
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
1055
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("input", { type: "hidden", name: nameSurah, value: String(current.surah) }),
1056
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("input", { type: "hidden", name: nameAyat, value: String(current.ayat) }),
1057
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1058
+ "button",
1059
+ {
1060
+ ref: fieldRef,
1061
+ type: "button",
1062
+ onClick: () => handleOpenChange(!open),
1063
+ className: `flex w-full items-center justify-between gap-2 rounded-xl border border-divider bg-background px-3 py-2.5 text-sm text-foreground ${className ?? ""}`,
1064
+ children: [
1065
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "truncate", children: text }),
1066
+ icon ?? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(BookIcon, { className: "w-4 h-4 shrink-0 text-muted" })
1067
+ ]
1068
+ }
1069
+ ),
1070
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1071
+ Popup,
1072
+ {
1073
+ open,
1074
+ onClose: () => setOpen(false),
1075
+ anchorRef: fieldRef,
1076
+ placement,
1077
+ label: labels.surah,
1078
+ className: popoverClassName,
1079
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "w-[320px] max-w-[calc(100vw-2rem)] p-3", children: [
1080
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1081
+ SurahWheelPicker,
1082
+ {
1083
+ value: draft,
1084
+ onChange: setDraft,
1085
+ physics,
1086
+ loop,
1087
+ chrome: false,
1088
+ labels: labelsProp,
1089
+ nameSurah,
1090
+ nameAyat
1091
+ }
1092
+ ),
1093
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "mt-3 flex justify-end gap-2", children: [
1094
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", className: "sp-btn sp-btn-ghost", onClick: () => setOpen(false), children: labels.cancel }),
1095
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", className: "sp-btn sp-btn-primary", onClick: handleSelect, children: labels.select })
1096
+ ] })
1097
+ ] })
1098
+ }
1099
+ )
1100
+ ] });
1101
+ }
1102
+ // Annotate the CommonJS export names for ESM import in node:
1103
+ 0 && (module.exports = {
1104
+ BookIcon,
1105
+ ChevronDownIcon,
1106
+ Popup,
1107
+ SURAH_LIST,
1108
+ SurahFieldPicker,
1109
+ SurahRangePicker,
1110
+ SurahWheelPicker,
1111
+ defaultLabels,
1112
+ getAyatCount,
1113
+ getSurah,
1114
+ resolveLabels
1115
+ });
1116
+ //# sourceMappingURL=index.cjs.map