[{"data":1,"prerenderedAt":4927},["ShallowReactive",2],{"categories":3,"registry-index":322,"registry-item-gantt":4789},[4,9,14,19,24,29,34,39,44,49,54,59,64,68,72,77,82,87,91,95,100,105,109,114,118,122,126,130,134,138,143,147,151,156,161,166,171,175,179,183,187,192,196,200,204,208,212,216,220,224,228,233,237,241,246,251,255,259,263,267,272,276,280,284,288,292,296,300,304,308,313,317],{"name":5,"label":6,"description":7,"count":8},"accordion","Accordion","Open-source shadcn accordion components for FAQ layouts, settings panels, and help sections.",11,{"name":10,"label":11,"description":12,"count":13},"alert","Alert","Open-source shadcn alert components for status messages, warning banners, and destructive notices.",20,{"name":15,"label":16,"description":17,"count":18},"alert-dialog","Alert Dialog","Open-source shadcn alert dialog components for delete confirmations, destructive actions, and critical decisions.",14,{"name":20,"label":21,"description":22,"count":23},"aspect-ratio","Aspect Ratio","Open-source shadcn aspect ratio components for responsive media, image galleries, and video previews.",8,{"name":25,"label":26,"description":27,"count":28},"autocomplete","Autocomplete","Open-source shadcn autocomplete components for smart search, typeahead inputs, and suggestion lists.",12,{"name":30,"label":31,"description":32,"count":33},"avatar","Avatar","Open-source shadcn avatar components for user profiles, team directories, and comment threads.",35,{"name":35,"label":36,"description":37,"count":38},"badge","Badge","Open-source shadcn badge components for status labels, feature tags, and count indicators.",25,{"name":40,"label":41,"description":42,"count":43},"breadcrumb","Breadcrumb","Open-source shadcn breadcrumb components for navigation trails, docs hierarchy, and app wayfinding.",15,{"name":45,"label":46,"description":47,"count":48},"button","Button","Open-source shadcn button components for primary actions, form submissions, and toolbar controls.",61,{"name":50,"label":51,"description":52,"count":53},"button-group","Button Group","Open-source shadcn button group components for segmented controls, toolbar actions, and view switchers.",57,{"name":55,"label":56,"description":57,"count":58},"calendar","Calendar","Open-source shadcn calendar components for date picking, scheduling flows, and booking interfaces.",30,{"name":60,"label":61,"description":62,"count":63},"card","Card","Open-source shadcn card components for content previews, dashboard widgets, and product summaries.",18,{"name":65,"label":66,"description":67,"count":8},"carousel","Carousel","Open-source shadcn carousel components for image galleries, feature showcases, and testimonials.",{"name":69,"label":70,"description":71,"count":38},"chart","Chart","Open-source shadcn chart components for analytics dashboards, KPI tracking, and reports.",{"name":73,"label":74,"description":75,"count":76},"checkbox","Checkbox","Open-source shadcn checkbox components for multi-select forms, settings preferences, and table selection.",22,{"name":78,"label":79,"description":80,"count":81},"collapsible","Collapsible","Open-source shadcn collapsible components for expandable sections, detail rows, and progressive disclosure.",10,{"name":83,"label":84,"description":85,"count":86},"combobox","Combobox","Open-source shadcn combobox components for searchable selection, async lookup, and command menus.",28,{"name":88,"label":89,"description":90,"count":23},"command","Command","Open-source shadcn command menu components for command palettes, global search, and power-user navigation.",{"name":92,"label":93,"description":94,"count":81},"context-menu","Context Menu","Open-source shadcn context menu components for right-click menus, file actions, and table row actions.",{"name":96,"label":97,"description":98,"count":99},"data-grid","Data Grid","Open-source shadcn data grid components for sorting and filtering, virtualized tables, and admin dashboards.",29,{"name":101,"label":102,"description":103,"count":104},"date-selector","Date Selector","Open-source shadcn date selector components for date range picking, booking windows, and calendar filters.",4,{"name":106,"label":107,"description":108,"count":81},"dialog","Dialog","Open-source shadcn dialog components for modal forms, focused tasks, and confirmations.",{"name":110,"label":111,"description":112,"count":113},"drawer","Drawer","Open-source shadcn drawer components for mobile panels, slide-over forms, and secondary workflows.",5,{"name":115,"label":116,"description":117,"count":63},"dropdown-menu","Dropdown Menu","Open-source shadcn dropdown menu components for action menus, profile menus, and toolbar menus.",{"name":119,"label":120,"description":121,"count":13},"empty","Empty","Open-source shadcn empty state components for zero-data screens, first-run onboarding, and guided next steps.",{"name":123,"label":124,"description":125,"count":113},"event-calendar","Event Calendar","Open-source shadcn event calendar components with month, week, day and agenda views, and drag-and-drop scheduling.",{"name":127,"label":128,"description":129,"count":8},"field","Field","Open-source shadcn form field components for form composition, validated inputs, and helper text.",{"name":131,"label":132,"description":133,"count":81},"file-upload","File Upload","Open-source shadcn file upload components for drag-and-drop uploads, multi-file flows, and upload progress.",{"name":135,"label":136,"description":137,"count":28},"filters","Filters","Open-source shadcn filters components for filter bars, faceted search, and dashboard filtering.",{"name":139,"label":140,"description":141,"count":142},"frame","Frame","Open-source shadcn frame components for embedded previews, app shells, and device mockups.",19,{"name":144,"label":145,"description":146,"count":113},"gantt","Gantt","Open-source shadcn gantt components with split tree and timeline panes, drag scheduling, and progress rollups.",{"name":148,"label":149,"description":150,"count":23},"hover-card","Hover Card","Open-source shadcn hover card components for profile previews, rich link previews, and contextual details.",{"name":152,"label":153,"description":154,"count":155},"icon-stack","Icon Stack","",6,{"name":157,"label":158,"description":159,"count":160},"icon-tile","Icon Tile","Open-source shadcn icon tile components for list row media, feature cards, empty states, and dashboard tiles.",17,{"name":162,"label":163,"description":164,"count":165},"input","Input","Open-source shadcn input components for form entry, search fields, and auth screens.",31,{"name":167,"label":168,"description":169,"count":170},"input-group","Input Group","Open-source shadcn input group components for prefixed inputs, suffixed actions, and combined form controls.",40,{"name":172,"label":173,"description":174,"count":155},"input-otp","Input Otp","Open-source shadcn OTP input components for verification codes, 2FA flows, and secure confirmation screens.",{"name":176,"label":177,"description":178,"count":28},"item","Item","Open-source shadcn item row components for list rows, settings items, and action rows.",{"name":180,"label":181,"description":182,"count":155},"kanban","Kanban","Open-source shadcn kanban components for task boards, workflow pipelines, and drag-and-drop planning.",{"name":184,"label":185,"description":186,"count":155},"kbd","Kbd","Open-source shadcn keyboard shortcut components for hotkey hints, command docs, and shortcut legends.",{"name":188,"label":189,"description":190,"count":191},"label","Label","Open-source shadcn label components for form labels, accessible inputs, and field pairing.",13,{"name":193,"label":194,"description":195,"count":113},"menubar","Menubar","Open-source shadcn menubar components for desktop navigation, editor menus, and app commands.",{"name":197,"label":198,"description":199,"count":155},"native-select","Native Select","Open-source shadcn native select components for lightweight selects, mobile-friendly forms, and basic option picking.",{"name":201,"label":202,"description":203,"count":104},"navigation-menu","Navigation Menu","Open-source shadcn navigation menu components for site navigation, mega menus, and product navigation.",{"name":205,"label":206,"description":207,"count":155},"number-field","Number Field","Open-source shadcn number field components for quantity pickers, pricing inputs, and step controls.",{"name":209,"label":210,"description":211,"count":43},"pagination","Pagination","Open-source shadcn pagination components for table pagination, search results, and content archives.",{"name":213,"label":214,"description":215,"count":23},"phone-input","Phone Input","Open-source shadcn phone input components for international signup, contact forms, and country codes.",{"name":217,"label":218,"description":219,"count":8},"popover","Popover","Open-source shadcn popover components for inline settings, contextual actions, and lightweight overlays.",{"name":221,"label":222,"description":223,"count":23},"progress","Progress","Open-source shadcn progress components for upload progress, goal tracking, and setup steps.",{"name":225,"label":226,"description":227,"count":160},"radio-group","Radio Group","Open-source shadcn radio group components for single-choice forms, plan selection, and settings preferences.",{"name":229,"label":230,"description":231,"count":232},"rating","Rating","Open-source shadcn rating components for product reviews, feedback forms, and satisfaction scores.",9,{"name":234,"label":235,"description":236,"count":81},"resizable","Resizable","Open-source shadcn resizable components for split panes, workspace layouts, and draggable dividers.",{"name":238,"label":239,"description":240,"count":113},"scroll-area","Scroll Area","Open-source shadcn scroll area components for custom scroll regions, sidebar panes, and dense lists.",{"name":242,"label":243,"description":244,"count":245},"scrollspy","Scrollspy","Open-source shadcn scrollspy components for docs navigation, active table of contents, and long-form sections.",2,{"name":247,"label":248,"description":249,"count":250},"select","Select","Open-source shadcn select components for form selection, settings menus, and filter controls.",33,{"name":252,"label":253,"description":254,"count":155},"separator","Separator","Open-source shadcn separator components for content grouping, toolbar dividers, and section breaks.",{"name":256,"label":257,"description":258,"count":104},"sheet","Sheet","Open-source shadcn sheet components for slide-over details, side panels, and task flows.",{"name":260,"label":261,"description":262,"count":81},"skeleton","Skeleton","Open-source shadcn skeleton components for loading placeholders, perceived performance, and content previews.",{"name":264,"label":265,"description":266,"count":28},"slider","Slider","Open-source shadcn slider components for range filters, pricing controls, and media settings.",{"name":268,"label":269,"description":270,"count":271},"sonner","Sonner","Open-source shadcn Sonner toast components for success feedback, error states, and non-blocking updates.",21,{"name":273,"label":274,"description":275,"count":23},"sortable","Sortable","Open-source shadcn sortable components for drag-and-drop lists, priority ranking, and reordering rows.",{"name":277,"label":278,"description":279,"count":28},"spinner","Spinner","Open-source shadcn spinner components for loading indicators, async actions, and button states.",{"name":281,"label":282,"description":283,"count":43},"stepper","Stepper","Open-source shadcn stepper components for multi-step forms, checkout flows, and onboarding.",{"name":285,"label":286,"description":287,"count":18},"switch","Switch","Open-source shadcn switch components for settings toggles, preferences, and binary choices.",{"name":289,"label":290,"description":291,"count":160},"table","Table","Open-source shadcn table components for admin tables, data display, and responsive lists.",{"name":293,"label":294,"description":295,"count":232},"tabs","Tabs","Open-source shadcn tabs components for segmented content, settings views, and dashboard sections.",{"name":297,"label":298,"description":299,"count":155},"textarea","Textarea","Open-source shadcn textarea components for comments, long-form input, and support tickets.",{"name":301,"label":302,"description":303,"count":28},"timeline","Timeline","Open-source shadcn timeline components for activity feeds, roadmaps, and event history.",{"name":305,"label":306,"description":307,"count":18},"toggle","Toggle","Open-source shadcn toggle components for formatting controls, compact actions, and binary UI states.",{"name":309,"label":310,"description":311,"count":312},"toggle-group","Toggle Group","Open-source shadcn toggle group components for view switches, toolbar options, and mutually exclusive actions.",16,{"name":314,"label":315,"description":316,"count":312},"tooltip","Tooltip","Open-source shadcn tooltip components for inline hints, contextual guidance, and helper text.",{"name":318,"label":319,"description":320,"count":321},"tree","Tree","Open-source shadcn tree components for file explorers, nested navigation, and hierarchical data.",7,{"$schema":323,"name":324,"homepage":325,"items":326},"https:\u002F\u002Fshadcn-vue.com\u002Fschema\u002Fregistry.json","revueui","https:\u002F\u002Frevueui.io",[327,332,335,338,341,344,347,350,353,356,359,362,365,368,371,374,377,380,383,386,389,392,395,398,401,404,407,410,413,416,419,422,425,428,431,434,437,440,443,446,449,452,455,458,461,464,467,470,473,476,479,484,487,490,493,496,499,502,505,508,511,514,517,520,523,526,529,532,535,538,541,548,553,559,564,569,574,579,583,587,591,595,599,603,607,611,615,619,623,627,631,635,639,643,647,651,655,659,663,667,671,675,679,683,687,691,695,699,703,707,711,714,718,722,726,730,734,738,742,746,750,753,757,761,765,769,773,777,781,785,789,793,797,801,805,809,813,817,821,825,829,833,837,841,845,849,853,857,861,865,868,872,876,880,884,888,892,896,900,904,908,912,916,920,924,928,931,935,939,943,947,951,955,959,963,967,971,975,979,983,987,991,995,999,1003,1007,1011,1015,1019,1023,1027,1031,1035,1039,1043,1047,1051,1055,1059,1063,1067,1071,1075,1079,1083,1087,1091,1095,1099,1103,1107,1111,1115,1119,1123,1127,1131,1135,1139,1143,1147,1151,1155,1159,1163,1167,1171,1175,1179,1183,1187,1191,1195,1199,1203,1207,1211,1214,1218,1222,1226,1230,1234,1238,1242,1246,1250,1254,1258,1262,1266,1270,1274,1278,1282,1286,1290,1294,1298,1302,1306,1310,1314,1318,1322,1326,1330,1334,1338,1342,1346,1350,1354,1358,1362,1366,1370,1374,1378,1382,1386,1390,1394,1398,1402,1406,1410,1414,1418,1422,1426,1430,1434,1438,1442,1446,1450,1454,1458,1462,1466,1470,1474,1478,1482,1486,1490,1493,1497,1501,1505,1509,1513,1517,1521,1525,1529,1533,1537,1541,1545,1549,1553,1557,1561,1565,1569,1573,1577,1581,1585,1589,1593,1597,1601,1605,1609,1613,1617,1621,1625,1629,1633,1637,1641,1645,1649,1653,1657,1661,1665,1669,1672,1676,1680,1684,1688,1692,1696,1700,1704,1708,1712,1716,1720,1724,1728,1732,1736,1740,1744,1748,1752,1756,1760,1764,1768,1772,1776,1780,1784,1788,1792,1796,1800,1804,1808,1812,1816,1820,1824,1828,1832,1836,1840,1844,1848,1852,1856,1860,1864,1868,1872,1876,1880,1884,1888,1892,1896,1900,1904,1908,1912,1916,1920,1924,1928,1932,1936,1940,1944,1948,1952,1956,1960,1964,1968,1972,1976,1980,1983,1987,1990,1994,1998,2002,2006,2010,2013,2017,2021,2025,2029,2033,2037,2041,2045,2049,2053,2057,2061,2065,2069,2073,2077,2081,2085,2089,2093,2097,2101,2105,2109,2113,2117,2121,2125,2129,2133,2137,2141,2145,2149,2153,2157,2161,2165,2169,2173,2177,2181,2185,2189,2193,2197,2201,2205,2209,2213,2217,2221,2225,2229,2233,2237,2241,2245,2249,2253,2257,2261,2265,2269,2273,2277,2281,2285,2289,2293,2297,2301,2305,2309,2313,2317,2321,2325,2329,2333,2337,2341,2345,2349,2353,2357,2361,2365,2369,2373,2377,2381,2385,2389,2393,2397,2401,2405,2409,2413,2417,2421,2425,2429,2433,2437,2441,2445,2449,2453,2457,2461,2465,2469,2473,2477,2481,2485,2489,2493,2497,2501,2505,2509,2513,2517,2521,2525,2529,2533,2537,2541,2545,2549,2553,2557,2561,2565,2569,2573,2577,2581,2585,2589,2593,2597,2602,2606,2610,2614,2618,2622,2626,2630,2634,2638,2642,2646,2650,2654,2658,2662,2666,2670,2674,2678,2682,2686,2690,2694,2698,2702,2706,2710,2714,2718,2722,2726,2730,2734,2738,2742,2746,2750,2754,2758,2762,2766,2770,2773,2777,2781,2785,2789,2793,2797,2801,2805,2809,2813,2817,2821,2825,2830,2834,2838,2842,2846,2850,2854,2858,2862,2866,2870,2875,2880,2885,2890,2895,2900,2904,2909,2914,2919,2924,2929,2934,2939,2944,2949,2954,2959,2964,2969,2974,2979,2984,2988,2992,2996,3000,3004,3008,3012,3016,3020,3024,3028,3032,3036,3040,3044,3048,3052,3056,3060,3064,3068,3072,3076,3080,3084,3088,3092,3096,3100,3104,3108,3112,3116,3120,3124,3128,3132,3136,3140,3144,3148,3152,3156,3160,3164,3168,3172,3176,3180,3184,3188,3192,3196,3200,3204,3208,3212,3216,3220,3224,3228,3232,3236,3240,3244,3248,3252,3256,3260,3264,3268,3272,3276,3280,3284,3288,3292,3296,3300,3304,3308,3312,3316,3320,3324,3328,3332,3336,3340,3344,3348,3352,3356,3360,3365,3369,3373,3377,3381,3385,3389,3393,3397,3401,3405,3409,3413,3417,3421,3425,3429,3433,3437,3441,3445,3449,3453,3457,3461,3465,3469,3473,3477,3481,3485,3489,3493,3497,3501,3505,3508,3511,3515,3519,3523,3527,3531,3535,3539,3543,3547,3551,3555,3559,3563,3567,3571,3575,3579,3583,3587,3591,3595,3599,3603,3607,3611,3615,3619,3623,3627,3631,3635,3639,3643,3647,3651,3655,3659,3663,3667,3671,3675,3679,3683,3687,3691,3695,3699,3703,3707,3711,3715,3719,3723,3727,3731,3735,3739,3743,3747,3751,3755,3759,3763,3767,3771,3775,3779,3783,3787,3791,3795,3799,3803,3807,3811,3815,3819,3823,3827,3831,3835,3839,3843,3847,3851,3855,3859,3862,3866,3870,3874,3878,3882,3886,3890,3894,3898,3902,3906,3910,3914,3918,3922,3926,3930,3934,3938,3942,3946,3950,3954,3958,3962,3966,3970,3974,3978,3982,3986,3990,3994,3998,4002,4006,4010,4014,4018,4022,4025,4029,4033,4037,4041,4045,4049,4053,4057,4061,4065,4069,4073,4077,4081,4085,4089,4093,4097,4101,4105,4109,4113,4117,4121,4125,4129,4133,4137,4141,4145,4149,4153,4157,4161,4165,4169,4173,4177,4181,4185,4189,4193,4197,4201,4205,4209,4213,4217,4221,4225,4229,4233,4238,4242,4246,4250,4254,4258,4262,4266,4270,4274,4278,4282,4286,4290,4294,4298,4302,4306,4310,4314,4318,4322,4326,4330,4334,4338,4342,4346,4350,4354,4358,4362,4366,4370,4374,4378,4382,4386,4390,4394,4398,4402,4406,4410,4414,4418,4422,4426,4430,4434,4438,4442,4446,4450,4454,4458,4462,4466,4470,4474,4478,4482,4486,4490,4494,4498,4502,4506,4510,4514,4518,4522,4526,4530,4534,4538,4542,4546,4550,4554,4558,4562,4566,4570,4574,4578,4582,4586,4590,4594,4598,4602,4606,4610,4614,4618,4622,4626,4630,4634,4638,4642,4646,4650,4654,4658,4662,4666,4670,4674,4678,4682,4686,4690,4694,4698,4702,4706,4710,4714,4718,4722,4726,4730,4734,4738,4742,4746,4750,4754,4758,4762,4766,4770,4774,4777,4781,4785],{"name":5,"type":328,"title":6,"description":329,"categories":330},"registry:ui","Accordion — базовый примитив RevueUI (Reka UI + shadcn-совместимый API).",[331],"primitives",{"name":15,"type":328,"title":16,"description":333,"categories":334},"Alert Dialog — базовый примитив RevueUI (Reka UI + shadcn-совместимый API).",[331],{"name":20,"type":328,"title":21,"description":336,"categories":337},"Aspect Ratio — базовый примитив RevueUI (Reka UI + shadcn-совместимый API).",[331],{"name":30,"type":328,"title":31,"description":339,"categories":340},"Avatar — базовый примитив RevueUI (Reka UI + shadcn-совместимый API).",[331],{"name":40,"type":328,"title":41,"description":342,"categories":343},"Breadcrumb — базовый примитив RevueUI (Reka UI + shadcn-совместимый API).",[331],{"name":45,"type":328,"title":46,"description":345,"categories":346},"Button — базовый примитив RevueUI (Reka UI + shadcn-совместимый API).",[331],{"name":50,"type":328,"title":51,"description":348,"categories":349},"Button Group — базовый примитив RevueUI (Reka UI + shadcn-совместимый API).",[331],{"name":55,"type":328,"title":56,"description":351,"categories":352},"Calendar — базовый примитив RevueUI (Reka UI + shadcn-совместимый API).",[331],{"name":60,"type":328,"title":61,"description":354,"categories":355},"Card — базовый примитив RevueUI (Reka UI + shadcn-совместимый API).",[331],{"name":65,"type":328,"title":66,"description":357,"categories":358},"Carousel — базовый примитив RevueUI (Reka UI + shadcn-совместимый API).",[331],{"name":69,"type":328,"title":70,"description":360,"categories":361},"Chart — базовый примитив RevueUI (Reka UI + shadcn-совместимый API).",[331],{"name":73,"type":328,"title":74,"description":363,"categories":364},"Checkbox — базовый примитив RevueUI (Reka UI + shadcn-совместимый API).",[331],{"name":78,"type":328,"title":79,"description":366,"categories":367},"Collapsible — базовый примитив RevueUI (Reka UI + shadcn-совместимый API).",[331],{"name":83,"type":328,"title":84,"description":369,"categories":370},"Combobox — базовый примитив RevueUI (Reka UI + shadcn-совместимый API).",[331],{"name":88,"type":328,"title":89,"description":372,"categories":373},"Command — базовый примитив RevueUI (Reka UI + shadcn-совместимый API).",[331],{"name":92,"type":328,"title":93,"description":375,"categories":376},"Context Menu — базовый примитив RevueUI (Reka UI + shadcn-совместимый API).",[331],{"name":106,"type":328,"title":107,"description":378,"categories":379},"Dialog — базовый примитив RevueUI (Reka UI + shadcn-совместимый API).",[331],{"name":110,"type":328,"title":111,"description":381,"categories":382},"Drawer — базовый примитив RevueUI (Reka UI + shadcn-совместимый API).",[331],{"name":115,"type":328,"title":116,"description":384,"categories":385},"Dropdown Menu — базовый примитив RevueUI (Reka UI + shadcn-совместимый API).",[331],{"name":119,"type":328,"title":120,"description":387,"categories":388},"Empty — базовый примитив RevueUI (Reka UI + shadcn-совместимый API).",[331],{"name":127,"type":328,"title":128,"description":390,"categories":391},"Field — базовый примитив RevueUI (Reka UI + shadcn-совместимый API).",[331],{"name":148,"type":328,"title":149,"description":393,"categories":394},"Hover Card — базовый примитив RevueUI (Reka UI + shadcn-совместимый API).",[331],{"name":162,"type":328,"title":163,"description":396,"categories":397},"Input — базовый примитив RevueUI (Reka UI + shadcn-совместимый API).",[331],{"name":167,"type":328,"title":168,"description":399,"categories":400},"Input Group — базовый примитив RevueUI (Reka UI + shadcn-совместимый API).",[331],{"name":172,"type":328,"title":173,"description":402,"categories":403},"Input Otp — базовый примитив RevueUI (Reka UI + shadcn-совместимый API).",[331],{"name":176,"type":328,"title":177,"description":405,"categories":406},"Item — базовый примитив RevueUI (Reka UI + shadcn-совместимый API).",[331],{"name":184,"type":328,"title":185,"description":408,"categories":409},"Kbd — базовый примитив RevueUI (Reka UI + shadcn-совместимый API).",[331],{"name":188,"type":328,"title":189,"description":411,"categories":412},"Label — базовый примитив RevueUI (Reka UI + shadcn-совместимый API).",[331],{"name":193,"type":328,"title":194,"description":414,"categories":415},"Menubar — базовый примитив RevueUI (Reka UI + shadcn-совместимый API).",[331],{"name":197,"type":328,"title":198,"description":417,"categories":418},"Native Select — базовый примитив RevueUI (Reka UI + shadcn-совместимый API).",[331],{"name":201,"type":328,"title":202,"description":420,"categories":421},"Navigation Menu — базовый примитив RevueUI (Reka UI + shadcn-совместимый API).",[331],{"name":209,"type":328,"title":210,"description":423,"categories":424},"Pagination — базовый примитив RevueUI (Reka UI + shadcn-совместимый API).",[331],{"name":217,"type":328,"title":218,"description":426,"categories":427},"Popover — базовый примитив RevueUI (Reka UI + shadcn-совместимый API).",[331],{"name":221,"type":328,"title":222,"description":429,"categories":430},"Progress — базовый примитив RevueUI (Reka UI + shadcn-совместимый API).",[331],{"name":225,"type":328,"title":226,"description":432,"categories":433},"Radio Group — базовый примитив RevueUI (Reka UI + shadcn-совместимый API).",[331],{"name":234,"type":328,"title":235,"description":435,"categories":436},"Resizable — базовый примитив RevueUI (Reka UI + shadcn-совместимый API).",[331],{"name":238,"type":328,"title":239,"description":438,"categories":439},"Scroll Area — базовый примитив RevueUI (Reka UI + shadcn-совместимый API).",[331],{"name":247,"type":328,"title":248,"description":441,"categories":442},"Select — базовый примитив RevueUI (Reka UI + shadcn-совместимый API).",[331],{"name":252,"type":328,"title":253,"description":444,"categories":445},"Separator — базовый примитив RevueUI (Reka UI + shadcn-совместимый API).",[331],{"name":256,"type":328,"title":257,"description":447,"categories":448},"Sheet — базовый примитив RevueUI (Reka UI + shadcn-совместимый API).",[331],{"name":260,"type":328,"title":261,"description":450,"categories":451},"Skeleton — базовый примитив RevueUI (Reka UI + shadcn-совместимый API).",[331],{"name":264,"type":328,"title":265,"description":453,"categories":454},"Slider — базовый примитив RevueUI (Reka UI + shadcn-совместимый API).",[331],{"name":277,"type":328,"title":278,"description":456,"categories":457},"Spinner — базовый примитив RevueUI (Reka UI + shadcn-совместимый API).",[331],{"name":285,"type":328,"title":286,"description":459,"categories":460},"Switch — базовый примитив RevueUI (Reka UI + shadcn-совместимый API).",[331],{"name":289,"type":328,"title":290,"description":462,"categories":463},"Table — базовый примитив RevueUI (Reka UI + shadcn-совместимый API).",[331],{"name":293,"type":328,"title":294,"description":465,"categories":466},"Tabs — базовый примитив RevueUI (Reka UI + shadcn-совместимый API).",[331],{"name":297,"type":328,"title":298,"description":468,"categories":469},"Textarea — базовый примитив RevueUI (Reka UI + shadcn-совместимый API).",[331],{"name":305,"type":328,"title":306,"description":471,"categories":472},"Toggle — базовый примитив RevueUI (Reka UI + shadcn-совместимый API).",[331],{"name":309,"type":328,"title":310,"description":474,"categories":475},"Toggle Group — базовый примитив RevueUI (Reka UI + shadcn-совместимый API).",[331],{"name":314,"type":328,"title":315,"description":477,"categories":478},"Tooltip — базовый примитив RevueUI (Reka UI + shadcn-совместимый API).",[331],{"name":10,"type":480,"title":11,"description":481,"categories":482},"registry:component","Alert — кастомный компонент, портированный из ReUI (keenthemes\u002Freui, MIT).",[483],"reui",{"name":25,"type":480,"title":26,"description":485,"categories":486},"Autocomplete — кастомный компонент, портированный из ReUI (keenthemes\u002Freui, MIT).",[483],{"name":35,"type":480,"title":36,"description":488,"categories":489},"Badge — кастомный компонент, портированный из ReUI (keenthemes\u002Freui, MIT).",[483],{"name":96,"type":480,"title":97,"description":491,"categories":492},"Data Grid — кастомный компонент, портированный из ReUI (keenthemes\u002Freui, MIT).",[483],{"name":101,"type":480,"title":102,"description":494,"categories":495},"Date Selector — кастомный компонент, портированный из ReUI (keenthemes\u002Freui, MIT).",[483],{"name":123,"type":480,"title":124,"description":497,"categories":498},"Event Calendar — кастомный компонент, портированный из ReUI (keenthemes\u002Freui, MIT).",[483],{"name":135,"type":480,"title":136,"description":500,"categories":501},"Filters — кастомный компонент, портированный из ReUI (keenthemes\u002Freui, MIT).",[483],{"name":139,"type":480,"title":140,"description":503,"categories":504},"Frame — кастомный компонент, портированный из ReUI (keenthemes\u002Freui, MIT).",[483],{"name":144,"type":480,"title":145,"description":506,"categories":507},"Gantt — кастомный компонент, портированный из ReUI (keenthemes\u002Freui, MIT).",[483],{"name":152,"type":480,"title":153,"description":509,"categories":510},"Icon Stack — кастомный компонент, портированный из ReUI (keenthemes\u002Freui, MIT).",[483],{"name":157,"type":480,"title":158,"description":512,"categories":513},"Icon Tile — кастомный компонент, портированный из ReUI (keenthemes\u002Freui, MIT).",[483],{"name":180,"type":480,"title":181,"description":515,"categories":516},"Kanban — кастомный компонент, портированный из ReUI (keenthemes\u002Freui, MIT).",[483],{"name":205,"type":480,"title":206,"description":518,"categories":519},"Number Field — кастомный компонент, портированный из ReUI (keenthemes\u002Freui, MIT).",[483],{"name":213,"type":480,"title":214,"description":521,"categories":522},"Phone Input — кастомный компонент, портированный из ReUI (keenthemes\u002Freui, MIT).",[483],{"name":229,"type":480,"title":230,"description":524,"categories":525},"Rating — кастомный компонент, портированный из ReUI (keenthemes\u002Freui, MIT).",[483],{"name":242,"type":480,"title":243,"description":527,"categories":528},"Scrollspy — кастомный компонент, портированный из ReUI (keenthemes\u002Freui, MIT).",[483],{"name":273,"type":480,"title":274,"description":530,"categories":531},"Sortable — кастомный компонент, портированный из ReUI (keenthemes\u002Freui, MIT).",[483],{"name":281,"type":480,"title":282,"description":533,"categories":534},"Stepper — кастомный компонент, портированный из ReUI (keenthemes\u002Freui, MIT).",[483],{"name":301,"type":480,"title":302,"description":536,"categories":537},"Timeline — кастомный компонент, портированный из ReUI (keenthemes\u002Freui, MIT).",[483],{"name":318,"type":480,"title":319,"description":539,"categories":540},"Tree — кастомный компонент, портированный из ReUI (keenthemes\u002Freui, MIT).",[483],{"name":542,"type":543,"title":544,"description":545,"categories":546},"utils","registry:lib","Utils","Утилита cn() (clsx + tailwind-merge) — используется практически всеми компонентами.",[547],"internals",{"name":549,"type":543,"title":550,"description":551,"categories":552},"dnd","Drag and Drop helpers","Общие помощники Pragmatic Drag and Drop (клавиатурный drag, reorder, прозрачный пиксель) — см. ADR-002.",[547],{"name":554,"type":555,"title":556,"description":557,"categories":558},"use-copy-to-clipboard","registry:hook","Use copy To Clipboard","Composable use-copy-to-clipboard() — VueUse-порт одноимённого хука ReUI.",[547],{"name":560,"type":555,"title":561,"description":562,"categories":563},"use-file-upload","Use file Upload","Composable use-file-upload() — VueUse-порт одноимённого хука ReUI.",[547],{"name":565,"type":555,"title":566,"description":567,"categories":568},"use-scroll-position","Use scroll Position","Composable use-scroll-position() — VueUse-порт одноимённого хука ReUI.",[547],{"name":570,"type":555,"title":571,"description":572,"categories":573},"use-slider-input","Use slider Input","Composable use-slider-input() — VueUse-порт одноимённого хука ReUI.",[547],{"name":575,"type":576,"title":577,"description":577,"categories":578},"c-accordion-1","registry:block","A basic single-expand accordion",[5],{"name":580,"type":576,"title":581,"description":581,"categories":582},"c-accordion-2","Accordion with plus\u002Fminus indicators",[5],{"name":584,"type":576,"title":585,"description":585,"categories":586},"c-accordion-3","Accordion with borders and rounded corners",[5],{"name":588,"type":576,"title":589,"description":589,"categories":590},"c-accordion-4","Accordion embedded within a Card",[5],{"name":592,"type":576,"title":593,"description":593,"categories":594},"c-accordion-5","Accordion with disabled items and highlighted state",[5],{"name":596,"type":576,"title":597,"description":597,"categories":598},"c-accordion-6","Advanced accordion with custom icons and badges",[5],{"name":600,"type":576,"title":601,"description":601,"categories":602},"c-accordion-7","Accordion items integrated within Frame and FramePanel",[5],{"name":604,"type":576,"title":605,"description":605,"categories":606},"c-accordion-8","Nested accordion example with bordered items",[5],{"name":608,"type":576,"title":609,"description":609,"categories":610},"c-accordion-9","User list accordion with avatars and role indicators",[5],{"name":612,"type":576,"title":613,"description":613,"categories":614},"c-accordion-10","Accordion with rotating arrow indicator",[5],{"name":616,"type":576,"title":617,"description":617,"categories":618},"c-accordion-11","Accordion pattern for onboarding or setup steps with icons, badges, and QR codes",[5],{"name":620,"type":576,"title":621,"description":621,"categories":622},"c-alert-dialog-1","Basic alert dialog with title, description, and action buttons",[15],{"name":624,"type":576,"title":625,"description":625,"categories":626},"c-alert-dialog-2","Small size alert dialog for quick confirmations",[15],{"name":628,"type":576,"title":629,"description":629,"categories":630},"c-alert-dialog-3","Alert dialog with icon",[15],{"name":632,"type":576,"title":633,"description":633,"categories":634},"c-alert-dialog-4","Small size alert dialog with media (icon)",[15],{"name":636,"type":576,"title":637,"description":637,"categories":638},"c-alert-dialog-5","Destructive alert dialog for irreversible actions like deletion",[15],{"name":640,"type":576,"title":641,"description":641,"categories":642},"c-alert-dialog-6","Alert dialog nested within a standard Dialog component",[15],{"name":644,"type":576,"title":645,"description":645,"categories":646},"c-alert-dialog-7","Task success confirmation",[15],{"name":648,"type":576,"title":649,"description":649,"categories":650},"c-alert-dialog-8","Account deactivation confirmation",[15],{"name":652,"type":576,"title":653,"description":653,"categories":654},"c-alert-dialog-9","Warning for unsaved changes when navigating away",[15],{"name":656,"type":576,"title":657,"description":657,"categories":658},"c-alert-dialog-10","Confirmation for successful e-ticket registration",[15],{"name":660,"type":576,"title":661,"description":661,"categories":662},"c-alert-dialog-11","System update notification requiring application restart",[15],{"name":664,"type":576,"title":665,"description":665,"categories":666},"c-alert-dialog-12","Billing warning for expired subscriptions or failed payments",[15],{"name":668,"type":576,"title":669,"description":669,"categories":670},"c-alert-dialog-13","Logout confirmation dialog",[15],{"name":672,"type":576,"title":673,"description":673,"categories":674},"c-alert-dialog-14","Advanced security audit confirmation",[15],{"name":676,"type":576,"title":677,"description":677,"categories":678},"c-alert-1","Basic alert",[10],{"name":680,"type":576,"title":681,"description":681,"categories":682},"c-alert-2","Alert with icon",[10],{"name":684,"type":576,"title":685,"description":685,"categories":686},"c-alert-3","Alert with icon and action buttons",[10],{"name":688,"type":576,"title":689,"description":689,"categories":690},"c-alert-4","Destructive alert with icon and action buttons",[10],{"name":692,"type":576,"title":693,"description":693,"categories":694},"c-alert-5","Info alert with icon and action buttons",[10],{"name":696,"type":576,"title":697,"description":697,"categories":698},"c-alert-6","Success alert",[10],{"name":700,"type":576,"title":701,"description":701,"categories":702},"c-alert-7","Warning alert",[10],{"name":704,"type":576,"title":705,"description":705,"categories":706},"c-alert-8","Error alert",[10],{"name":708,"type":576,"title":709,"description":709,"categories":710},"c-alert-9","Invert alert",[10],{"name":712,"type":576,"title":685,"description":685,"categories":713},"c-alert-10",[10],{"name":715,"type":576,"title":716,"description":716,"categories":717},"c-alert-11","Alert with description and action buttons",[10],{"name":719,"type":576,"title":720,"description":720,"categories":721},"c-alert-12","Alert integrated within a Frame with reset borders",[10],{"name":723,"type":576,"title":724,"description":724,"categories":725},"c-alert-13","Stacked alerts within a Frame",[10],{"name":727,"type":576,"title":728,"description":728,"categories":729},"c-alert-14","Alert with actions integrated within a Frame",[10],{"name":731,"type":576,"title":732,"description":732,"categories":733},"c-alert-15","Inverted alert variant",[10],{"name":735,"type":576,"title":736,"description":736,"categories":737},"c-alert-16","Urgent billing notice",[10],{"name":739,"type":576,"title":740,"description":740,"categories":741},"c-alert-17","Service status summary stacked within a frame",[10],{"name":743,"type":576,"title":744,"description":744,"categories":745},"c-alert-18","Feature discovery alert",[10],{"name":747,"type":576,"title":748,"description":748,"categories":749},"c-alert-19","User message notification alert",[10],{"name":751,"type":576,"title":748,"description":748,"categories":752},"c-alert-20",[10],{"name":754,"type":576,"title":755,"description":755,"categories":756},"c-aspect-ratio-1","16:9 widescreen aspect ratio",[20],{"name":758,"type":576,"title":759,"description":759,"categories":760},"c-aspect-ratio-2","4:3 standard aspect ratio",[20],{"name":762,"type":576,"title":763,"description":763,"categories":764},"c-aspect-ratio-3","1:1 square aspect ratio",[20],{"name":766,"type":576,"title":767,"description":767,"categories":768},"c-aspect-ratio-4","21:9 ultrawide aspect ratio",[20],{"name":770,"type":576,"title":771,"description":771,"categories":772},"c-aspect-ratio-5","9:16 portrait aspect ratio",[20],{"name":774,"type":576,"title":775,"description":775,"categories":776},"c-aspect-ratio-6","3:2 classic photography aspect ratio",[20],{"name":778,"type":576,"title":779,"description":779,"categories":780},"c-aspect-ratio-7","4:5 social media portrait aspect ratio",[20],{"name":782,"type":576,"title":783,"description":783,"categories":784},"c-aspect-ratio-8","16:10 computer monitor aspect ratio",[20],{"name":786,"type":576,"title":787,"description":787,"categories":788},"c-autocomplete-1","Basic autocomplete control",[25],{"name":790,"type":576,"title":791,"description":791,"categories":792},"c-autocomplete-2","Disabled state",[25],{"name":794,"type":576,"title":795,"description":795,"categories":796},"c-autocomplete-3","Auto highlight option as you type",[25],{"name":798,"type":576,"title":799,"description":799,"categories":800},"c-autocomplete-4","Control with label",[25],{"name":802,"type":576,"title":803,"description":803,"categories":804},"c-autocomplete-5","With clear button",[25],{"name":806,"type":576,"title":807,"description":807,"categories":808},"c-autocomplete-6","With trigger button",[25],{"name":810,"type":576,"title":811,"description":811,"categories":812},"c-autocomplete-7","With clear and trigger buttons",[25],{"name":814,"type":576,"title":815,"description":815,"categories":816},"c-autocomplete-8","With groups and labels",[25],{"name":818,"type":576,"title":819,"description":819,"categories":820},"c-autocomplete-9","With search results",[25],{"name":822,"type":576,"title":823,"description":823,"categories":824},"c-autocomplete-10","Small autocomplete",[25],{"name":826,"type":576,"title":827,"description":827,"categories":828},"c-autocomplete-11","Large autocomplete",[25],{"name":830,"type":576,"title":831,"description":831,"categories":832},"c-autocomplete-12","Form integration",[25],{"name":834,"type":576,"title":835,"description":835,"categories":836},"c-avatar-1","Basic avatar",[30],{"name":838,"type":576,"title":839,"description":839,"categories":840},"c-avatar-2","Avatar with fallback",[30],{"name":842,"type":576,"title":843,"description":843,"categories":844},"c-avatar-3","Avatars with different sizes",[30],{"name":846,"type":576,"title":847,"description":847,"categories":848},"c-avatar-4","Avatars with different border radiuses",[30],{"name":850,"type":576,"title":851,"description":851,"categories":852},"c-avatar-5","Avatars with custom sizes",[30],{"name":854,"type":576,"title":855,"description":855,"categories":856},"c-avatar-6","Avatar with an online status badge",[30],{"name":858,"type":576,"title":859,"description":859,"categories":860},"c-avatar-7","Avatar with badge icon",[30],{"name":862,"type":576,"title":863,"description":863,"categories":864},"c-avatar-8","Avatar with different badge positions",[30],{"name":866,"type":576,"title":863,"description":863,"categories":867},"c-avatar-9",[30],{"name":869,"type":576,"title":870,"description":870,"categories":871},"c-avatar-10","Basic avatar group",[30],{"name":873,"type":576,"title":874,"description":874,"categories":875},"c-avatar-11","Avatar group with numerical count",[30],{"name":877,"type":576,"title":878,"description":878,"categories":879},"c-avatar-12","Avatar group with an icon count",[30],{"name":881,"type":576,"title":882,"description":882,"categories":883},"c-avatar-13","Avatar with a distinct border ring",[30],{"name":885,"type":576,"title":886,"description":886,"categories":887},"c-avatar-14","Avatar with light background color fallback",[30],{"name":889,"type":576,"title":890,"description":890,"categories":891},"c-avatar-15","Avatar with a solid background color fallback",[30],{"name":893,"type":576,"title":894,"description":894,"categories":895},"c-avatar-16","Avatar with user details and badge",[30],{"name":897,"type":576,"title":898,"description":898,"categories":899},"c-avatar-17","Avatar with grayscale image filter",[30],{"name":901,"type":576,"title":902,"description":902,"categories":903},"c-avatar-18","Avatar group with online status on one",[30],{"name":905,"type":576,"title":906,"description":906,"categories":907},"c-avatar-19","Avatar inside small outline button",[30],{"name":909,"type":576,"title":910,"description":910,"categories":911},"c-avatar-20","Avatar social proof with text label",[30],{"name":913,"type":576,"title":914,"description":914,"categories":915},"c-avatar-21","Compact social proof with initials",[30],{"name":917,"type":576,"title":918,"description":918,"categories":919},"c-avatar-22","Avatar group with hover tooltips and lift effect",[30],{"name":921,"type":576,"title":922,"description":922,"categories":923},"c-avatar-23","Avatar group with hover effect",[30],{"name":925,"type":576,"title":926,"description":926,"categories":927},"c-avatar-24","Avatar group with hover effect and tooltips",[30],{"name":929,"type":576,"title":874,"description":874,"categories":930},"c-avatar-25",[30],{"name":932,"type":576,"title":933,"description":933,"categories":934},"c-avatar-26","Pilled small outline button with avatar",[30],{"name":936,"type":576,"title":937,"description":937,"categories":938},"c-avatar-27","Pilled small button with avatar",[30],{"name":940,"type":576,"title":941,"description":941,"categories":942},"c-avatar-28","Avatar inside an empty state example",[30],{"name":944,"type":576,"title":945,"description":945,"categories":946},"c-avatar-29","Avatar group with icon count and button",[30],{"name":948,"type":576,"title":949,"description":949,"categories":950},"c-avatar-30","Avatar with loading state demonstration",[30],{"name":952,"type":576,"title":953,"description":953,"categories":954},"c-avatar-31","Avatar with custom badge",[30],{"name":956,"type":576,"title":957,"description":957,"categories":958},"c-avatar-32","Avatar with ring animation",[30],{"name":960,"type":576,"title":961,"description":961,"categories":962},"c-avatar-33","Avatar with gradient animated ring",[30],{"name":964,"type":576,"title":965,"description":965,"categories":966},"c-avatar-34","Avatar with multiple badges",[30],{"name":968,"type":576,"title":969,"description":969,"categories":970},"c-avatar-35","Compact avatar dropdown menu",[30],{"name":972,"type":576,"title":973,"description":973,"categories":974},"c-badge-1","Default badge",[35],{"name":976,"type":576,"title":977,"description":977,"categories":978},"c-badge-2","Secondary badge",[35],{"name":980,"type":576,"title":981,"description":981,"categories":982},"c-badge-3","Destructive badge",[35],{"name":984,"type":576,"title":985,"description":985,"categories":986},"c-badge-4","Success badge",[35],{"name":988,"type":576,"title":989,"description":989,"categories":990},"c-badge-5","Info badge",[35],{"name":992,"type":576,"title":993,"description":993,"categories":994},"c-badge-6","Warning badge",[35],{"name":996,"type":576,"title":997,"description":997,"categories":998},"c-badge-7","Outline badge",[35],{"name":1000,"type":576,"title":1001,"description":1001,"categories":1002},"c-badge-8","Primary outline badge",[35],{"name":1004,"type":576,"title":1005,"description":1005,"categories":1006},"c-badge-9","Destructive outline badge",[35],{"name":1008,"type":576,"title":1009,"description":1009,"categories":1010},"c-badge-10","Info outline badge",[35],{"name":1012,"type":576,"title":1013,"description":1013,"categories":1014},"c-badge-11","Success outline badge",[35],{"name":1016,"type":576,"title":1017,"description":1017,"categories":1018},"c-badge-12","Warning outline badge",[35],{"name":1020,"type":576,"title":1021,"description":1021,"categories":1022},"c-badge-13","Primary light badge",[35],{"name":1024,"type":576,"title":1025,"description":1025,"categories":1026},"c-badge-14","Destructive light badge",[35],{"name":1028,"type":576,"title":1029,"description":1029,"categories":1030},"c-badge-15","Success light badge",[35],{"name":1032,"type":576,"title":1033,"description":1033,"categories":1034},"c-badge-16","Info light badge",[35],{"name":1036,"type":576,"title":1037,"description":1037,"categories":1038},"c-badge-17","Warning light badge",[35],{"name":1040,"type":576,"title":1041,"description":1041,"categories":1042},"c-badge-18","Badge size variations",[35],{"name":1044,"type":576,"title":1045,"description":1045,"categories":1046},"c-badge-19","Badge with full radius (radius=\"full\")",[35],{"name":1048,"type":576,"title":1049,"description":1049,"categories":1050},"c-badge-20","Badge with an icon",[35],{"name":1052,"type":576,"title":1053,"description":1053,"categories":1054},"c-badge-21","Badge with a dismiss button",[35],{"name":1056,"type":576,"title":1057,"description":1057,"categories":1058},"c-badge-22","Badge with a status dot",[35],{"name":1060,"type":576,"title":1061,"description":1061,"categories":1062},"c-badge-23","Badge rendered as a link",[35],{"name":1064,"type":576,"title":1065,"description":1065,"categories":1066},"c-badge-24","Badge with avatar",[35],{"name":1068,"type":576,"title":1069,"description":1069,"categories":1070},"c-badge-25","Badge with flag image",[35],{"name":1072,"type":576,"title":1073,"description":1073,"categories":1074},"c-breadcrumb-1","Basic breadcrumb",[40],{"name":1076,"type":576,"title":1077,"description":1077,"categories":1078},"c-breadcrumb-2","Breadcrumb with dropdown menu",[40],{"name":1080,"type":576,"title":1081,"description":1081,"categories":1082},"c-breadcrumb-3","Breadcrumb with Next.js link",[40],{"name":1084,"type":576,"title":1085,"description":1085,"categories":1086},"c-breadcrumb-4","Breadcrumb with a custom slash separator",[40],{"name":1088,"type":576,"title":1089,"description":1089,"categories":1090},"c-breadcrumb-5","Breadcrumb with icons for each item",[40],{"name":1092,"type":576,"title":1093,"description":1093,"categories":1094},"c-breadcrumb-6","Breadcrumb with ellipsis for long paths",[40],{"name":1096,"type":576,"title":1097,"description":1097,"categories":1098},"c-breadcrumb-7","Breadcrumb items with avatars",[40],{"name":1100,"type":576,"title":1101,"description":1101,"categories":1102},"c-breadcrumb-8","Breadcrumb with double chevron separators",[40],{"name":1104,"type":576,"title":1105,"description":1105,"categories":1106},"c-breadcrumb-9","Breadcrumb inside card",[40],{"name":1108,"type":576,"title":1109,"description":1109,"categories":1110},"c-breadcrumb-10","Pill-style breadcrumb inside frame",[40],{"name":1112,"type":576,"title":1113,"description":1113,"categories":1114},"c-breadcrumb-11","Breadcrumb items containing badge with count",[40],{"name":1116,"type":576,"title":1117,"description":1117,"categories":1118},"c-breadcrumb-12","Breadcrumb starting with home icon item",[40],{"name":1120,"type":576,"title":1121,"description":1121,"categories":1122},"c-breadcrumb-13","Breadcrumb with a custom separator",[40],{"name":1124,"type":576,"title":1125,"description":1125,"categories":1126},"c-breadcrumb-14","Button-style breadcrumb",[40],{"name":1128,"type":576,"title":1129,"description":1129,"categories":1130},"c-breadcrumb-15","Breadcrumb with project, user and document info",[40],{"name":1132,"type":576,"title":1133,"description":1133,"categories":1134},"c-button-group-1","Basic button group with two buttons",[50],{"name":1136,"type":576,"title":1137,"description":1137,"categories":1138},"c-button-group-2","Button group with button followed by input",[50],{"name":1140,"type":576,"title":1141,"description":1141,"categories":1142},"c-button-group-3","Button group with buttons and badge",[50],{"name":1144,"type":576,"title":1145,"description":1145,"categories":1146},"c-button-group-4","Button group with dropdown menu",[50],{"name":1148,"type":576,"title":1149,"description":1149,"categories":1150},"c-button-group-5","Team activity and collaboration group",[50],{"name":1152,"type":576,"title":1153,"description":1153,"categories":1154},"c-button-group-6","Button group using icons for visual actions",[50],{"name":1156,"type":576,"title":1157,"description":1157,"categories":1158},"c-button-group-7","Dropdown menu with status indicator",[50],{"name":1160,"type":576,"title":1161,"description":1161,"categories":1162},"c-button-group-8","Button group styled as a like button with a counter",[50],{"name":1164,"type":576,"title":1165,"description":1165,"categories":1166},"c-button-group-9","Nested button groups for complex action sets",[50],{"name":1168,"type":576,"title":1169,"description":1169,"categories":1170},"c-button-group-10","Button group used for standard pagination",[50],{"name":1172,"type":576,"title":1173,"description":1173,"categories":1174},"c-button-group-11","Pagination split into page numbers and navigation arrows",[50],{"name":1176,"type":576,"title":1177,"description":1177,"categories":1178},"c-button-group-12","Button group for editor text alignment controls",[50],{"name":1180,"type":576,"title":1181,"description":1181,"categories":1182},"c-button-group-13","Vertical button group orientation",[50],{"name":1184,"type":576,"title":1185,"description":1185,"categories":1186},"c-button-group-14","Nested vertical button group for complex tool palettes",[50],{"name":1188,"type":576,"title":1189,"description":1189,"categories":1190},"c-button-group-15","Star button group with count and dropdown",[50],{"name":1192,"type":576,"title":1193,"description":1193,"categories":1194},"c-button-group-16","Search input with filter menu and clear button",[50],{"name":1196,"type":576,"title":1197,"description":1197,"categories":1198},"c-button-group-17","Button group with comprehensive action menu",[50],{"name":1200,"type":576,"title":1201,"description":1201,"categories":1202},"c-button-group-18","Advanced git toolbar with branch selection and split commit button",[50],{"name":1204,"type":576,"title":1205,"description":1205,"categories":1206},"c-button-group-19","Button group with dropdown menu and input group",[50],{"name":1208,"type":576,"title":1209,"description":1209,"categories":1210},"c-button-group-20","Button group with input group and button",[50],{"name":1212,"type":576,"title":1205,"description":1205,"categories":1213},"c-button-group-21",[50],{"name":1215,"type":576,"title":1216,"description":1216,"categories":1217},"c-button-group-22","Basic button group with disabled button",[50],{"name":1219,"type":576,"title":1220,"description":1220,"categories":1221},"c-button-group-23","Button group buttons with separators",[50],{"name":1223,"type":576,"title":1224,"description":1224,"categories":1225},"c-button-group-24","View switcher for dashboard layouts",[50],{"name":1227,"type":576,"title":1228,"description":1228,"categories":1229},"c-button-group-25","Rating scale using numerical buttons",[50],{"name":1231,"type":576,"title":1232,"description":1232,"categories":1233},"c-button-group-26","Button group with user selection select",[50],{"name":1235,"type":576,"title":1236,"description":1236,"categories":1237},"c-button-group-27","Button group with select status addon",[50],{"name":1239,"type":576,"title":1240,"description":1240,"categories":1241},"c-button-group-28","Volume controller with plus\u002Fminus actions",[50],{"name":1243,"type":576,"title":1244,"description":1244,"categories":1245},"c-button-group-29","Button group with follower count",[50],{"name":1247,"type":576,"title":1248,"description":1248,"categories":1249},"c-button-group-30","Active state button group with count",[50],{"name":1251,"type":576,"title":1252,"description":1252,"categories":1253},"c-button-group-31","Horizontal button group with primary and secondary labels",[50],{"name":1255,"type":576,"title":1256,"description":1256,"categories":1257},"c-button-group-32","Button group with dropdown menu and view toggle",[50],{"name":1259,"type":576,"title":1260,"description":1260,"categories":1261},"c-button-group-33","Button group with select controls",[50],{"name":1263,"type":576,"title":1264,"description":1264,"categories":1265},"c-button-group-34","Segmented control with dropdown inside button group",[50],{"name":1267,"type":576,"title":1268,"description":1268,"categories":1269},"c-button-group-35","Pagination button group",[50],{"name":1271,"type":576,"title":1272,"description":1272,"categories":1273},"c-button-group-36","View switcher button group",[50],{"name":1275,"type":576,"title":1276,"description":1276,"categories":1277},"c-button-group-37","Zoom controls button group",[50],{"name":1279,"type":576,"title":1280,"description":1280,"categories":1281},"c-button-group-38","Media player controls button group",[50],{"name":1283,"type":576,"title":1284,"description":1284,"categories":1285},"c-button-group-39","Split button with dropdown trigger",[50],{"name":1287,"type":576,"title":1288,"description":1288,"categories":1289},"c-button-group-40","Vertical button group",[50],{"name":1291,"type":576,"title":1292,"description":1292,"categories":1293},"c-button-group-41","Model selector with tier badges",[50],{"name":1295,"type":576,"title":1296,"description":1296,"categories":1297},"c-button-group-42","Segmented order type selector",[50],{"name":1299,"type":576,"title":1300,"description":1300,"categories":1301},"c-button-group-43","Buy sell hold toggle group",[50],{"name":1303,"type":576,"title":1304,"description":1304,"categories":1305},"c-button-group-44","Pipeline stage mover with dropdown",[50],{"name":1307,"type":576,"title":1308,"description":1308,"categories":1309},"c-button-group-45","Environment switcher with indicator",[50],{"name":1311,"type":576,"title":1312,"description":1312,"categories":1313},"c-button-group-46","Compact time interval selector",[50],{"name":1315,"type":576,"title":1316,"description":1316,"categories":1317},"c-button-group-47","Review actions split button",[50],{"name":1319,"type":576,"title":1320,"description":1320,"categories":1321},"c-button-group-48","Toolbar filter with export and view toggle",[50],{"name":1323,"type":576,"title":1324,"description":1324,"categories":1325},"c-button-group-49","Vertical nav links with icons",[50],{"name":1327,"type":576,"title":1328,"description":1328,"categories":1329},"c-button-group-50","Toolbar filter sort and more",[50],{"name":1331,"type":576,"title":1332,"description":1332,"categories":1333},"c-button-group-51","New button with create dropdown",[50],{"name":1335,"type":576,"title":1336,"description":1336,"categories":1337},"c-button-group-52","Refresh share and export dropdown",[50],{"name":1339,"type":576,"title":1340,"description":1340,"categories":1341},"c-button-group-53","Publish controls with split button and overflow",[50],{"name":1343,"type":576,"title":1344,"description":1344,"categories":1345},"c-button-group-54","API service environment and version selectors",[50],{"name":1347,"type":576,"title":1348,"description":1348,"categories":1349},"c-button-group-55","Payout release controls with amount context",[50],{"name":1351,"type":576,"title":1352,"description":1352,"categories":1353},"c-button-group-56","Attachment button group with download action",[50],{"name":1355,"type":576,"title":1356,"description":1356,"categories":1357},"c-button-group-57","Agent mode selector with credit cap dropdown",[50],{"name":1359,"type":576,"title":1360,"description":1360,"categories":1361},"c-button-1","Default button",[45],{"name":1363,"type":576,"title":1364,"description":1364,"categories":1365},"c-button-2","Secondary button",[45],{"name":1367,"type":576,"title":1368,"description":1368,"categories":1369},"c-button-3","Outline button",[45],{"name":1371,"type":576,"title":1372,"description":1372,"categories":1373},"c-button-4","Ghost button",[45],{"name":1375,"type":576,"title":1376,"description":1376,"categories":1377},"c-button-5","Destructive button",[45],{"name":1379,"type":576,"title":1380,"description":1380,"categories":1381},"c-button-6","Link button variant",[45],{"name":1383,"type":576,"title":1384,"description":1384,"categories":1385},"c-button-7","Extra small button",[45],{"name":1387,"type":576,"title":1388,"description":1388,"categories":1389},"c-button-8","Small button",[45],{"name":1391,"type":576,"title":1392,"description":1392,"categories":1393},"c-button-9","Large button",[45],{"name":1395,"type":576,"title":1396,"description":1396,"categories":1397},"c-button-10","Disabled button",[45],{"name":1399,"type":576,"title":1400,"description":1400,"categories":1401},"c-button-11","Icon button",[45],{"name":1403,"type":576,"title":1404,"description":1404,"categories":1405},"c-button-12","Button with an icon on the right",[45],{"name":1407,"type":576,"title":1408,"description":1408,"categories":1409},"c-button-13","Button in a loading state",[45],{"name":1411,"type":576,"title":1412,"description":1412,"categories":1413},"c-button-14","Button with an icon on the left",[45],{"name":1415,"type":576,"title":1416,"description":1416,"categories":1417},"c-button-15","Button with an invalid state highlight",[45],{"name":1419,"type":576,"title":1420,"description":1420,"categories":1421},"c-button-16","Secondary button with an icon on the left",[45],{"name":1423,"type":576,"title":1424,"description":1424,"categories":1425},"c-button-17","Secondary button with an icon on the right",[45],{"name":1427,"type":576,"title":1428,"description":1428,"categories":1429},"c-button-18","Outline button with an icon on the left",[45],{"name":1431,"type":576,"title":1432,"description":1432,"categories":1433},"c-button-19","Outline button with an icon on the right",[45],{"name":1435,"type":576,"title":1436,"description":1436,"categories":1437},"c-button-20","Ghost button with an icon on the left",[45],{"name":1439,"type":576,"title":1440,"description":1440,"categories":1441},"c-button-21","Ghost button with an icon on the right",[45],{"name":1443,"type":576,"title":1444,"description":1444,"categories":1445},"c-button-22","Destructive button with an icon on the left",[45],{"name":1447,"type":576,"title":1448,"description":1448,"categories":1449},"c-button-23","Destructive button with an icon on the right",[45],{"name":1451,"type":576,"title":1452,"description":1452,"categories":1453},"c-button-24","Extra small icon button",[45],{"name":1455,"type":576,"title":1456,"description":1456,"categories":1457},"c-button-25","Small icon button",[45],{"name":1459,"type":576,"title":1460,"description":1460,"categories":1461},"c-button-26","Large (lg) icon-only button",[45],{"name":1463,"type":576,"title":1464,"description":1464,"categories":1465},"c-button-27","Button rendered as a Next.js Link",[45],{"name":1467,"type":576,"title":1468,"description":1468,"categories":1469},"c-button-28","Link button with an icon on the left",[45],{"name":1471,"type":576,"title":1472,"description":1472,"categories":1473},"c-button-29","Link button with an icon on the right",[45],{"name":1475,"type":576,"title":1476,"description":1476,"categories":1477},"c-button-30","Large button with an icon on the left",[45],{"name":1479,"type":576,"title":1480,"description":1480,"categories":1481},"c-button-31","Large button with an icon on the right",[45],{"name":1483,"type":576,"title":1484,"description":1484,"categories":1485},"c-button-32","Outline button with an invalid state",[45],{"name":1487,"type":576,"title":1488,"description":1488,"categories":1489},"c-button-33","Ghost button with an invalid state",[45],{"name":1491,"type":576,"title":1412,"description":1412,"categories":1492},"c-button-34",[45],{"name":1494,"type":576,"title":1495,"description":1495,"categories":1496},"c-button-35","Ghost button with more\u002Fless toggle",[45],{"name":1498,"type":576,"title":1499,"description":1499,"categories":1500},"c-button-36","Social login buttons with Remix Icons",[45],{"name":1502,"type":576,"title":1503,"description":1503,"categories":1504},"c-button-37","Outline like button with count",[45],{"name":1506,"type":576,"title":1507,"description":1507,"categories":1508},"c-button-38","Star button with count",[45],{"name":1510,"type":576,"title":1511,"description":1511,"categories":1512},"c-button-39","Button with an unread badge",[45],{"name":1514,"type":576,"title":1515,"description":1515,"categories":1516},"c-button-40","Button with icon, label, and shortcut keys",[45],{"name":1518,"type":576,"title":1519,"description":1519,"categories":1520},"c-button-41","Copy button with feedback",[45],{"name":1522,"type":576,"title":1523,"description":1523,"categories":1524},"c-button-42","Icon-only copy button with feedback",[45],{"name":1526,"type":576,"title":1527,"description":1527,"categories":1528},"c-button-43","Animated hamburger \u002F close toggle button",[45],{"name":1530,"type":576,"title":1531,"description":1531,"categories":1532},"c-button-44","Async action button with loading and success states",[45],{"name":1534,"type":576,"title":1535,"description":1535,"categories":1536},"c-button-45","Outline button with inline count badge",[45],{"name":1538,"type":576,"title":1539,"description":1539,"categories":1540},"c-button-46","Button with status dot badge",[45],{"name":1542,"type":576,"title":1543,"description":1543,"categories":1544},"c-button-47","Ghost button with a status badge",[45],{"name":1546,"type":576,"title":1547,"description":1547,"categories":1548},"c-button-48","Icon button with notification badge",[45],{"name":1550,"type":576,"title":1551,"description":1551,"categories":1552},"c-button-49","Go back link button",[45],{"name":1554,"type":576,"title":1555,"description":1555,"categories":1556},"c-button-50","Go back link button with animation",[45],{"name":1558,"type":576,"title":1559,"description":1559,"categories":1560},"c-button-51","Expanding button from icon to label",[45],{"name":1562,"type":576,"title":1563,"description":1563,"categories":1564},"c-button-52","Multi-line button with icon, title & description",[45],{"name":1566,"type":576,"title":1567,"description":1567,"categories":1568},"c-button-53","Sliding Icon Button (Hover reveal)",[45],{"name":1570,"type":576,"title":1571,"description":1571,"categories":1572},"c-button-54","Shortcut Tooltip Button (Kbd hints)",[45],{"name":1574,"type":576,"title":1575,"description":1575,"categories":1576},"c-button-55","Button with avatar",[45],{"name":1578,"type":576,"title":1579,"description":1579,"categories":1580},"c-button-56","Outline button with avatar",[45],{"name":1582,"type":576,"title":1583,"description":1583,"categories":1584},"c-button-57","Small button with avatar",[45],{"name":1586,"type":576,"title":1587,"description":1587,"categories":1588},"c-button-58","Small outline button with avatar",[45],{"name":1590,"type":576,"title":1591,"description":1591,"categories":1592},"c-button-59","Social login buttons with Remix icons",[45],{"name":1594,"type":576,"title":1595,"description":1595,"categories":1596},"c-button-60","Social icon-only buttons with Remix icons",[45],{"name":1598,"type":576,"title":1599,"description":1599,"categories":1600},"c-button-61","Theme toggle button with animated moon and sun icons",[45],{"name":1602,"type":576,"title":1603,"description":1603,"categories":1604},"c-calendar-1","Basic calendar",[55],{"name":1606,"type":576,"title":1607,"description":1607,"categories":1608},"c-calendar-2","Range calendar",[55],{"name":1610,"type":576,"title":1611,"description":1611,"categories":1612},"c-calendar-3","Disabled dates",[55],{"name":1614,"type":576,"title":1615,"description":1615,"categories":1616},"c-calendar-4","Multiple day selection",[55],{"name":1618,"type":576,"title":1619,"description":1619,"categories":1620},"c-calendar-5","Custom select day style",[55],{"name":1622,"type":576,"title":1623,"description":1623,"categories":1624},"c-calendar-6","Custom select range style",[55],{"name":1626,"type":576,"title":1627,"description":1627,"categories":1628},"c-calendar-7","Right navigation",[55],{"name":1630,"type":576,"title":1631,"description":1631,"categories":1632},"c-calendar-8","Month and year selection",[55],{"name":1634,"type":576,"title":1635,"description":1635,"categories":1636},"c-calendar-9","Year select with navigation",[55],{"name":1638,"type":576,"title":1639,"description":1639,"categories":1640},"c-calendar-10","Display week numbers",[55],{"name":1642,"type":576,"title":1643,"description":1643,"categories":1644},"c-calendar-11","Current month button",[55],{"name":1646,"type":576,"title":1647,"description":1647,"categories":1648},"c-calendar-12","Today button",[55],{"name":1650,"type":576,"title":1651,"description":1651,"categories":1652},"c-calendar-13","Custom navigation with year view",[55],{"name":1654,"type":576,"title":1655,"description":1655,"categories":1656},"c-calendar-14","Preset time selection",[55],{"name":1658,"type":576,"title":1659,"description":1659,"categories":1660},"c-calendar-15","Calendar with presets",[55],{"name":1662,"type":576,"title":1663,"description":1663,"categories":1664},"c-calendar-16","Range calendar with presets",[55],{"name":1666,"type":576,"title":1667,"description":1667,"categories":1668},"c-calendar-17","Calendar with pricing",[55],{"name":1670,"type":576,"title":1659,"description":1659,"categories":1671},"c-calendar-18",[55],{"name":1673,"type":576,"title":1674,"description":1674,"categories":1675},"c-calendar-19","Appointment calendar",[55],{"name":1677,"type":576,"title":1678,"description":1678,"categories":1679},"c-calendar-20","Display 2 months",[55],{"name":1681,"type":576,"title":1682,"description":1682,"categories":1683},"c-calendar-21","Display 2 months with range picker",[55],{"name":1685,"type":576,"title":1686,"description":1686,"categories":1687},"c-calendar-22","Calendar with event list",[55],{"name":1689,"type":576,"title":1690,"description":1690,"categories":1691},"c-calendar-23","Localize calendar",[55],{"name":1693,"type":576,"title":1694,"description":1694,"categories":1695},"c-calendar-24","Calendar with date picker",[55],{"name":1697,"type":576,"title":1698,"description":1698,"categories":1699},"c-calendar-25","Calendar with date range picker",[55],{"name":1701,"type":576,"title":1702,"description":1702,"categories":1703},"c-calendar-26","Calendar with date picker and year view",[55],{"name":1705,"type":576,"title":1706,"description":1706,"categories":1707},"c-calendar-27","Calendar with date and appointment picker",[55],{"name":1709,"type":576,"title":1710,"description":1710,"categories":1711},"c-calendar-28","Calendar with date picker and presets",[55],{"name":1713,"type":576,"title":1714,"description":1714,"categories":1715},"c-calendar-29","Range calendar with date picker and presets",[55],{"name":1717,"type":576,"title":1718,"description":1718,"categories":1719},"c-calendar-30","Calendar with date and time range picker",[55],{"name":1721,"type":576,"title":1722,"description":1722,"categories":1723},"c-card-1","Basic card",[60],{"name":1725,"type":576,"title":1726,"description":1726,"categories":1727},"c-card-2","Card header with border",[60],{"name":1729,"type":576,"title":1730,"description":1730,"categories":1731},"c-card-3","Card with border separation",[60],{"name":1733,"type":576,"title":1734,"description":1734,"categories":1735},"c-card-4","Card with header and footer",[60],{"name":1737,"type":576,"title":1738,"description":1738,"categories":1739},"c-card-5","Card with link",[60],{"name":1741,"type":576,"title":1742,"description":1742,"categories":1743},"c-card-6","Card with dropdown menu",[60],{"name":1745,"type":576,"title":1746,"description":1746,"categories":1747},"c-card-7","Card with image",[60],{"name":1749,"type":576,"title":1750,"description":1750,"categories":1751},"c-card-8","Card with image scale hover effect",[60],{"name":1753,"type":576,"title":1754,"description":1754,"categories":1755},"c-card-9","Card with full image",[60],{"name":1757,"type":576,"title":1758,"description":1758,"categories":1759},"c-card-10","Full card with image and shadow fade effect",[60],{"name":1761,"type":576,"title":1762,"description":1762,"categories":1763},"c-card-11","Card with stacked depth effect",[60],{"name":1765,"type":576,"title":1766,"description":1766,"categories":1767},"c-card-12","Advanced clean login form card",[60],{"name":1769,"type":576,"title":1770,"description":1770,"categories":1771},"c-card-13","Expandable billing usage card",[60],{"name":1773,"type":576,"title":1774,"description":1774,"categories":1775},"c-card-14","Deployment status summary card",[60],{"name":1777,"type":576,"title":1778,"description":1778,"categories":1779},"c-card-15","Stat card with trend and overflow menu",[60],{"name":1781,"type":576,"title":1782,"description":1782,"categories":1783},"c-card-16","Card with header badge and actions",[60],{"name":1785,"type":576,"title":1786,"description":1786,"categories":1787},"c-card-17","Card with icon, title and link",[60],{"name":1789,"type":576,"title":1790,"description":1790,"categories":1791},"c-card-18","Card with header label and link",[60],{"name":1793,"type":576,"title":1794,"description":1794,"categories":1795},"c-carousel-1","Basic carousel with single item",[65],{"name":1797,"type":576,"title":1798,"description":1798,"categories":1799},"c-carousel-2","Vertical carousel orientation",[65],{"name":1801,"type":576,"title":1802,"description":1802,"categories":1803},"c-carousel-3","Carousel with multiple items visible",[65],{"name":1805,"type":576,"title":1806,"description":1806,"categories":1807},"c-carousel-4","Carousel with responsive basis",[65],{"name":1809,"type":576,"title":1810,"description":1810,"categories":1811},"c-carousel-5","Carousel with autoplay plugin",[65],{"name":1813,"type":576,"title":1814,"description":1814,"categories":1815},"c-carousel-6","Carousel with centered slide alignment",[65],{"name":1817,"type":576,"title":1818,"description":1818,"categories":1819},"c-carousel-7","Carousel with custom spacing",[65],{"name":1821,"type":576,"title":1822,"description":1822,"categories":1823},"c-carousel-8","Carousel with images and overlays",[65],{"name":1825,"type":576,"title":1826,"description":1826,"categories":1827},"c-carousel-9","Carousel with thumbnail navigation",[65],{"name":1829,"type":576,"title":1830,"description":1830,"categories":1831},"c-carousel-10","Carousel with overlay thumbnail navigation",[65],{"name":1833,"type":576,"title":1834,"description":1834,"categories":1835},"c-carousel-11","Carousel with dots navigation and content overlays",[65],{"name":1837,"type":576,"title":1838,"description":1838,"categories":1839},"c-chart-1","Basic bar chart",[69],{"name":1841,"type":576,"title":1842,"description":1842,"categories":1843},"c-chart-2","Multi-dataset bar chart",[69],{"name":1845,"type":576,"title":1846,"description":1846,"categories":1847},"c-chart-3","Diagonal stripe bar chart",[69],{"name":1849,"type":576,"title":1850,"description":1850,"categories":1851},"c-chart-4","Dotted pattern bar chart",[69],{"name":1853,"type":576,"title":1854,"description":1854,"categories":1855},"c-chart-5","Striped and solid bar chart",[69],{"name":1857,"type":576,"title":1858,"description":1858,"categories":1859},"c-chart-6","3D gradient bar chart",[69],{"name":1861,"type":576,"title":1862,"description":1862,"categories":1863},"c-chart-7","Gradient bar chart",[69],{"name":1865,"type":576,"title":1866,"description":1866,"categories":1867},"c-chart-8","3D isometric bar chart",[69],{"name":1869,"type":576,"title":1870,"description":1870,"categories":1871},"c-chart-9","Dotted and solid bar chart",[69],{"name":1873,"type":576,"title":1874,"description":1874,"categories":1875},"c-chart-10","Duotone gradient bar chart",[69],{"name":1877,"type":576,"title":1878,"description":1878,"categories":1879},"c-chart-11","Animated monochrome bar chart",[69],{"name":1881,"type":576,"title":1882,"description":1882,"categories":1883},"c-chart-12","Vertical bar chart",[69],{"name":1885,"type":576,"title":1886,"description":1886,"categories":1887},"c-chart-13","Gradient area chart with stripe overlay",[69],{"name":1889,"type":576,"title":1890,"description":1890,"categories":1891},"c-chart-14","Stacked area chart with dashed strokes",[69],{"name":1893,"type":576,"title":1894,"description":1894,"categories":1895},"c-chart-15","Step area chart with dotted pattern fill",[69],{"name":1897,"type":576,"title":1898,"description":1898,"categories":1899},"c-chart-16","Area chart with glowing dot markers",[69],{"name":1901,"type":576,"title":1902,"description":1902,"categories":1903},"c-chart-17","Forecast line chart with stripe pattern zone",[69],{"name":1905,"type":576,"title":1906,"description":1906,"categories":1907},"c-chart-18","Stacked area chart with crosshatch pattern fill",[69],{"name":1909,"type":576,"title":1910,"description":1910,"categories":1911},"c-chart-19","Donut chart with center total",[69],{"name":1913,"type":576,"title":1914,"description":1914,"categories":1915},"c-chart-20","Donut chart with center stats",[69],{"name":1917,"type":576,"title":1918,"description":1918,"categories":1919},"c-chart-21","Bug priority pie with stripe patterns",[69],{"name":1921,"type":576,"title":1922,"description":1922,"categories":1923},"c-chart-22","Active segment donut with center label",[69],{"name":1925,"type":576,"title":1926,"description":1926,"categories":1927},"c-chart-23","Radar chart with gradient fill",[69],{"name":1929,"type":576,"title":1930,"description":1930,"categories":1931},"c-chart-24","Filled radar with glowing stroke",[69],{"name":1933,"type":576,"title":1934,"description":1934,"categories":1935},"c-chart-25","Lighthouse scores radial with labels",[69],{"name":1937,"type":576,"title":1938,"description":1938,"categories":1939},"c-checkbox-1","Basic checkbox with label",[73],{"name":1941,"type":576,"title":1942,"description":1942,"categories":1943},"c-checkbox-2","Disabled checkbox",[73],{"name":1945,"type":576,"title":1946,"description":1946,"categories":1947},"c-checkbox-3","Invalid checkbox",[73],{"name":1949,"type":576,"title":1950,"description":1950,"categories":1951},"c-checkbox-4","Indeterminate checkbox",[73],{"name":1953,"type":576,"title":1954,"description":1954,"categories":1955},"c-checkbox-5","Checkbox with label and description",[73],{"name":1957,"type":576,"title":1958,"description":1958,"categories":1959},"c-checkbox-6","Colored checkbox",[73],{"name":1961,"type":576,"title":1962,"description":1962,"categories":1963},"c-checkbox-7","Circle checkbox",[73],{"name":1965,"type":576,"title":1966,"description":1966,"categories":1967},"c-checkbox-8","Checkbox group",[73],{"name":1969,"type":576,"title":1970,"description":1970,"categories":1971},"c-checkbox-9","Card checkbox with group",[73],{"name":1973,"type":576,"title":1974,"description":1974,"categories":1975},"c-checkbox-10","Checkbox group in a frame",[73],{"name":1977,"type":576,"title":1978,"description":1978,"categories":1979},"c-checkbox-11","Card checkbox group with icons",[73],{"name":1981,"type":576,"title":1978,"description":1978,"categories":1982},"c-checkbox-12",[73],{"name":1984,"type":576,"title":1985,"description":1985,"categories":1986},"c-checkbox-13","Custom positioned checkbox",[73],{"name":1988,"type":576,"title":1985,"description":1985,"categories":1989},"c-checkbox-14",[73],{"name":1991,"type":576,"title":1992,"description":1992,"categories":1993},"c-checkbox-15","Avatar based card checkbox",[73],{"name":1995,"type":576,"title":1996,"description":1996,"categories":1997},"c-checkbox-16","Nested checkbox group",[73],{"name":1999,"type":576,"title":2000,"description":2000,"categories":2001},"c-checkbox-17","Payment method card checkbox",[73],{"name":2003,"type":576,"title":2004,"description":2004,"categories":2005},"c-checkbox-18","Checkbox with label and tooltip info",[73],{"name":2007,"type":576,"title":2008,"description":2008,"categories":2009},"c-checkbox-19","Checkbox group with badge",[73],{"name":2011,"type":576,"title":2004,"description":2004,"categories":2012},"c-checkbox-20",[73],{"name":2014,"type":576,"title":2015,"description":2015,"categories":2016},"c-checkbox-21","Inline horizontal checkbox group",[73],{"name":2018,"type":576,"title":2019,"description":2019,"categories":2020},"c-checkbox-22","Checkbox with feature badge",[73],{"name":2022,"type":576,"title":2023,"description":2023,"categories":2024},"c-collapsible-1","Basic collapsible",[78],{"name":2026,"type":576,"title":2027,"description":2027,"categories":2028},"c-collapsible-2","Collapsible with checkbox settings",[78],{"name":2030,"type":576,"title":2031,"description":2031,"categories":2032},"c-collapsible-3","Collapsible animated card",[78],{"name":2034,"type":576,"title":2035,"description":2035,"categories":2036},"c-collapsible-4","Collapsible card with bottom trigger",[78],{"name":2038,"type":576,"title":2039,"description":2039,"categories":2040},"c-collapsible-5","Collapsible form fields",[78],{"name":2042,"type":576,"title":2043,"description":2043,"categories":2044},"c-collapsible-6","Collapsible frame",[78],{"name":2046,"type":576,"title":2047,"description":2047,"categories":2048},"c-collapsible-7","Nested collapsible list with actions",[78],{"name":2050,"type":576,"title":2051,"description":2051,"categories":2052},"c-collapsible-8","Collapsible User profile",[78],{"name":2054,"type":576,"title":2055,"description":2055,"categories":2056},"c-collapsible-9","Multi-level collapsible menu",[78],{"name":2058,"type":576,"title":2059,"description":2059,"categories":2060},"c-collapsible-10","Tree view with file actions",[78],{"name":2062,"type":576,"title":2063,"description":2063,"categories":2064},"c-combobox-1","A basic combobox with a list of options",[83],{"name":2066,"type":576,"title":2067,"description":2067,"categories":2068},"c-combobox-2","A disabled combobox",[83],{"name":2070,"type":576,"title":2071,"description":2071,"categories":2072},"c-combobox-3","A combobox in an invalid state",[83],{"name":2074,"type":576,"title":2075,"description":2075,"categories":2076},"c-combobox-4","A combobox with a clear button",[83],{"name":2078,"type":576,"title":2079,"description":2079,"categories":2080},"c-combobox-5","A combobox with auto-highlight enabled",[83],{"name":2082,"type":576,"title":2083,"description":2083,"categories":2084},"c-combobox-6","A combobox with grouped options",[83],{"name":2086,"type":576,"title":2087,"description":2087,"categories":2088},"c-combobox-7","A combobox with grouped options and separators",[83],{"name":2090,"type":576,"title":2091,"description":2091,"categories":2092},"c-combobox-8","A combobox with a large list of options",[83],{"name":2094,"type":576,"title":2095,"description":2095,"categories":2096},"c-combobox-9","A combobox with an icon addon",[83],{"name":2098,"type":576,"title":2099,"description":2099,"categories":2100},"c-combobox-10","A combobox rendered inside a popup",[83],{"name":2102,"type":576,"title":2103,"description":2103,"categories":2104},"c-combobox-11","A combobox used within a form",[83],{"name":2106,"type":576,"title":2107,"description":2107,"categories":2108},"c-combobox-12","A multi-select combobox",[83],{"name":2110,"type":576,"title":2111,"description":2111,"categories":2112},"c-combobox-13","A disabled multi-select combobox",[83],{"name":2114,"type":576,"title":2115,"description":2115,"categories":2116},"c-combobox-14","A multi-select combobox in an invalid state",[83],{"name":2118,"type":576,"title":2119,"description":2119,"categories":2120},"c-combobox-15","A multi-select with chips that cannot be removed",[83],{"name":2122,"type":576,"title":2123,"description":2123,"categories":2124},"c-combobox-16","A combobox with custom item rendering",[83],{"name":2126,"type":576,"title":2127,"description":2127,"categories":2128},"c-combobox-17","A combobox used within a dialog",[83],{"name":2130,"type":576,"title":2131,"description":2131,"categories":2132},"c-combobox-18","A combobox with a custom trigger icon",[83],{"name":2134,"type":576,"title":2135,"description":2135,"categories":2136},"c-combobox-19","A multi-select combobox with user tags",[83],{"name":2138,"type":576,"title":2139,"description":2139,"categories":2140},"c-combobox-20","Invisible combobox with member tags",[83],{"name":2142,"type":576,"title":2143,"description":2143,"categories":2144},"c-combobox-21","A status change combobox",[83],{"name":2146,"type":576,"title":2147,"description":2147,"categories":2148},"c-combobox-22","A priority change combobox",[83],{"name":2150,"type":576,"title":2151,"description":2151,"categories":2152},"c-combobox-23","A lead selection combobox",[83],{"name":2154,"type":576,"title":2155,"description":2155,"categories":2156},"c-combobox-24","A multi-member selection combobox",[83],{"name":2158,"type":576,"title":2159,"description":2159,"categories":2160},"c-combobox-25","A date selection combobox with an optional custom picker dialog",[83],{"name":2162,"type":576,"title":2163,"description":2163,"categories":2164},"c-combobox-26","A multi-label selection combobox with a no-label mode",[83],{"name":2166,"type":576,"title":2167,"description":2167,"categories":2168},"c-combobox-27","A team selection combobox",[83],{"name":2170,"type":576,"title":2171,"description":2171,"categories":2172},"c-combobox-28","An assignee selection combobox",[83],{"name":2174,"type":576,"title":2175,"description":2175,"categories":2176},"c-command-1","Basic command",[88],{"name":2178,"type":576,"title":2179,"description":2179,"categories":2180},"c-command-2","Command with shortcuts",[88],{"name":2182,"type":576,"title":2183,"description":2183,"categories":2184},"c-command-3","Command with groups",[88],{"name":2186,"type":576,"title":2187,"description":2187,"categories":2188},"c-command-4","Command with many groups",[88],{"name":2190,"type":576,"title":2191,"description":2191,"categories":2192},"c-command-5","File search command palette",[88],{"name":2194,"type":576,"title":2195,"description":2195,"categories":2196},"c-command-6","User search command with avatars",[88],{"name":2198,"type":576,"title":2199,"description":2199,"categories":2200},"c-command-7","Action palette with keyboard shortcuts",[88],{"name":2202,"type":576,"title":2203,"description":2203,"categories":2204},"c-command-8","Command with recent and favorites sections",[88],{"name":2206,"type":576,"title":2207,"description":2207,"categories":2208},"c-context-menu-1","Basic context menu.",[92],{"name":2210,"type":576,"title":2211,"description":2211,"categories":2212},"c-context-menu-2","Context menu with icons for common actions.",[92],{"name":2214,"type":576,"title":2215,"description":2215,"categories":2216},"c-context-menu-3","Context menu with keyboard shortcuts.",[92],{"name":2218,"type":576,"title":2219,"description":2219,"categories":2220},"c-context-menu-4","Context menu with nested submenus.",[92],{"name":2222,"type":576,"title":2223,"description":2223,"categories":2224},"c-context-menu-5","Context menu labels and separators.",[92],{"name":2226,"type":576,"title":2227,"description":2227,"categories":2228},"c-context-menu-6","Context menu with checkbox items.",[92],{"name":2230,"type":576,"title":2231,"description":2231,"categories":2232},"c-context-menu-7","Context menu with radio items.",[92],{"name":2234,"type":576,"title":2235,"description":2235,"categories":2236},"c-context-menu-8","Context menu with destructive actions.",[92],{"name":2238,"type":576,"title":2239,"description":2239,"categories":2240},"c-context-menu-9","Context menu with different alignment sides.",[92],{"name":2242,"type":576,"title":2243,"description":2243,"categories":2244},"c-context-menu-10","Context menu used inside a dialog component.",[92],{"name":2246,"type":576,"title":2247,"description":2247,"categories":2248},"c-data-grid-1","Data grid with pagination",[96],{"name":2250,"type":576,"title":2251,"description":2251,"categories":2252},"c-data-grid-2","Data grid with cell border",[96],{"name":2254,"type":576,"title":2255,"description":2255,"categories":2256},"c-data-grid-3","Data grid with dense layout",[96],{"name":2258,"type":576,"title":2259,"description":2259,"categories":2260},"c-data-grid-4","Data grid without table borders",[96],{"name":2262,"type":576,"title":2263,"description":2263,"categories":2264},"c-data-grid-5","Data grid with striped rows",[96],{"name":2266,"type":576,"title":2267,"description":2267,"categories":2268},"c-data-grid-6","Data grid with auto width table layout",[96],{"name":2270,"type":576,"title":2271,"description":2271,"categories":2272},"c-data-grid-7","Data grid with row selection",[96],{"name":2274,"type":576,"title":2275,"description":2275,"categories":2276},"c-data-grid-8","Data grid with expandable rows",[96],{"name":2278,"type":576,"title":2279,"description":2279,"categories":2280},"c-data-grid-9","Data grid with sub table",[96],{"name":2282,"type":576,"title":2283,"description":2283,"categories":2284},"c-data-grid-10","Data grid with column icons",[96],{"name":2286,"type":576,"title":2287,"description":2287,"categories":2288},"c-data-grid-11","Data grid with sortable columns",[96],{"name":2290,"type":576,"title":2291,"description":2291,"categories":2292},"c-data-grid-12","Data grid with movable columns",[96],{"name":2294,"type":576,"title":2295,"description":2295,"categories":2296},"c-data-grid-13","Data grid with draggable columns",[96],{"name":2298,"type":576,"title":2299,"description":2299,"categories":2300},"c-data-grid-14","Data grid with draggable rows",[96],{"name":2302,"type":576,"title":2303,"description":2303,"categories":2304},"c-data-grid-15","Data grid with column resizing",[96],{"name":2306,"type":576,"title":2307,"description":2307,"categories":2308},"c-data-grid-16","Data grid with pinnable columns",[96],{"name":2310,"type":576,"title":2311,"description":2311,"categories":2312},"c-data-grid-17","Data grid with sticky header",[96],{"name":2314,"type":576,"title":2315,"description":2315,"categories":2316},"c-data-grid-18","Data grid with column controls",[96],{"name":2318,"type":576,"title":2319,"description":2319,"categories":2320},"c-data-grid-19","Data grid with card container",[96],{"name":2322,"type":576,"title":2323,"description":2323,"categories":2324},"c-data-grid-20","Data grid with column visibility controls",[96],{"name":2326,"type":576,"title":2327,"description":2327,"categories":2328},"c-data-grid-21","Data grid with loading skeleton",[96],{"name":2330,"type":576,"title":2331,"description":2331,"categories":2332},"c-data-grid-22","Data grid with CRUD features",[96],{"name":2334,"type":576,"title":2335,"description":2335,"categories":2336},"c-data-grid-23","Data grid with CRUD in frame container",[96],{"name":2338,"type":576,"title":2339,"description":2339,"categories":2340},"c-data-grid-24","Data grid with column totals footer",[96],{"name":2342,"type":576,"title":2343,"description":2343,"categories":2344},"c-data-grid-25","Data grid with summary stats footer",[96],{"name":2346,"type":576,"title":2347,"description":2347,"categories":2348},"c-data-grid-26","Data grid with per-column aggregate footer",[96],{"name":2350,"type":576,"title":2351,"description":2351,"categories":2352},"c-data-grid-27","Data grid with local infinite scroll",[96],{"name":2354,"type":576,"title":2355,"description":2355,"categories":2356},"c-data-grid-28","Data grid with remote infinite scroll",[96],{"name":2358,"type":576,"title":2359,"description":2359,"categories":2360},"c-data-grid-29","Data grid with row pinning support",[96],{"name":2362,"type":576,"title":2363,"description":2363,"categories":2364},"c-date-selector-1","Basic date selector",[101],{"name":2366,"type":576,"title":2367,"description":2367,"categories":2368},"c-date-selector-2","Date selector with popover",[101],{"name":2370,"type":576,"title":2371,"description":2371,"categories":2372},"c-date-selector-3","Date selector with dialog",[101],{"name":2374,"type":576,"title":2375,"description":2375,"categories":2376},"c-date-selector-4","Date selector with dropdown menu",[101],{"name":2378,"type":576,"title":2379,"description":2379,"categories":2380},"c-dialog-1","Basic dialog",[106],{"name":2382,"type":576,"title":2383,"description":2383,"categories":2384},"c-dialog-2","Dialog with scrollable content",[106],{"name":2386,"type":576,"title":2387,"description":2387,"categories":2388},"c-dialog-3","Dialog with scrollable content and sticky footer.",[106],{"name":2390,"type":576,"title":2391,"description":2391,"categories":2392},"c-dialog-4","Dialog without a close button.",[106],{"name":2394,"type":576,"title":2395,"description":2395,"categories":2396},"c-dialog-5","Dialog with custom close button",[106],{"name":2398,"type":576,"title":2399,"description":2399,"categories":2400},"c-dialog-6","Full-screen fluid dialog with sticky header and footer",[106],{"name":2402,"type":576,"title":2403,"description":2403,"categories":2404},"c-dialog-7","Confirmation dialog with destructive action",[106],{"name":2406,"type":576,"title":2407,"description":2407,"categories":2408},"c-dialog-8","Cookie consent dialog",[106],{"name":2410,"type":576,"title":2411,"description":2411,"categories":2412},"c-dialog-9","Dialog with keyboard shortcuts list",[106],{"name":2414,"type":576,"title":2415,"description":2415,"categories":2416},"c-dialog-10","Dialog with full width button",[106],{"name":2418,"type":576,"title":2419,"description":2419,"categories":2420},"c-drawer-1","A basic drawer with scrollable content",[110],{"name":2422,"type":576,"title":2423,"description":2423,"categories":2424},"c-drawer-2","A drawer that opens from the right side",[110],{"name":2426,"type":576,"title":2427,"description":2427,"categories":2428},"c-drawer-3","A drawer that opens from the left side",[110],{"name":2430,"type":576,"title":2431,"description":2431,"categories":2432},"c-drawer-4","A drawer that opens from the top side",[110],{"name":2434,"type":576,"title":2435,"description":2435,"categories":2436},"c-drawer-5","Drawers opening from different sides",[110],{"name":2438,"type":576,"title":2439,"description":2439,"categories":2440},"c-dropdown-menu-1","Basic dropdown menu",[115],{"name":2442,"type":576,"title":2443,"description":2443,"categories":2444},"c-dropdown-menu-2","Dropdown menus opening positions",[115],{"name":2446,"type":576,"title":2447,"description":2447,"categories":2448},"c-dropdown-menu-3","Dropdown menu with icons",[115],{"name":2450,"type":576,"title":2451,"description":2451,"categories":2452},"c-dropdown-menu-4","Dropdown menu with keyboard shortcuts",[115],{"name":2454,"type":576,"title":2455,"description":2455,"categories":2456},"c-dropdown-menu-5","Dropdown menu with nested submenus",[115],{"name":2458,"type":576,"title":2459,"description":2459,"categories":2460},"c-dropdown-menu-6","Dropdown menu with checkboxes",[115],{"name":2462,"type":576,"title":2463,"description":2463,"categories":2464},"c-dropdown-menu-7","Dropdown menu with radio groups",[115],{"name":2466,"type":576,"title":2467,"description":2467,"categories":2468},"c-dropdown-menu-8","Advance dropdown menu",[115],{"name":2470,"type":576,"title":2471,"description":2471,"categories":2472},"c-dropdown-menu-9","User profile dropdown menu",[115],{"name":2474,"type":576,"title":2475,"description":2475,"categories":2476},"c-dropdown-menu-10","Workspace switcher dropdown menu",[115],{"name":2478,"type":576,"title":2479,"description":2479,"categories":2480},"c-dropdown-menu-11","Notifications dropdown menu",[115],{"name":2482,"type":576,"title":2483,"description":2483,"categories":2484},"c-dropdown-menu-12","Actions dropdown menu",[115],{"name":2486,"type":576,"title":2487,"description":2487,"categories":2488},"c-dropdown-menu-13","Share and export dropdown menu",[115],{"name":2490,"type":576,"title":2491,"description":2491,"categories":2492},"c-dropdown-menu-14","User profile dropdown menu with theme toggle and status selector",[115],{"name":2494,"type":576,"title":2495,"description":2495,"categories":2496},"c-dropdown-menu-15","User and create menu",[115],{"name":2498,"type":576,"title":2499,"description":2499,"categories":2500},"c-dropdown-menu-16","Quick action menu",[115],{"name":2502,"type":576,"title":2503,"description":2503,"categories":2504},"c-dropdown-menu-17","Notifications dropdown with items",[115],{"name":2506,"type":576,"title":2507,"description":2507,"categories":2508},"c-dropdown-menu-18","AI model selector with provider icons",[115],{"name":2510,"type":576,"title":2511,"description":2511,"categories":2512},"c-empty-1","Basic empty state",[119],{"name":2514,"type":576,"title":2515,"description":2515,"categories":2516},"c-empty-2","Search empty state",[119],{"name":2518,"type":576,"title":2519,"description":2519,"categories":2520},"c-empty-3","Empty state with search input",[119],{"name":2522,"type":576,"title":2523,"description":2523,"categories":2524},"c-empty-4","Empty state with add button",[119],{"name":2526,"type":576,"title":2527,"description":2527,"categories":2528},"c-empty-5","Document empty state",[119],{"name":2530,"type":576,"title":2531,"description":2531,"categories":2532},"c-empty-6","Dashed upload empty state",[119],{"name":2534,"type":576,"title":2535,"description":2535,"categories":2536},"c-empty-7","Inbox empty state",[119],{"name":2538,"type":576,"title":2539,"description":2539,"categories":2540},"c-empty-8","Team members empty state",[119],{"name":2542,"type":576,"title":2543,"description":2543,"categories":2544},"c-empty-9","No notifications empty state",[119],{"name":2546,"type":576,"title":2547,"description":2547,"categories":2548},"c-empty-10","No data analytics empty state",[119],{"name":2550,"type":576,"title":2551,"description":2551,"categories":2552},"c-empty-11","Bookmarks empty state",[119],{"name":2554,"type":576,"title":2555,"description":2555,"categories":2556},"c-empty-12","Tasks empty state",[119],{"name":2558,"type":576,"title":2559,"description":2559,"categories":2560},"c-empty-13","No automations empty state with toggle illustration",[119],{"name":2562,"type":576,"title":2563,"description":2563,"categories":2564},"c-empty-14","Comments empty state with isometric board illustration",[119],{"name":2566,"type":576,"title":2567,"description":2567,"categories":2568},"c-empty-15","No products empty state with stacked cards and blur effect",[119],{"name":2570,"type":576,"title":2571,"description":2571,"categories":2572},"c-empty-16","Search empty state with stacked file cards illustration",[119],{"name":2574,"type":576,"title":2575,"description":2575,"categories":2576},"c-empty-17","No messages empty state with chat bubbles illustration",[119],{"name":2578,"type":576,"title":2579,"description":2579,"categories":2580},"c-empty-18","No payments empty state with credit card illustration",[119],{"name":2582,"type":576,"title":2583,"description":2583,"categories":2584},"c-empty-19","No integrations empty state with connected nodes illustration",[119],{"name":2586,"type":576,"title":2587,"description":2587,"categories":2588},"c-empty-20","No events empty state with calendar illustration",[119],{"name":2590,"type":576,"title":2591,"description":2591,"categories":2592},"c-event-calendar-1","Event calendar with all views and live settings",[123],{"name":2594,"type":576,"title":2595,"description":2595,"categories":2596},"c-event-calendar-2","Event calendar with resource view for room bookings",[123],{"name":2598,"type":576,"title":2599,"description":2600,"categories":2601},"c-event-calendar-3","Event calendar with create and edit dialog","Month calendar with event CRUD in one dialog: open from an empty day, an event, or Add event, commit via apiRef. Title, color, start, duration, all-day fields. ReUI EventCalendar, Nav, Toolbar; shadcn Dialog, Field, Select, Switch. Scheduling apps.",[123],{"name":2603,"type":576,"title":2604,"description":2604,"categories":2605},"c-event-calendar-4","Event calendar with custom event chips",[123],{"name":2607,"type":576,"title":2608,"description":2608,"categories":2609},"c-event-calendar-5","Event calendar with drag-to-book appointments",[123],{"name":2611,"type":576,"title":2612,"description":2612,"categories":2613},"c-field-1","Fields with input groups",[127],{"name":2615,"type":576,"title":2616,"description":2616,"categories":2617},"c-field-2","Field with input and textarea",[127],{"name":2619,"type":576,"title":2620,"description":2620,"categories":2621},"c-field-3","Field with select control",[127],{"name":2623,"type":576,"title":2624,"description":2624,"categories":2625},"c-field-4","Field with radio groups and checkboxes",[127],{"name":2627,"type":576,"title":2628,"description":2628,"categories":2629},"c-field-5","Filed with slider and switch",[127],{"name":2631,"type":576,"title":2632,"description":2632,"categories":2633},"c-field-6","Field with OTP input",[127],{"name":2635,"type":576,"title":2636,"description":2636,"categories":2637},"c-field-7","Settings form with validation",[127],{"name":2639,"type":576,"title":2640,"description":2640,"categories":2641},"c-field-8","Permission settings with checkboxes",[127],{"name":2643,"type":576,"title":2644,"description":2644,"categories":2645},"c-field-9","Responsive field layout",[127],{"name":2647,"type":576,"title":2648,"description":2648,"categories":2649},"c-field-10","Notification preferences form",[127],{"name":2651,"type":576,"title":2652,"description":2652,"categories":2653},"c-field-11","Form with validation errors",[127],{"name":2655,"type":576,"title":2656,"description":2656,"categories":2657},"c-file-upload-1","Basic file upload",[131],{"name":2659,"type":576,"title":2660,"description":2660,"categories":2661},"c-file-upload-2","Avatar upload",[131],{"name":2663,"type":576,"title":2664,"description":2664,"categories":2665},"c-file-upload-3","Basic drag and drop file upload",[131],{"name":2667,"type":576,"title":2668,"description":2668,"categories":2669},"c-file-upload-4","Gallery file upload",[131],{"name":2671,"type":576,"title":2672,"description":2672,"categories":2673},"c-file-upload-5","Progress file upload",[131],{"name":2675,"type":576,"title":2676,"description":2676,"categories":2677},"c-file-upload-6","Table file upload",[131],{"name":2679,"type":576,"title":2680,"description":2680,"categories":2681},"c-file-upload-7","Image upload",[131],{"name":2683,"type":576,"title":2684,"description":2684,"categories":2685},"c-file-upload-8","Image upload with sorting.",[131],{"name":2687,"type":576,"title":2688,"description":2688,"categories":2689},"c-file-upload-9","Card file upload.",[131],{"name":2691,"type":576,"title":2692,"description":2692,"categories":2693},"c-file-upload-10","Cover image upload.",[131],{"name":2695,"type":576,"title":2696,"description":2696,"categories":2697},"c-filters-1","Filters with various field types",[135],{"name":2699,"type":576,"title":2700,"description":2700,"categories":2701},"c-filters-2","Filters with input validation",[135],{"name":2703,"type":576,"title":2704,"description":2704,"categories":2705},"c-filters-3","Filters with custom trigger button",[135],{"name":2707,"type":576,"title":2708,"description":2708,"categories":2709},"c-filters-4","Filters with small size controls",[135],{"name":2711,"type":576,"title":2712,"description":2712,"categories":2713},"c-filters-5","Filters with large size controls",[135],{"name":2715,"type":576,"title":2716,"description":2716,"categories":2717},"c-filters-6","Filters with custom controls",[135],{"name":2719,"type":576,"title":2720,"description":2720,"categories":2721},"c-filters-7","Filters with data grid",[135],{"name":2723,"type":576,"title":2724,"description":2724,"categories":2725},"c-filters-8","Filters with data grid and async mode",[135],{"name":2727,"type":576,"title":2728,"description":2728,"categories":2729},"c-filters-9","Filters with i18n support",[135],{"name":2731,"type":576,"title":2732,"description":2732,"categories":2733},"c-filters-10","Filters with virtualized large lists",[135],{"name":2735,"type":576,"title":2736,"description":2736,"categories":2737},"c-filters-11","Filters with prefetched async options",[135],{"name":2739,"type":576,"title":2740,"description":2740,"categories":2741},"c-filters-12","Filters with async server-side search",[135],{"name":2743,"type":576,"title":2744,"description":2744,"categories":2745},"c-frame-1","Basic frame",[139],{"name":2747,"type":576,"title":2748,"description":2748,"categories":2749},"c-frame-2","Frame with separated panels",[139],{"name":2751,"type":576,"title":2752,"description":2752,"categories":2753},"c-frame-3","Frame with stacked panels",[139],{"name":2755,"type":576,"title":2756,"description":2756,"categories":2757},"c-frame-4","Frame with stacked panels and dense style",[139],{"name":2759,"type":576,"title":2760,"description":2760,"categories":2761},"c-frame-5","Frame with collapsible panels",[139],{"name":2763,"type":576,"title":2764,"description":2764,"categories":2765},"c-frame-6","Frame with ghost(no outer border) variant",[139],{"name":2767,"type":576,"title":2768,"description":2768,"categories":2769},"c-frame-7","Frame with dense layout",[139],{"name":2771,"type":576,"title":2752,"description":2752,"categories":2772},"c-frame-8",[139],{"name":2774,"type":576,"title":2775,"description":2775,"categories":2776},"c-frame-9","Frame with extra small spacing",[139],{"name":2778,"type":576,"title":2779,"description":2779,"categories":2780},"c-frame-10","Frame with small spacing",[139],{"name":2782,"type":576,"title":2783,"description":2783,"categories":2784},"c-frame-11","Frame with default spacing",[139],{"name":2786,"type":576,"title":2787,"description":2787,"categories":2788},"c-frame-12","Frame with large spacing",[139],{"name":2790,"type":576,"title":2791,"description":2791,"categories":2792},"c-frame-13","Frame with small border radius",[139],{"name":2794,"type":576,"title":2795,"description":2795,"categories":2796},"c-frame-14","Frame with medium border radius",[139],{"name":2798,"type":576,"title":2799,"description":2799,"categories":2800},"c-frame-15","Frame with large border radius",[139],{"name":2802,"type":576,"title":2803,"description":2803,"categories":2804},"c-frame-16","Frame with default border radius",[139],{"name":2806,"type":576,"title":2807,"description":2807,"categories":2808},"c-frame-17","Frame with extra large border radius",[139],{"name":2810,"type":576,"title":2811,"description":2811,"categories":2812},"c-frame-18","Frame with content-only panels",[139],{"name":2814,"type":576,"title":2815,"description":2815,"categories":2816},"c-frame-19","Frame with stacked content-only panels",[139],{"name":2818,"type":576,"title":2819,"description":2819,"categories":2820},"c-gantt-1","Gantt chart with settings menu and schedulable rows",[144],{"name":2822,"type":576,"title":2823,"description":2823,"categories":2824},"c-gantt-2","Gantt chart with drag-to-reorder tree rows",[144],{"name":2826,"type":576,"title":2827,"description":2828,"categories":2829},"c-gantt-3","Gantt chart with quarter roadmap and rollups","Quarter-scale roadmap where workstream groups form swimlanes and parent rows roll children into one summary bar. Toolbar button calls addEvent via apiRef to schedule backlog items. ReUI Gantt, GanttNav, GanttToolbar, GanttView; shadcn Card, Button.",[144],{"name":2831,"type":576,"title":2832,"description":2832,"categories":2833},"c-gantt-4","Gantt chart with weekly capacity board",[144],{"name":2835,"type":576,"title":2836,"description":2836,"categories":2837},"c-gantt-5","Gantt chart with task table and progress",[144],{"name":2839,"type":576,"title":2840,"description":2840,"categories":2841},"c-hover-card-1","Basic hover card.",[148],{"name":2843,"type":576,"title":2844,"description":2844,"categories":2845},"c-hover-card-2","Hover card with positions.",[148],{"name":2847,"type":576,"title":2848,"description":2848,"categories":2849},"c-hover-card-3","Hover card with profile information.",[148],{"name":2851,"type":576,"title":2852,"description":2852,"categories":2853},"c-hover-card-4","Hover card with icon",[148],{"name":2855,"type":576,"title":2856,"description":2856,"categories":2857},"c-hover-card-5","Hover card with an image and text content.",[148],{"name":2859,"type":576,"title":2860,"description":2860,"categories":2861},"c-hover-card-6","Hover card inside a dialog.",[148],{"name":2863,"type":576,"title":2864,"description":2864,"categories":2865},"c-hover-card-7","Timestamp hover card with active relative time",[148],{"name":2867,"type":576,"title":2868,"description":2868,"categories":2869},"c-hover-card-8","Hover card with paginated release notes",[148],{"name":2871,"type":576,"title":2872,"description":2873,"categories":2874},"c-icon-stack-1","Isometric icon stack","Layered isometric icon",[152],{"name":2876,"type":576,"title":2877,"description":2878,"categories":2879},"c-icon-stack-2","Icon stack sizes","Responsive icon stack sizes",[152],{"name":2881,"type":576,"title":2882,"description":2883,"categories":2884},"c-icon-stack-3","Icon stack colors","Semantic icon stack colors",[152],{"name":2886,"type":576,"title":2887,"description":2888,"categories":2889},"c-icon-stack-4","Icon badge stack","Icon stack with badge overlay",[152],{"name":2891,"type":576,"title":2892,"description":2893,"categories":2894},"c-icon-stack-5","Empty state illustration","Shadcn empty state illustration",[152],{"name":2896,"type":576,"title":2897,"description":2898,"categories":2899},"c-icon-stack-6","Item media stack","Icon stack in shadcn item",[152],{"name":2901,"type":576,"title":2902,"description":2902,"categories":2903},"c-icon-tile-1","Default icon tile",[157],{"name":2905,"type":576,"title":2906,"description":2907,"categories":2908},"c-icon-tile-2","Elevated icon tile","Icon tile with elevated surface",[157],{"name":2910,"type":576,"title":2911,"description":2912,"categories":2913},"c-icon-tile-3","Framed icon tile","Icon tile with framed surface",[157],{"name":2915,"type":576,"title":2916,"description":2917,"categories":2918},"c-icon-tile-4","All variants","Every icon tile variant",[157],{"name":2920,"type":576,"title":2921,"description":2922,"categories":2923},"c-icon-tile-5","Sizes","Icon tile size scale",[157],{"name":2925,"type":576,"title":2926,"description":2927,"categories":2928},"c-icon-tile-6","Circular tiles","Icon tiles with circular shape",[157],{"name":2930,"type":576,"title":2931,"description":2932,"categories":2933},"c-icon-tile-7","Custom sizing","Icon tile with custom sizing",[157],{"name":2935,"type":576,"title":2936,"description":2937,"categories":2938},"c-icon-tile-8","Color tones","Semantic icon tile color tones",[157],{"name":2940,"type":576,"title":2941,"description":2942,"categories":2943},"c-icon-tile-9","Text content","Icon tile with text content",[157],{"name":2945,"type":576,"title":2946,"description":2947,"categories":2948},"c-icon-tile-10","List row","Icon tile in a list row",[157],{"name":2950,"type":576,"title":2951,"description":2952,"categories":2953},"c-icon-tile-11","Feature card","Icon tile in a feature card",[157],{"name":2955,"type":576,"title":2956,"description":2957,"categories":2958},"c-icon-tile-12","Empty state","Icon tile in an empty state",[157],{"name":2960,"type":576,"title":2961,"description":2962,"categories":2963},"c-icon-tile-13","Interactive tile","Interactive icon tile",[157],{"name":2965,"type":576,"title":2966,"description":2967,"categories":2968},"c-icon-tile-14","Status overlay","Icon tile with status overlay",[157],{"name":2970,"type":576,"title":2971,"description":2972,"categories":2973},"c-icon-tile-15","Soft tones","Tone-adjustable soft variant",[157],{"name":2975,"type":576,"title":2976,"description":2977,"categories":2978},"c-icon-tile-16","Solid tones","Tone-adjustable solid variant",[157],{"name":2980,"type":576,"title":2981,"description":2982,"categories":2983},"c-icon-tile-17","Brand colors","Elevated tile with a Tailwind color fill",[157],{"name":2985,"type":576,"title":2986,"description":2986,"categories":2987},"c-input-group-1","Basic input group",[167],{"name":2989,"type":576,"title":2990,"description":2990,"categories":2991},"c-input-group-2","Input group in disabled state",[167],{"name":2993,"type":576,"title":2994,"description":2994,"categories":2995},"c-input-group-3","Input group in invalid state",[167],{"name":2997,"type":576,"title":2998,"description":2998,"categories":2999},"c-input-group-4","Input group with prefix icon",[167],{"name":3001,"type":576,"title":3002,"description":3002,"categories":3003},"c-input-group-5","Input group with suffix icon",[167],{"name":3005,"type":576,"title":3006,"description":3006,"categories":3007},"c-input-group-6","Input group with both prefix and suffix icons",[167],{"name":3009,"type":576,"title":3010,"description":3010,"categories":3011},"c-input-group-7","Input group with prefix text addon",[167],{"name":3013,"type":576,"title":3014,"description":3014,"categories":3015},"c-input-group-8","Input group with suffix text addon",[167],{"name":3017,"type":576,"title":3018,"description":3018,"categories":3019},"c-input-group-9","Input group with both prefix and suffix text addons",[167],{"name":3021,"type":576,"title":3022,"description":3022,"categories":3023},"c-input-group-10","Input group with action button addon",[167],{"name":3025,"type":576,"title":3026,"description":3026,"categories":3027},"c-input-group-11","Input group with icon button action",[167],{"name":3029,"type":576,"title":3030,"description":3030,"categories":3031},"c-input-group-12","Input group with dropdown menu selection",[167],{"name":3033,"type":576,"title":3034,"description":3034,"categories":3035},"c-input-group-13","Input group with keyboard shortcut (Kbd)",[167],{"name":3037,"type":576,"title":3038,"description":3038,"categories":3039},"c-input-group-14","Input group with tooltip action",[167],{"name":3041,"type":576,"title":3042,"description":3042,"categories":3043},"c-input-group-15","Input group with loading spinner",[167],{"name":3045,"type":576,"title":3046,"description":3046,"categories":3047},"c-input-group-16","Input group with block-start addon",[167],{"name":3049,"type":576,"title":3050,"description":3050,"categories":3051},"c-input-group-17","Textarea input group with dynamic character limit",[167],{"name":3053,"type":576,"title":3054,"description":3054,"categories":3055},"c-input-group-18","AI Chat textarea with rich toolbar actions",[167],{"name":3057,"type":576,"title":3058,"description":3058,"categories":3059},"c-input-group-19","AI Chat interface with file and context attachments",[167],{"name":3061,"type":576,"title":3062,"description":3062,"categories":3063},"c-input-group-20","Input group demonstrating label usage",[167],{"name":3065,"type":576,"title":3066,"description":3066,"categories":3067},"c-input-group-21","Input group with multiple icons\u002Fbuttons at the end",[167],{"name":3069,"type":576,"title":3070,"description":3070,"categories":3071},"c-input-group-22","Input group with popover trigger addon",[167],{"name":3073,"type":576,"title":3074,"description":3074,"categories":3075},"c-input-group-23","Input group with Sparkles icon and complex Kbd shortcut",[167],{"name":3077,"type":576,"title":3078,"description":3078,"categories":3079},"c-input-group-24","Input group with success indicator circle",[167],{"name":3081,"type":576,"title":3082,"description":3082,"categories":3083},"c-input-group-25","Input group with search results count",[167],{"name":3085,"type":576,"title":3086,"description":3086,"categories":3087},"c-input-group-26","Textarea input group with action buttons",[167],{"name":3089,"type":576,"title":3090,"description":3090,"categories":3091},"c-input-group-27","Input group wrapped in a button group with text prefixes\u002Fsuffixes",[167],{"name":3093,"type":576,"title":3094,"description":3094,"categories":3095},"c-input-group-28","Input group with a menu button (three dots) at the end",[167],{"name":3097,"type":576,"title":3098,"description":3098,"categories":3099},"c-input-group-29","Pill-shaped input group with a category dropdown at the end",[167],{"name":3101,"type":576,"title":3102,"description":3102,"categories":3103},"c-input-group-30","Input group with inline addons (no separating borders)",[167],{"name":3105,"type":576,"title":3106,"description":3106,"categories":3107},"c-input-group-31","Input group with inline start and block-end addon",[167],{"name":3109,"type":576,"title":3110,"description":3110,"categories":3111},"c-input-group-32","Member row with avatar, email and role selection",[167],{"name":3113,"type":576,"title":3114,"description":3114,"categories":3115},"c-input-group-33","Project save interface with status and action buttons",[167],{"name":3117,"type":576,"title":3118,"description":3118,"categories":3119},"c-input-group-34","Pill-shaped AI prompt bar with voice and audio icons",[167],{"name":3121,"type":576,"title":3122,"description":3122,"categories":3123},"c-input-group-35","Password input with visibility toggle",[167],{"name":3125,"type":576,"title":3126,"description":3126,"categories":3127},"c-input-group-36","API request input with method selector and send action",[167],{"name":3129,"type":576,"title":3130,"description":3130,"categories":3131},"c-input-group-37","Order search input with status filter",[167],{"name":3133,"type":576,"title":3134,"description":3134,"categories":3135},"c-input-group-38","Company search input with region selector",[167],{"name":3137,"type":576,"title":3138,"description":3138,"categories":3139},"c-input-group-39","Contact search input with filter and actions menu",[167],{"name":3141,"type":576,"title":3142,"description":3142,"categories":3143},"c-input-group-40","Share link input with visibility selector and copy action",[167],{"name":3145,"type":576,"title":3146,"description":3146,"categories":3147},"c-input-otp-1","Basic OTP input.",[172],{"name":3149,"type":576,"title":3150,"description":3150,"categories":3151},"c-input-otp-2","OTP input with numeric digits.",[172],{"name":3153,"type":576,"title":3154,"description":3154,"categories":3155},"c-input-otp-3","OTP input with multiple separators.",[172],{"name":3157,"type":576,"title":3158,"description":3158,"categories":3159},"c-input-otp-4","OTP input with letters and numbers.",[172],{"name":3161,"type":576,"title":3162,"description":3162,"categories":3163},"c-input-otp-5","OTP input for PIN codes.",[172],{"name":3165,"type":576,"title":3166,"description":3166,"categories":3167},"c-input-otp-6","OTP input verification code.",[172],{"name":3169,"type":576,"title":3170,"description":3170,"categories":3171},"c-input-1","Basic input",[162],{"name":3173,"type":576,"title":3174,"description":3174,"categories":3175},"c-input-2","Input with label",[162],{"name":3177,"type":576,"title":3178,"description":3178,"categories":3179},"c-input-3","Input with description",[162],{"name":3181,"type":576,"title":3182,"description":3182,"categories":3183},"c-input-4","Disabled input",[162],{"name":3185,"type":576,"title":3186,"description":3186,"categories":3187},"c-input-5","Input with error message",[162],{"name":3189,"type":576,"title":3190,"description":3190,"categories":3191},"c-input-6","Input with character counter",[162],{"name":3193,"type":576,"title":3194,"description":3194,"categories":3195},"c-input-7","Input with password type",[162],{"name":3197,"type":576,"title":3198,"description":3198,"categories":3199},"c-input-8","Input with phone type",[162],{"name":3201,"type":576,"title":3202,"description":3202,"categories":3203},"c-input-9","Input with URL type",[162],{"name":3205,"type":576,"title":3206,"description":3206,"categories":3207},"c-input-10","Input with number type",[162],{"name":3209,"type":576,"title":3210,"description":3210,"categories":3211},"c-input-11","Input with date type",[162],{"name":3213,"type":576,"title":3214,"description":3214,"categories":3215},"c-input-12","Input with file type",[162],{"name":3217,"type":576,"title":3218,"description":3218,"categories":3219},"c-input-13","Input with required indicator",[162],{"name":3221,"type":576,"title":3222,"description":3222,"categories":3223},"c-input-14","Input with time type",[162],{"name":3225,"type":576,"title":3226,"description":3226,"categories":3227},"c-input-15","Complex input form with multiple fields",[162],{"name":3229,"type":576,"title":3230,"description":3230,"categories":3231},"c-input-16","Input label with tooltip",[162],{"name":3233,"type":576,"title":3234,"description":3234,"categories":3235},"c-input-17","Input label with badge",[162],{"name":3237,"type":576,"title":3238,"description":3238,"categories":3239},"c-input-18","Input label with optional badge",[162],{"name":3241,"type":576,"title":3242,"description":3242,"categories":3243},"c-input-19","Input label with link and visibility toggle",[162],{"name":3245,"type":576,"title":3246,"description":3246,"categories":3247},"c-input-20","Input with horizontal orientation",[162],{"name":3249,"type":576,"title":3250,"description":3250,"categories":3251},"c-input-21","Input with multiple error messages",[162],{"name":3253,"type":576,"title":3254,"description":3254,"categories":3255},"c-input-22","Basic password strength with dynamic hint and icons",[162],{"name":3257,"type":576,"title":3258,"description":3258,"categories":3259},"c-input-23","Advanced password strength indicator with progress",[162],{"name":3261,"type":576,"title":3262,"description":3262,"categories":3263},"c-input-24","Input with pulsed background animation",[162],{"name":3265,"type":576,"title":3266,"description":3266,"categories":3267},"c-input-25","Input with custom focus ring",[162],{"name":3269,"type":576,"title":3270,"description":3270,"categories":3271},"c-input-26","Input with subtle background",[162],{"name":3273,"type":576,"title":3274,"description":3274,"categories":3275},"c-input-27","Input with bottom border only",[162],{"name":3277,"type":576,"title":3278,"description":3278,"categories":3279},"c-input-28","Color input",[162],{"name":3281,"type":576,"title":3282,"description":3282,"categories":3283},"c-input-29","Range input with value indicator",[162],{"name":3285,"type":576,"title":3286,"description":3286,"categories":3287},"c-input-30","Pill-shaped input",[162],{"name":3289,"type":576,"title":3290,"description":3290,"categories":3291},"c-input-31","Minimal input without borders or background",[162],{"name":3293,"type":576,"title":3294,"description":3294,"categories":3295},"c-item-1","Basic item with title and description",[176],{"name":3297,"type":576,"title":3298,"description":3298,"categories":3299},"c-item-2","Items with icon media and action buttons",[176],{"name":3301,"type":576,"title":3302,"description":3302,"categories":3303},"c-item-3","User item with avatar, info, and follow button",[176],{"name":3305,"type":576,"title":3306,"description":3306,"categories":3307},"c-item-4","Clickable navigation items with icons and chevron",[176],{"name":3309,"type":576,"title":3310,"description":3310,"categories":3311},"c-item-5","Item group with status badges",[176],{"name":3313,"type":576,"title":3314,"description":3314,"categories":3315},"c-item-6","File items with icon media, size, and actions",[176],{"name":3317,"type":576,"title":3318,"description":3318,"categories":3319},"c-item-7","Items with keyboard shortcuts",[176],{"name":3321,"type":576,"title":3322,"description":3322,"categories":3323},"c-item-8","Small-size items with badges",[176],{"name":3325,"type":576,"title":3326,"description":3326,"categories":3327},"c-item-9","Team members with roles and multiple actions",[176],{"name":3329,"type":576,"title":3330,"description":3330,"categories":3331},"c-item-10","Item with header and footer sections",[176],{"name":3333,"type":576,"title":3334,"description":3334,"categories":3335},"c-item-11","Integration items with connect actions",[176],{"name":3337,"type":576,"title":3338,"description":3338,"categories":3339},"c-item-12","Activity feed items with avatars and actions",[176],{"name":3341,"type":576,"title":3342,"description":3342,"categories":3343},"c-kanban-1","Kanban board with placeholder overlay",[180],{"name":3345,"type":576,"title":3346,"description":3346,"categories":3347},"c-kanban-2","Kanban board with dynamic overlay",[180],{"name":3349,"type":576,"title":3350,"description":3350,"categories":3351},"c-kanban-3","Kanban board with frame columns",[180],{"name":3353,"type":576,"title":3354,"description":3354,"categories":3355},"c-kanban-4","Minimal kanban with stacked frame",[180],{"name":3357,"type":576,"title":3358,"description":3358,"categories":3359},"c-kanban-5","Feature roadmap kanban with progress",[180],{"name":3361,"type":576,"title":3362,"description":3363,"categories":3364},"c-kanban-6","Kanban board persisted to a backend","Persist moves with onValueCommit, optimistic updates, and Sonner rollback",[180],{"name":3366,"type":576,"title":3367,"description":3367,"categories":3368},"c-kbd-1","Basic keyboard keys.",[184],{"name":3370,"type":576,"title":3371,"description":3371,"categories":3372},"c-kbd-2","Keyboard keys grouped together.",[184],{"name":3374,"type":576,"title":3375,"description":3375,"categories":3376},"c-kbd-3","Keyboard keys combined with icons.",[184],{"name":3378,"type":576,"title":3379,"description":3379,"categories":3380},"c-kbd-4","Keyboard key within an input.",[184],{"name":3382,"type":576,"title":3383,"description":3383,"categories":3384},"c-kbd-5","Keyboard shortcut in a tooltip.",[184],{"name":3386,"type":576,"title":3387,"description":3387,"categories":3388},"c-kbd-6","Keyboard shortcuts reference list",[184],{"name":3390,"type":576,"title":3391,"description":3391,"categories":3392},"c-label-1","Basic label.",[188],{"name":3394,"type":576,"title":3395,"description":3395,"categories":3396},"c-label-2","Label paired with a checkbox.",[188],{"name":3398,"type":576,"title":3399,"description":3399,"categories":3400},"c-label-3","Label for a textarea field.",[188],{"name":3402,"type":576,"title":3403,"description":3403,"categories":3404},"c-label-4","Label in a disabled state.",[188],{"name":3406,"type":576,"title":3407,"description":3407,"categories":3408},"c-label-5","Label with required indicator",[188],{"name":3410,"type":576,"title":3411,"description":3411,"categories":3412},"c-label-6","Label with optional indicator",[188],{"name":3414,"type":576,"title":3415,"description":3415,"categories":3416},"c-label-7","Label with tooltip info icon",[188],{"name":3418,"type":576,"title":3419,"description":3419,"categories":3420},"c-label-8","Label with badge indicator",[188],{"name":3422,"type":576,"title":3423,"description":3423,"categories":3424},"c-label-9","Label with character counter",[188],{"name":3426,"type":576,"title":3427,"description":3427,"categories":3428},"c-label-10","Label with helper description text",[188],{"name":3430,"type":576,"title":3431,"description":3431,"categories":3432},"c-label-11","Label with error state",[188],{"name":3434,"type":576,"title":3435,"description":3435,"categories":3436},"c-label-12","Label with inline edit toggle",[188],{"name":3438,"type":576,"title":3439,"description":3439,"categories":3440},"c-label-13","Label with status indicator dot",[188],{"name":3442,"type":576,"title":3443,"description":3443,"categories":3444},"c-menubar-1","Basic menubar.",[193],{"name":3446,"type":576,"title":3447,"description":3447,"categories":3448},"c-menubar-2","Menubar with dropdown submenus.",[193],{"name":3450,"type":576,"title":3451,"description":3451,"categories":3452},"c-menubar-3","Menubar with checkbox & radio items.",[193],{"name":3454,"type":576,"title":3455,"description":3455,"categories":3456},"c-menubar-4","Menubar with icons.",[193],{"name":3458,"type":576,"title":3459,"description":3459,"categories":3460},"c-menubar-5","Menubar for formatting and media.",[193],{"name":3462,"type":576,"title":3463,"description":3463,"categories":3464},"c-native-select-1","Basic native select.",[197],{"name":3466,"type":576,"title":3467,"description":3467,"categories":3468},"c-native-select-2","Native select with options grouped.",[197],{"name":3470,"type":576,"title":3471,"description":3471,"categories":3472},"c-native-select-3","Small native select.",[197],{"name":3474,"type":576,"title":3475,"description":3475,"categories":3476},"c-native-select-4","Native select embedded within a field.",[197],{"name":3478,"type":576,"title":3479,"description":3479,"categories":3480},"c-native-select-5","Disabled native select.",[197],{"name":3482,"type":576,"title":3483,"description":3483,"categories":3484},"c-native-select-6","Invalid native select.",[197],{"name":3486,"type":576,"title":3487,"description":3487,"categories":3488},"c-navigation-menu-1","Basic navigation menu.",[201],{"name":3490,"type":576,"title":3491,"description":3491,"categories":3492},"c-navigation-menu-2","Navigation menu with grid layout.",[201],{"name":3494,"type":576,"title":3495,"description":3495,"categories":3496},"c-navigation-menu-3","Navigation menu with icons.",[201],{"name":3498,"type":576,"title":3499,"description":3499,"categories":3500},"c-navigation-menu-4","Navigation menu with grid layout and learn more button.",[201],{"name":3502,"type":576,"title":3503,"description":3503,"categories":3504},"c-number-field-1","Basic number field",[205],{"name":3506,"type":576,"title":3503,"description":3503,"categories":3507},"c-number-field-2",[205],{"name":3509,"type":576,"title":3503,"description":3503,"categories":3510},"c-number-field-3",[205],{"name":3512,"type":576,"title":3513,"description":3513,"categories":3514},"c-number-field-4","Number filed wiith buttons on right",[205],{"name":3516,"type":576,"title":3517,"description":3517,"categories":3518},"c-number-field-5","Number field with spinner buttons",[205],{"name":3520,"type":576,"title":3521,"description":3521,"categories":3522},"c-number-field-6","Number field in form",[205],{"name":3524,"type":576,"title":3525,"description":3525,"categories":3526},"c-pagination-1","Basic pagination.",[209],{"name":3528,"type":576,"title":3529,"description":3529,"categories":3530},"c-pagination-2","Pagination without labels.",[209],{"name":3532,"type":576,"title":3533,"description":3533,"categories":3534},"c-pagination-3","Pagination with hover effect.",[209],{"name":3536,"type":576,"title":3537,"description":3537,"categories":3538},"c-pagination-4","Pagination with circle buttons.",[209],{"name":3540,"type":576,"title":3541,"description":3541,"categories":3542},"c-pagination-5","Pagination with go-to-page input",[209],{"name":3544,"type":576,"title":3545,"description":3545,"categories":3546},"c-pagination-6","Numbered pagination in card",[209],{"name":3548,"type":576,"title":3549,"description":3549,"categories":3550},"c-pagination-7","Pagination with arrows buttons",[209],{"name":3552,"type":576,"title":3553,"description":3553,"categories":3554},"c-pagination-8","Pagination with page info on center",[209],{"name":3556,"type":576,"title":3557,"description":3557,"categories":3558},"c-pagination-9","Pagination with page info on left",[209],{"name":3560,"type":576,"title":3561,"description":3561,"categories":3562},"c-pagination-10","Pagination with ellipsis indicator",[209],{"name":3564,"type":576,"title":3565,"description":3565,"categories":3566},"c-pagination-11","Pagination with outline style buttons",[209],{"name":3568,"type":576,"title":3569,"description":3569,"categories":3570},"c-pagination-12","Rows per page select with range info and first\u002Fprev\u002Fnext\u002Flast icons",[209],{"name":3572,"type":576,"title":3573,"description":3573,"categories":3574},"c-pagination-13","Pagination with page select dropdown and first\u002Flast navigation",[209],{"name":3576,"type":576,"title":3577,"description":3577,"categories":3578},"c-pagination-14","Numbered pagination with go-to-page input",[209],{"name":3580,"type":576,"title":3581,"description":3581,"categories":3582},"c-pagination-15","Pagination with page info, numbered pages, ellipsis, and per-page select",[209],{"name":3584,"type":576,"title":3585,"description":3585,"categories":3586},"c-phone-input-1","Basic phone input",[213],{"name":3588,"type":576,"title":3589,"description":3589,"categories":3590},"c-phone-input-2","Small phone input",[213],{"name":3592,"type":576,"title":3593,"description":3593,"categories":3594},"c-phone-input-3","Large phone input",[213],{"name":3596,"type":576,"title":3597,"description":3597,"categories":3598},"c-phone-input-4","Disabled phone input",[213],{"name":3600,"type":576,"title":3601,"description":3601,"categories":3602},"c-phone-input-5","Phone input with preset value",[213],{"name":3604,"type":576,"title":3605,"description":3605,"categories":3606},"c-phone-input-6","Phone input with error state",[213],{"name":3608,"type":576,"title":3609,"description":3609,"categories":3610},"c-phone-input-7","Phone input with specific default country",[213],{"name":3612,"type":576,"title":3613,"description":3613,"categories":3614},"c-phone-input-8","Read-only phone input",[213],{"name":3616,"type":576,"title":3617,"description":3617,"categories":3618},"c-popover-1","Basic popover.",[217],{"name":3620,"type":576,"title":3621,"description":3621,"categories":3622},"c-popover-2","Popovers opening from different sides.",[217],{"name":3624,"type":576,"title":3625,"description":3625,"categories":3626},"c-popover-3","Popover with form.",[217],{"name":3628,"type":576,"title":3629,"description":3629,"categories":3630},"c-popover-4","Popover alignment positions.",[217],{"name":3632,"type":576,"title":3633,"description":3633,"categories":3634},"c-popover-5","Popover within a dialog.",[217],{"name":3636,"type":576,"title":3637,"description":3637,"categories":3638},"c-popover-6","Popover with user profile details.",[217],{"name":3640,"type":576,"title":3641,"description":3641,"categories":3642},"c-popover-7","Popover with custom content",[217],{"name":3644,"type":576,"title":3645,"description":3645,"categories":3646},"c-popover-8","Popover with media preview.",[217],{"name":3648,"type":576,"title":3649,"description":3649,"categories":3650},"c-popover-9","Relative time popover with timezone details",[217],{"name":3652,"type":576,"title":3653,"description":3653,"categories":3654},"c-popover-10","Popover with controls",[217],{"name":3656,"type":576,"title":3657,"description":3657,"categories":3658},"c-popover-11","Popover with navigation",[217],{"name":3660,"type":576,"title":3661,"description":3661,"categories":3662},"c-progress-1","Basic progress.",[221],{"name":3664,"type":576,"title":3665,"description":3665,"categories":3666},"c-progress-2","Small progress.",[221],{"name":3668,"type":576,"title":3669,"description":3669,"categories":3670},"c-progress-3","Large progress.",[221],{"name":3672,"type":576,"title":3673,"description":3673,"categories":3674},"c-progress-4","Progress bar with status messages.",[221],{"name":3676,"type":576,"title":3677,"description":3677,"categories":3678},"c-progress-5","File upload list with progress & status.",[221],{"name":3680,"type":576,"title":3681,"description":3681,"categories":3682},"c-progress-6","Progress bar with slider.",[221],{"name":3684,"type":576,"title":3685,"description":3685,"categories":3686},"c-progress-7","Multi-step progress indicator",[221],{"name":3688,"type":576,"title":3689,"description":3689,"categories":3690},"c-progress-8","Progress bar with custom colors.",[221],{"name":3692,"type":576,"title":3693,"description":3693,"categories":3694},"c-radio-group-1","Basic radio group",[225],{"name":3696,"type":576,"title":3697,"description":3697,"categories":3698},"c-radio-group-2","Disabled radio group",[225],{"name":3700,"type":576,"title":3701,"description":3701,"categories":3702},"c-radio-group-3","Radio with description",[225],{"name":3704,"type":576,"title":3705,"description":3705,"categories":3706},"c-radio-group-4","Invalid radio group",[225],{"name":3708,"type":576,"title":3709,"description":3709,"categories":3710},"c-radio-group-5","Colored radio group",[225],{"name":3712,"type":576,"title":3713,"description":3713,"categories":3714},"c-radio-group-6","Radio group with legend and description",[225],{"name":3716,"type":576,"title":3717,"description":3717,"categories":3718},"c-radio-group-7","Card radio group with descriptions",[225],{"name":3720,"type":576,"title":3721,"description":3721,"categories":3722},"c-radio-group-8","Radio group in card with separators",[225],{"name":3724,"type":576,"title":3725,"description":3725,"categories":3726},"c-radio-group-9","Radio group in card with icons",[225],{"name":3728,"type":576,"title":3729,"description":3729,"categories":3730},"c-radio-group-10","Radio group in frame",[225],{"name":3732,"type":576,"title":3733,"description":3733,"categories":3734},"c-radio-group-11","Radio group with grid layout",[225],{"name":3736,"type":576,"title":3737,"description":3737,"categories":3738},"c-radio-group-12","Avatar based card radio group",[225],{"name":3740,"type":576,"title":3741,"description":3741,"categories":3742},"c-radio-group-13","Payment method radio cards",[225],{"name":3744,"type":576,"title":3745,"description":3745,"categories":3746},"c-radio-group-14","Radio group with tooltip info",[225],{"name":3748,"type":576,"title":3749,"description":3749,"categories":3750},"c-radio-group-15","Radio group with badges",[225],{"name":3752,"type":576,"title":3753,"description":3753,"categories":3754},"c-radio-group-16","Inline horizontal radio group",[225],{"name":3756,"type":576,"title":3757,"description":3757,"categories":3758},"c-radio-group-17","Pricing plan radio cards",[225],{"name":3760,"type":576,"title":3761,"description":3761,"categories":3762},"c-rating-1","Basic rating.",[229],{"name":3764,"type":576,"title":3765,"description":3765,"categories":3766},"c-rating-2","Rating with decimal value.",[229],{"name":3768,"type":576,"title":3769,"description":3769,"categories":3770},"c-rating-3","Rating with show value.",[229],{"name":3772,"type":576,"title":3773,"description":3773,"categories":3774},"c-rating-4","Rating with editable.",[229],{"name":3776,"type":576,"title":3777,"description":3777,"categories":3778},"c-rating-5","Rating with size.",[229],{"name":3780,"type":576,"title":3781,"description":3781,"categories":3782},"c-rating-6","Rating with review summary",[229],{"name":3784,"type":576,"title":3785,"description":3785,"categories":3786},"c-rating-7","Interactive half-star rating",[229],{"name":3788,"type":576,"title":3789,"description":3789,"categories":3790},"c-rating-8","Emoji reaction rating",[229],{"name":3792,"type":576,"title":3793,"description":3793,"categories":3794},"c-rating-9","Rating with review text input",[229],{"name":3796,"type":576,"title":3797,"description":3797,"categories":3798},"c-resizable-1","Horizontal resizable layout",[234],{"name":3800,"type":576,"title":3801,"description":3801,"categories":3802},"c-resizable-2","Vertical resizable layout.",[234],{"name":3804,"type":576,"title":3805,"description":3805,"categories":3806},"c-resizable-3","Nested mixed-direction resizable layout.",[234],{"name":3808,"type":576,"title":3809,"description":3809,"categories":3810},"c-resizable-4","Nested resizable",[234],{"name":3812,"type":576,"title":3813,"description":3813,"categories":3814},"c-resizable-5","Handle with animated pill indicator",[234],{"name":3816,"type":576,"title":3817,"description":3817,"categories":3818},"c-resizable-6","Handle pill with spring scale on drag",[234],{"name":3820,"type":576,"title":3821,"description":3821,"categories":3822},"c-resizable-7","Handle with large capsule expansion on drag",[234],{"name":3824,"type":576,"title":3825,"description":3825,"categories":3826},"c-resizable-8","Nested layout with animated pill handles",[234],{"name":3828,"type":576,"title":3829,"description":3829,"categories":3830},"c-resizable-9","Three-panel layout with animated pill handles",[234],{"name":3832,"type":576,"title":3833,"description":3833,"categories":3834},"c-resizable-10","State-tracked resizable layout.",[234],{"name":3836,"type":576,"title":3837,"description":3837,"categories":3838},"c-scroll-area-1","Basic scroll area",[238],{"name":3840,"type":576,"title":3841,"description":3841,"categories":3842},"c-scroll-area-2","Scroll area with horizontal scrollbar",[238],{"name":3844,"type":576,"title":3845,"description":3845,"categories":3846},"c-scroll-area-3","Scroll area with both scrollbars",[238],{"name":3848,"type":576,"title":3849,"description":3849,"categories":3850},"c-scroll-area-4","Scroll area with fade effect",[238],{"name":3852,"type":576,"title":3853,"description":3853,"categories":3854},"c-scroll-area-5","Scroll area with scrollbar gutter",[238],{"name":3856,"type":576,"title":3857,"description":3857,"categories":3858},"c-scrollspy-1","Basic scrollspy",[242],{"name":3860,"type":576,"title":3837,"description":3837,"categories":3861},"c-scrollspy-2",[242],{"name":3863,"type":576,"title":3864,"description":3864,"categories":3865},"c-select-1","A basic select component with a list of options",[247],{"name":3867,"type":576,"title":3868,"description":3868,"categories":3869},"c-select-2","Select with icons and placeholder state",[247],{"name":3871,"type":576,"title":3872,"description":3872,"categories":3873},"c-select-3","Select component with grouped options and labels",[247],{"name":3875,"type":576,"title":3876,"description":3876,"categories":3877},"c-select-4","Select component with a large list of options",[247],{"name":3879,"type":576,"title":3880,"description":3880,"categories":3881},"c-select-5","Select component with small trigger size",[247],{"name":3883,"type":576,"title":3884,"description":3884,"categories":3885},"c-select-6","Select component with a subscription plan style",[247],{"name":3887,"type":576,"title":3888,"description":3888,"categories":3889},"c-select-7","Select component with item alignment disabled",[247],{"name":3891,"type":576,"title":3892,"description":3892,"categories":3893},"c-select-8","Select component within a Field with label and description",[247],{"name":3895,"type":576,"title":3896,"description":3896,"categories":3897},"c-select-9","Select component in an invalid state",[247],{"name":3899,"type":576,"title":3900,"description":3900,"categories":3901},"c-select-10","Select component in a disabled state",[247],{"name":3903,"type":576,"title":3904,"description":3904,"categories":3905},"c-select-11","Select component inside a Dialog",[247],{"name":3907,"type":576,"title":3908,"description":3908,"categories":3909},"c-select-12","Select component with single selection",[247],{"name":3911,"type":576,"title":3912,"description":3912,"categories":3913},"c-select-13","Select component inline with Input",[247],{"name":3915,"type":576,"title":3916,"description":3916,"categories":3917},"c-select-14","Select component with Button side by side",[247],{"name":3919,"type":576,"title":3920,"description":3920,"categories":3921},"c-select-15","Select component with item alignment enabled",[247],{"name":3923,"type":576,"title":3924,"description":3924,"categories":3925},"c-select-16","Select with custom access level descriptions",[247],{"name":3927,"type":576,"title":3928,"description":3928,"categories":3929},"c-select-17","Select with colored bullets for status",[247],{"name":3931,"type":576,"title":3932,"description":3932,"categories":3933},"c-select-18","Select with user avatars",[247],{"name":3935,"type":576,"title":3936,"description":3936,"categories":3937},"c-select-19","Select with status badges",[247],{"name":3939,"type":576,"title":3940,"description":3940,"categories":3941},"c-select-20","Select with indicator on the left side",[247],{"name":3943,"type":576,"title":3944,"description":3944,"categories":3945},"c-select-21","Select with clear button",[247],{"name":3947,"type":576,"title":3948,"description":3948,"categories":3949},"c-select-22","Select with custom chevrons up down icon",[247],{"name":3951,"type":576,"title":3952,"description":3952,"categories":3953},"c-select-23","Select with colored status dots",[247],{"name":3955,"type":576,"title":3956,"description":3956,"categories":3957},"c-select-24","Select with grouped options and separators",[247],{"name":3959,"type":576,"title":3960,"description":3960,"categories":3961},"c-select-25","Country picker select with flag emojis",[247],{"name":3963,"type":576,"title":3964,"description":3964,"categories":3965},"c-select-26","Status filter select with colored dots",[247],{"name":3967,"type":576,"title":3968,"description":3968,"categories":3969},"c-select-27","Select with description text per option",[247],{"name":3971,"type":576,"title":3972,"description":3972,"categories":3973},"c-select-28","Timezone select with UTC offsets",[247],{"name":3975,"type":576,"title":3976,"description":3976,"categories":3977},"c-select-29","Select with avatar items for team member assignment",[247],{"name":3979,"type":576,"title":3980,"description":3980,"categories":3981},"c-select-30","Priority select with colored badges",[247],{"name":3983,"type":576,"title":3984,"description":3984,"categories":3985},"c-select-31","Font family select with preview",[247],{"name":3987,"type":576,"title":3988,"description":3988,"categories":3989},"c-select-32","Select with icon-labeled categories",[247],{"name":3991,"type":576,"title":3992,"description":3992,"categories":3993},"c-select-33","Size variant select with small trigger",[247],{"name":3995,"type":576,"title":3996,"description":3996,"categories":3997},"c-separator-1","Horizontal separator for content sections.",[252],{"name":3999,"type":576,"title":4000,"description":4000,"categories":4001},"c-separator-2","Vertical separator for inline elements.",[252],{"name":4003,"type":576,"title":4004,"description":4004,"categories":4005},"c-separator-3","Vertical separator groups related menu items.",[252],{"name":4007,"type":576,"title":4008,"description":4008,"categories":4009},"c-separator-4","Horizontal separator between items in a list.",[252],{"name":4011,"type":576,"title":4012,"description":4012,"categories":4013},"c-separator-5","Separator with centered text label",[252],{"name":4015,"type":576,"title":4016,"description":4016,"categories":4017},"c-separator-6","Order summary with separators",[252],{"name":4019,"type":576,"title":4020,"description":4020,"categories":4021},"c-sheet-1","Sheet containing a form for editing profile information.",[256],{"name":4023,"type":576,"title":4020,"description":4020,"categories":4024},"c-sheet-2",[256],{"name":4026,"type":576,"title":4027,"description":4027,"categories":4028},"c-sheet-3","Sheets that open from any side.",[256],{"name":4030,"type":576,"title":4031,"description":4031,"categories":4032},"c-sheet-4","Sheet with scrollable content",[256],{"name":4034,"type":576,"title":4035,"description":4035,"categories":4036},"c-skeleton-1","Skeleton loader for avatars and user info.",[260],{"name":4038,"type":576,"title":4039,"description":4039,"categories":4040},"c-skeleton-2","Skeleton loader for a card component.",[260],{"name":4042,"type":576,"title":4043,"description":4043,"categories":4044},"c-skeleton-3","Skeleton loader for text and paragraphs.",[260],{"name":4046,"type":576,"title":4047,"description":4047,"categories":4048},"c-skeleton-4","Skeleton loader for a form.",[260],{"name":4050,"type":576,"title":4051,"description":4051,"categories":4052},"c-skeleton-5","Skeleton loader for a data table.",[260],{"name":4054,"type":576,"title":4055,"description":4055,"categories":4056},"c-skeleton-6","Skeleton loader for a dashboard stats row",[260],{"name":4058,"type":576,"title":4059,"description":4059,"categories":4060},"c-skeleton-7","Skeleton loader for a list with actions",[260],{"name":4062,"type":576,"title":4063,"description":4063,"categories":4064},"c-skeleton-8","Skeleton loader for a card grid",[260],{"name":4066,"type":576,"title":4067,"description":4067,"categories":4068},"c-skeleton-9","Skeleton loader for a full profile page",[260],{"name":4070,"type":576,"title":4071,"description":4071,"categories":4072},"c-skeleton-10","Skeleton loader for chat messages",[260],{"name":4074,"type":576,"title":4075,"description":4075,"categories":4076},"c-slider-1","Basic slider.",[264],{"name":4078,"type":576,"title":4079,"description":4079,"categories":4080},"c-slider-2","Disabled slider",[264],{"name":4082,"type":576,"title":4083,"description":4083,"categories":4084},"c-slider-3","Slider with multiple discrete values",[264],{"name":4086,"type":576,"title":4087,"description":4087,"categories":4088},"c-slider-4","Vertical slider with range selection",[264],{"name":4090,"type":576,"title":4091,"description":4091,"categories":4092},"c-slider-5","Slider with controlled value tracking",[264],{"name":4094,"type":576,"title":4095,"description":4095,"categories":4096},"c-slider-6","Slider with range selection",[264],{"name":4098,"type":576,"title":4099,"description":4099,"categories":4100},"c-slider-7","Slider synced with number input",[264],{"name":4102,"type":576,"title":4103,"description":4103,"categories":4104},"c-slider-8","Color temperature slider",[264],{"name":4106,"type":576,"title":4107,"description":4107,"categories":4108},"c-slider-9","Slider with reference labels",[264],{"name":4110,"type":576,"title":4111,"description":4111,"categories":4112},"c-slider-10","Slider with tick marks",[264],{"name":4114,"type":576,"title":4115,"description":4115,"categories":4116},"c-slider-11","Slider with dynamic tooltip indicator",[264],{"name":4118,"type":576,"title":4119,"description":4119,"categories":4120},"c-slider-12","Rating slider with emoji feedback",[264],{"name":4122,"type":576,"title":4123,"description":4123,"categories":4124},"c-sonner-1","Basic toast notification.",[268],{"name":4126,"type":576,"title":4127,"description":4127,"categories":4128},"c-sonner-2","Toast notification with description.",[268],{"name":4130,"type":576,"title":4131,"description":4131,"categories":4132},"c-sonner-3","Toast positions",[268],{"name":4134,"type":576,"title":4135,"description":4135,"categories":4136},"c-sonner-4","Toast variants with colored icons",[268],{"name":4138,"type":576,"title":4139,"description":4139,"categories":4140},"c-sonner-5","Toast duration options",[268],{"name":4142,"type":576,"title":4143,"description":4143,"categories":4144},"c-sonner-6","Toast with action button",[268],{"name":4146,"type":576,"title":4147,"description":4147,"categories":4148},"c-sonner-7","Promise toast with loading state",[268],{"name":4150,"type":576,"title":4151,"description":4151,"categories":4152},"c-sonner-8","Toast with custom close and cancel buttons",[268],{"name":4154,"type":576,"title":4155,"description":4155,"categories":4156},"c-sonner-9","Toast with custom rich content",[268],{"name":4158,"type":576,"title":4159,"description":4159,"categories":4160},"c-sonner-10","Toast with upload progress simulation",[268],{"name":4162,"type":576,"title":4163,"description":4163,"categories":4164},"c-sonner-11","Toast with status alert markup",[268],{"name":4166,"type":576,"title":4167,"description":4167,"categories":4168},"c-sonner-12","Custom accent border toast",[268],{"name":4170,"type":576,"title":4171,"description":4171,"categories":4172},"c-sonner-13","Custom invert success toast",[268],{"name":4174,"type":576,"title":4175,"description":4175,"categories":4176},"c-sonner-14","Custom invert error toast with details",[268],{"name":4178,"type":576,"title":4179,"description":4179,"categories":4180},"c-sonner-15","Custom invert info toast with avatar",[268],{"name":4182,"type":576,"title":4183,"description":4183,"categories":4184},"c-sonner-16","Custom invert warning toast with countdown",[268],{"name":4186,"type":576,"title":4187,"description":4187,"categories":4188},"c-sonner-17","Multi-action invert toast with avatar group",[268],{"name":4190,"type":576,"title":4191,"description":4191,"categories":4192},"c-sonner-18","Toast with close button on top-right",[268],{"name":4194,"type":576,"title":4195,"description":4195,"categories":4196},"c-sonner-19","Toast with custom icon",[268],{"name":4198,"type":576,"title":4199,"description":4199,"categories":4200},"c-sonner-20","Custom integration toast",[268],{"name":4202,"type":576,"title":4203,"description":4203,"categories":4204},"c-sonner-21","Updatable toast with ID",[268],{"name":4206,"type":576,"title":4207,"description":4207,"categories":4208},"c-sortable-1","Sortable list of items with drag-and-drop",[273],{"name":4210,"type":576,"title":4211,"description":4211,"categories":4212},"c-sortable-2","Sortable list of items with grid layout",[273],{"name":4214,"type":576,"title":4215,"description":4215,"categories":4216},"c-sortable-3","Sortable list of items with nested layout",[273],{"name":4218,"type":576,"title":4219,"description":4219,"categories":4220},"c-sortable-4","Sortable playlist with frame",[273],{"name":4222,"type":576,"title":4223,"description":4223,"categories":4224},"c-sortable-5","Sortable settings priority with frame",[273],{"name":4226,"type":576,"title":4227,"description":4227,"categories":4228},"c-sortable-6","Sortable sidebar navigation with frame",[273],{"name":4230,"type":576,"title":4231,"description":4231,"categories":4232},"c-sortable-7","Sortable image gallery grid with frame",[273],{"name":4234,"type":576,"title":4235,"description":4236,"categories":4237},"c-sortable-8","Sortable list persisted to a backend","Persist the new order with onValueCommit, optimistic updates, and Sonner rollback",[273],{"name":4239,"type":576,"title":4240,"description":4240,"categories":4241},"c-spinner-1","Basic spinner.",[277],{"name":4243,"type":576,"title":4244,"description":4244,"categories":4245},"c-spinner-2","Spinners in buttons.",[277],{"name":4247,"type":576,"title":4248,"description":4248,"categories":4249},"c-spinner-3","Spinners in badges.",[277],{"name":4251,"type":576,"title":4252,"description":4252,"categories":4253},"c-spinner-4","Spinner in input group.",[277],{"name":4255,"type":576,"title":4256,"description":4256,"categories":4257},"c-spinner-5","Spinner in empty state.",[277],{"name":4259,"type":576,"title":4260,"description":4260,"categories":4261},"c-spinner-6","Button loading states",[277],{"name":4263,"type":576,"title":4264,"description":4264,"categories":4265},"c-spinner-7","Overlay loading spinner",[277],{"name":4267,"type":576,"title":4268,"description":4268,"categories":4269},"c-spinner-8","Full page loading state",[277],{"name":4271,"type":576,"title":4272,"description":4272,"categories":4273},"c-spinner-9","Inline loading text with spinner",[277],{"name":4275,"type":576,"title":4276,"description":4276,"categories":4277},"c-spinner-10","Animated loading dots",[277],{"name":4279,"type":576,"title":4280,"description":4280,"categories":4281},"c-spinner-11","Spinner overlay on card",[277],{"name":4283,"type":576,"title":4284,"description":4284,"categories":4285},"c-spinner-12","Spinner color variants",[277],{"name":4287,"type":576,"title":4288,"description":4288,"categories":4289},"c-stepper-1","Basic stepper",[281],{"name":4291,"type":576,"title":4292,"description":4292,"categories":4293},"c-stepper-2","Stepper with completed state",[281],{"name":4295,"type":576,"title":4296,"description":4296,"categories":4297},"c-stepper-3","Stepper with loading state",[281],{"name":4299,"type":576,"title":4300,"description":4300,"categories":4301},"c-stepper-4","Controlled stepper",[281],{"name":4303,"type":576,"title":4304,"description":4304,"categories":4305},"c-stepper-5","Stepper with title and indicator",[281],{"name":4307,"type":576,"title":4308,"description":4308,"categories":4309},"c-stepper-6","Stepper with progress bar indicator",[281],{"name":4311,"type":576,"title":4312,"description":4312,"categories":4313},"c-stepper-7","Stepper with icons and badges",[281],{"name":4315,"type":576,"title":4316,"description":4316,"categories":4317},"c-stepper-8","Stepper with descriptions",[281],{"name":4319,"type":576,"title":4320,"description":4320,"categories":4321},"c-stepper-9","Stepper with inline titles",[281],{"name":4323,"type":576,"title":4324,"description":4324,"categories":4325},"c-stepper-10","Stepper with content for each step",[281],{"name":4327,"type":576,"title":4328,"description":4328,"categories":4329},"c-stepper-11","Stepper with progress bar and titles",[281],{"name":4331,"type":576,"title":4332,"description":4332,"categories":4333},"c-stepper-12","Stepper with segmented progress bar",[281],{"name":4335,"type":576,"title":4336,"description":4336,"categories":4337},"c-stepper-13","Stepper with vertical orientation and descriptions",[281],{"name":4339,"type":576,"title":4340,"description":4340,"categories":4341},"c-stepper-14","Stepper with vertical orientation",[281],{"name":4343,"type":576,"title":4344,"description":4344,"categories":4345},"c-stepper-15","Stepper with vertical orientation and titles",[281],{"name":4347,"type":576,"title":4348,"description":4348,"categories":4349},"c-switch-1","Basic switch.",[285],{"name":4351,"type":576,"title":4352,"description":4352,"categories":4353},"c-switch-2","Switch with description.",[285],{"name":4355,"type":576,"title":4356,"description":4356,"categories":4357},"c-switch-3","Disabled switch.",[285],{"name":4359,"type":576,"title":4360,"description":4360,"categories":4361},"c-switch-4","Switch in different sizes.",[285],{"name":4363,"type":576,"title":4364,"description":4364,"categories":4365},"c-switch-5","Switch group",[285],{"name":4367,"type":576,"title":4368,"description":4368,"categories":4369},"c-switch-6","Switch list in card with separators",[285],{"name":4371,"type":576,"title":4372,"description":4372,"categories":4373},"c-switch-7","Switch list in card with icons",[285],{"name":4375,"type":576,"title":4376,"description":4376,"categories":4377},"c-switch-8","Switch in frame",[285],{"name":4379,"type":576,"title":4380,"description":4380,"categories":4381},"c-switch-9","Switch with tooltip info",[285],{"name":4383,"type":576,"title":4384,"description":4384,"categories":4385},"c-switch-10","Switch with badges",[285],{"name":4387,"type":576,"title":4388,"description":4388,"categories":4389},"c-switch-11","Colored switches",[285],{"name":4391,"type":576,"title":4392,"description":4392,"categories":4393},"c-switch-12","Destructive switch with confirmation text",[285],{"name":4395,"type":576,"title":4396,"description":4396,"categories":4397},"c-switch-13","Compact settings table with switches",[285],{"name":4399,"type":576,"title":4400,"description":4400,"categories":4401},"c-switch-14","Switch with descriptions in card grid",[285],{"name":4403,"type":576,"title":4404,"description":4404,"categories":4405},"c-table-1","Basic table.",[289],{"name":4407,"type":576,"title":4408,"description":4408,"categories":4409},"c-table-2","Table with footer.",[289],{"name":4411,"type":576,"title":4412,"description":4412,"categories":4413},"c-table-3","Table in card with status badges.",[289],{"name":4415,"type":576,"title":4416,"description":4416,"categories":4417},"c-table-4","Table card with actions column.",[289],{"name":4419,"type":576,"title":4420,"description":4420,"categories":4421},"c-table-5","Table with interactive elements.",[289],{"name":4423,"type":576,"title":4424,"description":4424,"categories":4425},"c-table-6","Team members table with avatars, roles, and status",[289],{"name":4427,"type":576,"title":4428,"description":4428,"categories":4429},"c-table-7","Orders table with customer info and payment status",[289],{"name":4431,"type":576,"title":4432,"description":4432,"categories":4433},"c-table-8","Projects table with team avatars and status",[289],{"name":4435,"type":576,"title":4436,"description":4436,"categories":4437},"c-table-9","Activity log table with user avatars and timestamps",[289],{"name":4439,"type":576,"title":4440,"description":4440,"categories":4441},"c-table-10","Subscriptions table with plan badges and billing info",[289],{"name":4443,"type":576,"title":4444,"description":4444,"categories":4445},"c-table-11","Product inventory table with stock badges and prices",[289],{"name":4447,"type":576,"title":4448,"description":4448,"categories":4449},"c-table-12","API keys table with permission badges and actions",[289],{"name":4451,"type":576,"title":4452,"description":4452,"categories":4453},"c-table-13","File manager table with owners and sharing badges",[289],{"name":4455,"type":576,"title":4456,"description":4456,"categories":4457},"c-table-14","Issues table with priority, assignee, and labels",[289],{"name":4459,"type":576,"title":4460,"description":4460,"categories":4461},"c-table-15","Leaderboard table with rank, score, and level badges",[289],{"name":4463,"type":576,"title":4464,"description":4464,"categories":4465},"c-table-16","Vertical table with employee profile details",[289],{"name":4467,"type":576,"title":4468,"description":4468,"categories":4469},"c-table-17","Vertical table with server configuration details",[289],{"name":4471,"type":576,"title":4472,"description":4472,"categories":4473},"c-tabs-1","Basic tabs.",[293],{"name":4475,"type":576,"title":4476,"description":4476,"categories":4477},"c-tabs-2","Tabs with line variant.",[293],{"name":4479,"type":576,"title":4480,"description":4480,"categories":4481},"c-tabs-3","Tabs with vertical orientation.",[293],{"name":4483,"type":576,"title":4484,"description":4484,"categories":4485},"c-tabs-4","Tabs with vertical orientation and line variant.",[293],{"name":4487,"type":576,"title":4488,"description":4488,"categories":4489},"c-tabs-5","Tabs with icons.",[293],{"name":4491,"type":576,"title":4492,"description":4492,"categories":4493},"c-tabs-6","Tabs with icons",[293],{"name":4495,"type":576,"title":4496,"description":4496,"categories":4497},"c-tabs-7","Tabs with badge counts",[293],{"name":4499,"type":576,"title":4500,"description":4500,"categories":4501},"c-tabs-8","Tabs with icons and line variant",[293],{"name":4503,"type":576,"title":4504,"description":4504,"categories":4505},"c-tabs-9","Segmented control tabs",[293],{"name":4507,"type":576,"title":4508,"description":4508,"categories":4509},"c-textarea-1","Basic textarea.",[297],{"name":4511,"type":576,"title":4512,"description":4512,"categories":4513},"c-textarea-2","Invalid textarea.",[297],{"name":4515,"type":576,"title":4516,"description":4516,"categories":4517},"c-textarea-3","Textarea with label.",[297],{"name":4519,"type":576,"title":4520,"description":4520,"categories":4521},"c-textarea-4","Textarea with description.",[297],{"name":4523,"type":576,"title":4524,"description":4524,"categories":4525},"c-textarea-5","Disabled textarea.",[297],{"name":4527,"type":576,"title":4528,"description":4528,"categories":4529},"c-textarea-6","Auto-resize textarea with character count",[297],{"name":4531,"type":576,"title":4532,"description":4532,"categories":4533},"c-timeline-1","Basic timeline.",[301],{"name":4535,"type":576,"title":4536,"description":4536,"categories":4537},"c-timeline-2","Timeline with roadmap.",[301],{"name":4539,"type":576,"title":4540,"description":4540,"categories":4541},"c-timeline-3","Timeline with order status.",[301],{"name":4543,"type":576,"title":4544,"description":4544,"categories":4545},"c-timeline-4","Timeline with git activity.",[301],{"name":4547,"type":576,"title":4548,"description":4548,"categories":4549},"c-timeline-5","Timeline with milestones.",[301],{"name":4551,"type":576,"title":4552,"description":4552,"categories":4553},"c-timeline-6","Timeline with pipeline steps.",[301],{"name":4555,"type":576,"title":4556,"description":4556,"categories":4557},"c-timeline-7","Timeline with roadmap items.",[301],{"name":4559,"type":576,"title":4560,"description":4560,"categories":4561},"c-timeline-8","Vertical timeline",[301],{"name":4563,"type":576,"title":4564,"description":4564,"categories":4565},"c-timeline-9","Horizontal timeline with leading labels",[301],{"name":4567,"type":576,"title":4568,"description":4568,"categories":4569},"c-timeline-10","Deployment log timeline",[301],{"name":4571,"type":576,"title":4572,"description":4572,"categories":4573},"c-timeline-11","Activity feed timeline with user avatars",[301],{"name":4575,"type":576,"title":4576,"description":4576,"categories":4577},"c-timeline-12","Compact horizontal milestone timeline",[301],{"name":4579,"type":576,"title":4580,"description":4580,"categories":4581},"c-toggle-group-1","Basic toggle group.",[309],{"name":4583,"type":576,"title":4584,"description":4584,"categories":4585},"c-toggle-group-2","Toggle group with outline variant.",[309],{"name":4587,"type":576,"title":4588,"description":4588,"categories":4589},"c-toggle-group-3","Toggle groups in different sizes.",[309],{"name":4591,"type":576,"title":4592,"description":4592,"categories":4593},"c-toggle-group-4","Toggle group with custom spacing.",[309],{"name":4595,"type":576,"title":4596,"description":4596,"categories":4597},"c-toggle-group-5","Toggle group with filter control.",[309],{"name":4599,"type":576,"title":4600,"description":4600,"categories":4601},"c-toggle-group-6","Toggle group with vertical orientation.",[309],{"name":4603,"type":576,"title":4604,"description":4604,"categories":4605},"c-toggle-group-7","Toggle group for text alignment",[309],{"name":4607,"type":576,"title":4608,"description":4608,"categories":4609},"c-toggle-group-8","Toggle group for view mode switching",[309],{"name":4611,"type":576,"title":4612,"description":4612,"categories":4613},"c-toggle-group-9","Vertical toggle group for settings",[309],{"name":4615,"type":576,"title":4616,"description":4616,"categories":4617},"c-toggle-group-10","Toggle group for color theme",[309],{"name":4619,"type":576,"title":4620,"description":4620,"categories":4621},"c-toggle-group-11","Toggle group for font size",[309],{"name":4623,"type":576,"title":4624,"description":4624,"categories":4625},"c-toggle-group-12","Toggle group for time range filter",[309],{"name":4627,"type":576,"title":4628,"description":4628,"categories":4629},"c-toggle-group-13","Toggle group for notification channels",[309],{"name":4631,"type":576,"title":4632,"description":4632,"categories":4633},"c-toggle-group-14","Toggle group for layout columns",[309],{"name":4635,"type":576,"title":4636,"description":4636,"categories":4637},"c-toggle-group-15","Toggle group for sort direction",[309],{"name":4639,"type":576,"title":4640,"description":4640,"categories":4641},"c-toggle-group-16","Toggle group for pricing tier",[309],{"name":4643,"type":576,"title":4644,"description":4644,"categories":4645},"c-toggle-1","Basic toggle.",[305],{"name":4647,"type":576,"title":4648,"description":4648,"categories":4649},"c-toggle-2","Toggle buttons with outline variant.",[305],{"name":4651,"type":576,"title":4652,"description":4652,"categories":4653},"c-toggle-3","Toggle buttons in different sizes.",[305],{"name":4655,"type":576,"title":4656,"description":4656,"categories":4657},"c-toggle-4","Toggle with button and icon.",[305],{"name":4659,"type":576,"title":4660,"description":4660,"categories":4661},"c-toggle-5","Disabled toggle.",[305],{"name":4663,"type":576,"title":4664,"description":4664,"categories":4665},"c-toggle-6","Toggle with icon.",[305],{"name":4667,"type":576,"title":4668,"description":4668,"categories":4669},"c-toggle-7","Toggle with notification count badge",[305],{"name":4671,"type":576,"title":4672,"description":4672,"categories":4673},"c-toggle-8","Toggle size variants showcase",[305],{"name":4675,"type":576,"title":4676,"description":4676,"categories":4677},"c-toggle-9","Toggle with icon swap on press",[305],{"name":4679,"type":576,"title":4680,"description":4680,"categories":4681},"c-toggle-10","Toggle with text label that changes",[305],{"name":4683,"type":576,"title":4684,"description":4684,"categories":4685},"c-toggle-11","Social media reaction toggles",[305],{"name":4687,"type":576,"title":4688,"description":4688,"categories":4689},"c-toggle-12","Toggle with tooltip",[305],{"name":4691,"type":576,"title":4692,"description":4692,"categories":4693},"c-toggle-13","Disabled toggle states",[305],{"name":4695,"type":576,"title":4696,"description":4696,"categories":4697},"c-toggle-14","Toggle as mute\u002Funmute button",[305],{"name":4699,"type":576,"title":4700,"description":4700,"categories":4701},"c-tooltip-1","Basic tooltip.",[314],{"name":4703,"type":576,"title":4704,"description":4704,"categories":4705},"c-tooltip-2","Tooltips opening from different sides.",[314],{"name":4707,"type":576,"title":4708,"description":4708,"categories":4709},"c-tooltip-3","Tooltip with icon trigger.",[314],{"name":4711,"type":576,"title":4712,"description":4712,"categories":4713},"c-tooltip-4","Tooltip with keyboard shortcut.",[314],{"name":4715,"type":576,"title":4716,"description":4716,"categories":4717},"c-tooltip-5","Tooltip on notification bell with badge count",[314],{"name":4719,"type":576,"title":4720,"description":4720,"categories":4721},"c-tooltip-6","Tooltip with status badge",[314],{"name":4723,"type":576,"title":4724,"description":4724,"categories":4725},"c-tooltip-7","Tooltip with icon and description",[314],{"name":4727,"type":576,"title":4728,"description":4728,"categories":4729},"c-tooltip-8","Toolbar with tooltip actions",[314],{"name":4731,"type":576,"title":4732,"description":4732,"categories":4733},"c-tooltip-9","Tooltip with warning badge",[314],{"name":4735,"type":576,"title":4736,"description":4736,"categories":4737},"c-tooltip-10","Tooltip with feature badge and upgrade link",[314],{"name":4739,"type":576,"title":4740,"description":4740,"categories":4741},"c-tooltip-11","Tooltip with avatar and role badge",[314],{"name":4743,"type":576,"title":4744,"description":4744,"categories":4745},"c-tooltip-12","Tooltip on disabled button with wrapper",[314],{"name":4747,"type":576,"title":4748,"description":4748,"categories":4749},"c-tooltip-13","Tooltip with online status indicator",[314],{"name":4751,"type":576,"title":4752,"description":4752,"categories":4753},"c-tooltip-14","Tooltip with file info and badges",[314],{"name":4755,"type":576,"title":4756,"description":4756,"categories":4757},"c-tooltip-15","Tooltip with label badges",[314],{"name":4759,"type":576,"title":4760,"description":4760,"categories":4761},"c-tooltip-16","Tooltip with action button inside",[314],{"name":4763,"type":576,"title":4764,"description":4764,"categories":4765},"c-tree-1","Basic tree.",[318],{"name":4767,"type":576,"title":4768,"description":4768,"categories":4769},"c-tree-2","Tree with indented lines.",[318],{"name":4771,"type":576,"title":4772,"description":4772,"categories":4773},"c-tree-3","Tree with custom indent.",[318],{"name":4775,"type":576,"title":4772,"description":4772,"categories":4776},"c-tree-4",[318],{"name":4778,"type":576,"title":4779,"description":4779,"categories":4780},"c-tree-5","File explorer tree with type icons",[318],{"name":4782,"type":576,"title":4783,"description":4783,"categories":4784},"c-tree-6","Organization chart tree with avatars",[318],{"name":4786,"type":576,"title":4787,"description":4787,"categories":4788},"c-tree-7","Permissions tree with checkboxes",[318],{"$schema":4790,"name":144,"type":480,"title":145,"description":506,"categories":4791,"registryDependencies":4792,"dependencies":4793,"files":4797,"meta":4926},"https:\u002F\u002Fshadcn-vue.com\u002Fschema\u002Fregistry-item.json",[483],[45,55,73,115,217,542],[4794,4795,4796],"@date-fns\u002Ftz","date-fns","reka-ui",[4798,4802,4806,4810,4814,4818,4822,4826,4830,4834,4838,4842,4846,4850,4854,4858,4862,4866,4870,4874,4878,4882,4886,4890,4894,4898,4902,4906,4910,4914,4918,4922],{"path":4799,"type":480,"target":4800,"content":4801},"src\u002Freui\u002Fgantt\u002FGantt.vue","components\u002Freui\u002Fgantt\u002FGantt.vue","\u003Cscript setup lang=\"ts\" generic=\"TData = unknown\">\n\u002F**\n * Порт ReUI Gantt (registry-reui\u002Fbases\u002Fradix\u002Freui\u002Fgantt\u002Fgantt.tsx, MIT).\n * Headless-стор — в context.ts (useGanttState); здесь только root-провайдер\n * + контейнер, композиция \u003CGantt>\u003CGanttNav\u002F>\u003CGanttView\u002F>\u003C\u002FGantt>.\n *\n * Упрощение относительно оригинала: `splitOptions`\u002F`shallowEqualRecord`\u002F\n * `viewConfigRef` там существуют ради стабильности ССЫЛКИ на объект\n * viewConfig между рендерами React (иначе на каждый инлайновый JSX все\n * строки дерева переподписывались бы на новый контекст) — у Vue `computed`\n * уже не пересчитывается, пока не изменится реально прочитанное поле, так\n * что весь этот механизм не нужен: `viewConfig` ниже — обычный `computed`\n * по списку известных ключей.\n *\n * `apiRef` (императивный доступ снаружи дерева) не портирован — Vue\n * заменяет его `defineExpose`, но текущий кейс его не использует; добавить\n * тривиально, когда понадобится.\n *\u002F\nimport type { ComputedRef, HTMLAttributes } from \"vue\"\nimport { computed, provide } from \"vue\"\nimport { Primitive } from \"reka-ui\"\nimport { cn } from \"@\u002Flib\u002Futils\"\nimport {\n  DEFAULT_VIEW_CONFIG,\n  GanttContextKey,\n  GanttViewConfigContextKey,\n  useGanttState,\n  type GanttInstance,\n  type GanttViewConfig,\n  type UseGanttStateOptions,\n} from \".\u002Fcontext\"\n\ndefineOptions({ inheritAttrs: true })\n\n\u002F\u002F withDefaults(..., { field: undefined }) on every top-level `boolean` field:\n\u002F\u002F Vue casts an ABSENT prop whose type is plain `boolean` to `false` (not\n\u002F\u002F `undefined`) unless it has an explicit `default` key - `false !== undefined`\n\u002F\u002F then survives the VIEW_CONFIG_KEYS merge below and silently overwrites\n\u002F\u002F DEFAULT_VIEW_CONFIG's `true` defaults (see docs\u002FPORTING.md §32, same bug\n\u002F\u002F already found once in EventCalendar.vue). `default: undefined` suppresses\n\u002F\u002F the cast so \"not passed\" reads as `undefined` again, as the type promises.\nconst props = withDefaults(\n  defineProps\u003C\n    UseGanttStateOptions\u003CTData> &\n      Partial\u003CGanttViewConfig\u003CTData>> & {\n        class?: HTMLAttributes[\"class\"]\n        \u002F** Adopt a hoisted useGanttState instance; option props are then ignored. *\u002F\n        calendar?: GanttInstance\u003CTData>\n        asChild?: boolean\n      }\n  >(),\n  {\n    loading: undefined,\n    nowIndicator: undefined,\n    displayScheduleHint: undefined,\n    dragCreate: undefined,\n    displayCreateTaskHint: undefined,\n    zoomControl: undefined,\n    wheelZoom: undefined,\n    offscreenIndicators: undefined,\n    infiniteScroll: undefined,\n    stickyNav: undefined,\n    rowCheckboxes: undefined,\n    parentScheduling: undefined,\n    summaryBars: undefined,\n    offDays: undefined,\n    asChild: false,\n  }\n)\n\nconst VIEW_CONFIG_KEYS = [\n  \"nowIndicator\",\n  \"interval\",\n  \"scrollbars\",\n  \"displayScheduleHint\",\n  \"initialCenter\",\n  \"displayCreateTaskHint\",\n  \"dragCreate\",\n  \"zoomControl\",\n  \"wheelZoom\",\n  \"navButtonVariant\",\n  \"navButtonSize\",\n  \"offDays\",\n  \"columns\",\n  \"columnsMenu\",\n  \"treePanel\",\n  \"metrics\",\n  \"timelineLines\",\n  \"barLabel\",\n  \"offscreenIndicators\",\n  \"infiniteScroll\",\n  \"zoomRange\",\n  \"stickyNav\",\n  \"rowCheckboxes\",\n  \"selectedRows\",\n  \"onSelectedRowsChange\",\n  \"collapsedGroups\",\n  \"defaultCollapsedGroups\",\n  \"onCollapsedGroupsChange\",\n  \"zoom\",\n  \"defaultZoom\",\n  \"onZoomChange\",\n  \"parentScheduling\",\n  \"summaryBars\",\n  \"scheduleMode\",\n  \"rowAlign\",\n  \"classNames\",\n  \"renderEvent\",\n  \"renderEventMenu\",\n  \"renderResourceLabel\",\n  \"renderResourceMenu\",\n  \"renderNoResources\",\n  \"renderDragPreview\",\n  \"renderResizeIndicator\",\n  \"renderScheduleHint\",\n  \"renderSummary\",\n  \"getSummaryProgress\",\n] as const\n\nconst viewConfig = computed\u003CGanttViewConfig\u003CTData>>(() => {\n  const merged = { ...DEFAULT_VIEW_CONFIG } as unknown as GanttViewConfig\u003CTData>\n  const source = props as unknown as Record\u003Cstring, unknown>\n  for (const key of VIEW_CONFIG_KEYS) {\n    const value = source[key]\n    if (value !== undefined) (merged as unknown as Record\u003Cstring, unknown>)[key] = value\n  }\n  return merged\n})\n\n\u002F\u002F `own` is always constructed (even when adopting `calendar`): it is a plain\n\u002F\u002F composable call, cheap, and keeps this component free of a conditional\n\u002F\u002F hook-call footgun. Only its RESULT is discarded when `calendar` is set.\nconst own = useGanttState\u003CTData>(props as UseGanttStateOptions\u003CTData>)\nconst instance: GanttInstance\u003CTData> = props.calendar ?? own\n\nprovide(GanttContextKey, instance as unknown as GanttInstance\u003Cunknown>)\nprovide(GanttViewConfigContextKey, viewConfig as unknown as ComputedRef\u003CGanttViewConfig\u003Cunknown>>)\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003CPrimitive\n    as=\"div\"\n    :as-child=\"asChild ?? false\"\n    data-slot=\"gantt\"\n    :class=\"cn('text-foreground flex min-h-0 min-w-0 flex-col text-xs', props.class)\"\n  >\n    \u003Cslot \u002F>\n    \u003Cdiv data-slot=\"gantt-announcer\" aria-live=\"polite\" class=\"sr-only\" \u002F>\n  \u003C\u002FPrimitive>\n\u003C\u002Ftemplate>\n",{"path":4803,"type":480,"target":4804,"content":4805},"src\u002Freui\u002Fgantt\u002FGanttBar.vue","components\u002Freui\u002Fgantt\u002FGanttBar.vue","\u003Cscript setup lang=\"ts\" generic=\"TData = unknown\">\n\u002F**\n * Порт ReUI GanttBar (registry-reui\u002Fbases\u002Fradix\u002Freui\u002Fgantt\u002Fgantt-bar.tsx, MIT).\n *\n * ponytail: перенесены выбор\u002Fклики\u002Fдефолтный контент\u002Fпрогресс-заливка\u002F\n * ручки ресайза (сама разметка `data-slot=\"gantt-resize-handle\"`, реальные\n * `data-*` атрибуты состояния). ЧТО ПРОПУЩЕНО в этом первом срезе и почему:\n *  - `Tooltip`\u002F`TooltipProvider` (наведение -> диапазон дат) — не влияет на\n *    статичный скриншот (открывается только по hover), тот же выбор уже\n *    сделан для `EventCalendarEvent.vue` (см. его шапку, `eventTooltip`).\n *    Добавить как обёртку из `..\u002F..\u002Fui\u002Ftooltip`, когда появится\n *    интеракционный кейс.\n *  - `ContextMenu` (`viewConfig.renderEventMenu`) — рендерится только когда\n *    потребитель передал `renderEventMenu`; ни один текущий кейс этого не\n *    делает, поэтому условная ветка не реализована (просто нет `menu`).\n *  - Перетаскивание (`gestures.beginMove`\u002F`beginResize`) — теперь реальное:\n *    `.\u002Fgestures.ts` содержит полный порт `gantt-dnd.tsx` (ADR-002\n *    неприменим, там нативные pointer-события, не dnd-kit).\n * `IconPlaceholder` (docs\u002FPORTING.md §5) заменён инлайновым `\u003Csvg>` (пути\n * lucide \"repeat\"\u002F\"check\" v0.545.0), как и во всех остальных портах.\n *\u002F\nimport { computed, provide, type CSSProperties, type HTMLAttributes } from \"vue\"\nimport { Primitive, useForwardExpose } from \"reka-ui\"\nimport { cn } from \"@\u002Flib\u002Futils\"\nimport {\n  GanttBarContextKey,\n  useGantt,\n  useGanttSelector,\n  useGanttViewConfig,\n  type GanttBarContextValue,\n} from \".\u002Fcontext\"\nimport { useGanttGestures, wasRecentDrag } from \".\u002Fgestures\"\nimport { flattenResources, toZoned } from \".\u002Flib\"\nimport type { GanttSegment } from \".\u002Ftypes\"\n\nconst props = withDefaults(\n  defineProps\u003C{\n    class?: HTMLAttributes[\"class\"]\n    segment: GanttSegment\u003CTData>\n    \u002F**\n     * The title renders beside the bar (view-owned), so the default inner\n     * content is suppressed. Explicit children and renderEvent still win.\n     *\u002F\n    labelOutside?: boolean\n    \u002F** The owning row's title for the aria-label; falls back to a tree lookup. *\u002F\n    rowTitle?: string\n    asChild?: boolean\n  }>(),\n  { labelOutside: false, asChild: false }\n)\n\ndefineSlots\u003C{ default?(): unknown }>()\n\nconst { forwardRef } = useForwardExpose()\nconst instance = useGantt\u003CTData>()\nconst viewConfig = useGanttViewConfig\u003CTData>()\nconst gestures = useGanttGestures\u003CTData>()\nconst settings = instance.settings\n\nconst occurrence = computed(() => props.segment.occurrence)\nconst event = computed(() => occurrence.value.event)\n\nconst isSelected = useGanttSelector\u003CTData, boolean>(\n  (state) => state.selection.eventKeys.includes(occurrence.value.key),\n  { calendar: instance }\n)\nconst isDragging = useGanttSelector\u003CTData, boolean>(\n  (state) => state.drag?.occurrence.key === occurrence.value.key,\n  { calendar: instance }\n)\nconst dragKind = useGanttSelector\u003CTData, string | null>(\n  (state) => (state.drag?.occurrence.key === occurrence.value.key ? state.drag.kind : null),\n  { calendar: instance }\n)\n\nconst progress = computed(() =>\n  typeof event.value.progress === \"number\"\n    ? Math.min(Math.max(Math.round(event.value.progress), 0), 100)\n    : null\n)\n\nconst renderProps = computed(() => ({\n  occurrence: occurrence.value,\n  segment: props.segment,\n  isDragging: isDragging.value,\n  isSelected: isSelected.value,\n}))\nconst customContent = computed(() => viewConfig.value.renderEvent?.(renderProps.value))\nconst consumerOwnsContent = computed(() => !!viewConfig.value.renderEvent)\n\nconst timeLabel = computed(() =>\n  settings.value.i18n.functions.formatEventTime(\n    toZoned(occurrence.value.start, settings.value.timeZone),\n    toZoned(occurrence.value.end, settings.value.timeZone),\n    occurrence.value.allDay,\n    settings.value.locale\n  )\n)\n\nconst rowTitle = computed(\n  () =>\n    props.rowTitle ??\n    (event.value.resourceId\n      ? flattenResources(settings.value.resources).find(\n          ({ resource }) => resource.id === event.value.resourceId\n        )?.resource.title\n      : undefined)\n)\n\nconst showResize = computed(() => gestures.canResize(props.segment))\n\nconst ariaLabel = computed(() =>\n  settings.value.i18n.functions.formatEventAriaLabel({\n    title: event.value.title,\n    timeLabel: timeLabel.value,\n    rowTitle: rowTitle.value,\n    progressLabel:\n      progress.value !== null ? settings.value.i18n.labels.progress(progress.value) : undefined,\n    continues: props.segment.continuesBefore || props.segment.continuesAfter,\n  })\n)\n\nconst barStyle = computed(\n  () =>\n    ({\n      \"--gantt-event-color\": event.value.color ?? \"var(--color-primary)\",\n    }) as CSSProperties\n)\n\nfunction onPointerDown(e: PointerEvent) {\n  e.stopPropagation()\n  gestures.beginMove(e, props.segment)\n}\n\nfunction onClick(e: MouseEvent) {\n  e.stopPropagation()\n  if (wasRecentDrag()) return\n  instance.api.selectEvent(occurrence.value.key)\n  settings.value.onEventClick?.(occurrence.value, e)\n}\n\nfunction onDoubleClick(e: MouseEvent) {\n  e.stopPropagation()\n  settings.value.onEventDoubleClick?.(occurrence.value, e)\n}\n\nprovide(GanttBarContextKey, {\n  get occurrence() {\n    return occurrence.value\n  },\n  get segment() {\n    return props.segment\n  },\n  get isDragging() {\n    return isDragging.value\n  },\n  get isSelected() {\n    return isSelected.value\n  },\n} as unknown as GanttBarContextValue\u003Cunknown>)\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003CPrimitive\n    :ref=\"forwardRef\"\n    as=\"button\"\n    :as-child=\"asChild\"\n    type=\"button\"\n    data-slot=\"gantt-bar\"\n    :data-all-day=\"occurrence.allDay || undefined\"\n    :data-recurring=\"occurrence.isRecurring || undefined\"\n    :data-selected=\"isSelected || undefined\"\n    :data-dragging=\"isDragging || undefined\"\n    :data-drag-kind=\"dragKind ?? undefined\"\n    :data-past=\"occurrence.end.getTime() \u003C Date.now() || undefined\"\n    :data-label-outside=\"labelOutside || undefined\"\n    :data-progress=\"progress ?? undefined\"\n    :data-completed=\"progress === 100 || undefined\"\n    :aria-label=\"ariaLabel\"\n    :style=\"barStyle\"\n    :class=\"\n      cn(\n        'group\u002Fgantt-bar-group text-foreground @container relative flex w-full min-w-0 cursor-pointer touch-none items-center gap-1.5 overflow-hidden rounded-sm px-1.5 py-0.5 text-start leading-normal select-none',\n        'focus-visible:ring-ring\u002F50 outline-none focus-visible:ring-2',\n        'bg-(--gantt-event-color)\u002F20 hover:bg-(--gantt-event-color)\u002F30',\n        'data-[drag-kind=move]:opacity-0',\n        'data-[drag-kind=resize-end]:opacity-40 data-[drag-kind=resize-start]:opacity-40',\n        'data-selected:bg-(--gantt-event-color)\u002F30',\n        segment.continuesBefore && 'rounded-s-none',\n        segment.continuesAfter && 'rounded-e-none',\n        viewConfig.classNames?.event,\n        props.class\n      )\n    \"\n    @pointerdown=\"onPointerDown\"\n    @click=\"onClick\"\n    @dblclick=\"onDoubleClick\"\n  >\n    \u003Cspan\n      v-if=\"progress !== null\"\n      aria-hidden\n      data-slot=\"gantt-bar-progress\"\n      class=\"pointer-events-none absolute inset-y-0 start-0 border-e border-(--gantt-event-color)\u002F65 bg-(--gantt-event-color)\u002F40 data-full:border-e-0\"\n      :data-full=\"progress === 100 || undefined\"\n      :style=\"{ width: `${progress}%` }\"\n    \u002F>\n    \u003Csvg\n      v-if=\"progress === 100 && !consumerOwnsContent\"\n      xmlns=\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\"\n      width=\"24\"\n      height=\"24\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      stroke-width=\"2\"\n      stroke-linecap=\"round\"\n      stroke-linejoin=\"round\"\n      class=\"relative size-2.5 shrink-0 opacity-80\"\n      aria-hidden=\"true\"\n    >\u003Cpath d=\"M20 6 9 17l-5-5\" \u002F>\u003C\u002Fsvg>\n    \u003Cslot>\n      \u003Ccomponent :is=\"() => customContent\" v-if=\"customContent\" \u002F>\n      \u003Ctemplate v-else-if=\"!labelOutside\">\n        \u003Csvg\n          v-if=\"occurrence.isRecurring\"\n          xmlns=\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\"\n          width=\"24\"\n          height=\"24\"\n          viewBox=\"0 0 24 24\"\n          fill=\"none\"\n          stroke=\"currentColor\"\n          stroke-width=\"2\"\n          stroke-linecap=\"round\"\n          stroke-linejoin=\"round\"\n          class=\"size-2.5 shrink-0 opacity-70\"\n          aria-hidden=\"true\"\n        >\u003Cpath d=\"m17 2 4 4-4 4\" \u002F>\u003Cpath d=\"M3 11v-1a4 4 0 0 1 4-4h14\" \u002F>\u003Cpath d=\"m7 22-4-4 4-4\" \u002F>\u003Cpath d=\"M21 13v1a4 4 0 0 1-4 4H3\" \u002F>\u003C\u002Fsvg>\n        \u003Cspan class=\"truncate font-medium\">{{ event.title }}\u003C\u002Fspan>\n        \u003Cspan\n          v-if=\"!occurrence.allDay && segment.isStart\"\n          class=\"text-muted-foreground hidden truncate @[8rem]:inline\"\n        >{{ timeLabel }}\u003C\u002Fspan>\n      \u003C\u002Ftemplate>\n    \u003C\u002Fslot>\n    \u003Ctemplate v-if=\"showResize\">\n      \u003Cspan\n        v-if=\"segment.isStart\"\n        data-slot=\"gantt-resize-handle\"\n        data-edge=\"start\"\n        class=\"absolute inset-y-0 start-0.5 flex w-2 cursor-ew-resize items-center justify-start opacity-0 group-hover\u002Fgantt-bar-group:opacity-100 pointer-coarse:opacity-100\"\n        @pointerdown=\"(e: PointerEvent) => gestures.beginResize(e, segment, 'start')\"\n      >\n        \u003Cspan aria-hidden class=\"bg-foreground\u002F40 h-2.5 w-0.5 rounded-full\" \u002F>\n      \u003C\u002Fspan>\n      \u003Cspan\n        v-if=\"segment.isEnd\"\n        data-slot=\"gantt-resize-handle\"\n        data-edge=\"end\"\n        class=\"absolute inset-y-0 end-0.5 flex w-2 cursor-ew-resize items-center justify-end opacity-0 group-hover\u002Fgantt-bar-group:opacity-100 pointer-coarse:opacity-100\"\n        @pointerdown=\"(e: PointerEvent) => gestures.beginResize(e, segment, 'end')\"\n      >\n        \u003Cspan aria-hidden class=\"bg-foreground\u002F40 h-2.5 w-0.5 rounded-full\" \u002F>\n      \u003C\u002Fspan>\n    \u003C\u002Ftemplate>\n  \u003C\u002FPrimitive>\n\u003C\u002Ftemplate>\n",{"path":4807,"type":480,"target":4808,"content":4809},"src\u002Freui\u002Fgantt\u002FGanttCustomDragLayer.vue","components\u002Freui\u002Fgantt\u002FGanttCustomDragLayer.vue","\u003Cscript setup lang=\"ts\" generic=\"TData = unknown\">\n\u002F**\n * Порт ReUI GanttCustomDragLayer (gantt-view.tsx, MIT).\n *\n * Consumer-owned drag\u002Fresize indicator (renderDragPreview\u002FrenderResizeIndicator):\n * the dnd engine (gantt-dnd.tsx, not yet ported) adopts this wrapper and\n * writes its cursor-tracking transform imperatively — nothing here does that\n * yet, so the wrapper renders `visibility: hidden` exactly as the original\n * does before its first positioned frame (no functional gap: without\n * gantt-dnd.tsx there is no drag to position it for).\n *\u002F\nimport { computed } from \"vue\"\nimport { useGanttSelector, useGanttViewConfig } from \".\u002Fcontext\"\n\nconst viewConfig = useGanttViewConfig\u003CTData>()\nconst drag = useGanttSelector\u003CTData, unknown>((state) => state.drag)\n\nconst render = computed(() => {\n  const d = drag.value as {\n    kind: \"move\" | \"resize-start\" | \"resize-end\"\n    occurrence: unknown\n    proposedStart: Date\n    proposedEnd: Date\n    valid: boolean\n  } | null\n  if (!d) return null\n  const renderFn = d.kind === \"move\" ? viewConfig.value.renderDragPreview : viewConfig.value.renderResizeIndicator\n  if (!renderFn) return null\n  return {\n    slot: d.kind === \"move\" ? \"gantt-drag-overlay\" : \"gantt-resize-indicator\",\n    content: renderFn({\n      occurrence: d.occurrence,\n      kind: d.kind,\n      start: d.proposedStart,\n      end: d.proposedEnd,\n      valid: d.valid,\n    } as never),\n  }\n})\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cdiv\n    v-if=\"render\"\n    :data-slot=\"render.slot\"\n    data-custom=\"\"\n    class=\"pointer-events-none fixed top-0 left-0 z-100 will-change-transform\"\n    style=\"visibility: hidden\"\n  >\n    \u003Ccomponent :is=\"() => render!.content\" \u002F>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n",{"path":4811,"type":480,"target":4812,"content":4813},"src\u002Freui\u002Fgantt\u002FGanttDatePicker.vue","components\u002Freui\u002Fgantt\u002FGanttDatePicker.vue","\u003Cscript setup lang=\"ts\">\n\u002F**\n * Порт ReUI GanttDatePicker (gantt-nav.tsx, MIT) — компактный go-to-date\n * пикер (Calendar в Popover). Без тултипа по дизайну (см. NavButtonProps\n * policy оригинала — открывающая оверлей кнопка тултип не получает).\n *\n * Открытое состояние (Popover + Calendar) не проверяется дифом по ДВУМ\n * уже задокументированным причинам сразу: позиционирование floating-слоя\n * (docs\u002FPORTING.md §5 `ui-dropdown-menu-open`) и раскладка сетки дней\n * `date-selector`\u002F`ui-calendar` (§5) — оба класса ограничения уже приняты\n * для других компонентов. Кейс проверяет только закрытый триггер.\n *\u002F\nimport type { HTMLAttributes } from \"vue\"\nimport { ref } from \"vue\"\nimport { Button } from \"@\u002Fcomponents\u002Fui\u002Fbutton\"\nimport { Popover, PopoverContent, PopoverTrigger } from \"@\u002Fcomponents\u002Fui\u002Fpopover\"\nimport { Calendar, type CalendarDateRange } from \"@\u002Fcomponents\u002Fui\u002Fcalendar\"\nimport { useGanttNavigation, useGanttSettings } from \".\u002Fcontext\"\nimport { useGanttNavButtonProps } from \".\u002Fnav\"\nimport { toZoned } from \".\u002Flib\"\nimport { cn } from \"@\u002Flib\u002Futils\"\n\nconst props = defineProps\u003C{ class?: HTMLAttributes[\"class\"] }>()\n\nconst { date, goTo } = useGanttNavigation()\nconst settings = useGanttSettings()\nconst nav = useGanttNavButtonProps()\nconst open = ref(false)\n\n\u002F\u002F Calendar (mode=\"single\", default) only ever emits a single `Date | undefined`\n\u002F\u002F here - the wider union is the primitive's general (multiple\u002Frange) signature.\nfunction onSelect(next: Date | Date[] | CalendarDateRange | undefined) {\n  if (next instanceof Date) {\n    goTo(next)\n    open.value = false\n  }\n}\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003CPopover v-model:open=\"open\">\n    \u003CPopoverTrigger as-child>\n      \u003CButton :variant=\"nav.variant\" :size=\"nav.iconSize\" data-slot=\"gantt-date-picker\" :aria-label=\"settings.i18n.labels.goToDate\" :class=\"cn(props.class)\">\n        \u003Csvg xmlns=\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\">\u003Crect width=\"18\" height=\"18\" x=\"3\" y=\"4\" rx=\"2\" ry=\"2\" \u002F>\u003Cline x1=\"16\" x2=\"16\" y1=\"2\" y2=\"6\" \u002F>\u003Cline x1=\"8\" x2=\"8\" y1=\"2\" y2=\"6\" \u002F>\u003Cline x1=\"3\" x2=\"21\" y1=\"10\" y2=\"10\" \u002F>\u003C\u002Fsvg>\n      \u003C\u002FButton>\n    \u003C\u002FPopoverTrigger>\n    \u003CPopoverContent align=\"start\" class=\"w-auto p-0!\">\n      \u003CCalendar\n        :model-value=\"toZoned(date, settings.timeZone)\"\n        :placeholder=\"toZoned(date, settings.timeZone)\"\n        :week-starts-on=\"settings.weekStartsOn\"\n        @update:model-value=\"onSelect\"\n      \u002F>\n    \u003C\u002FPopoverContent>\n  \u003C\u002FPopover>\n\u003C\u002Ftemplate>\n",{"path":4815,"type":480,"target":4816,"content":4817},"src\u002Freui\u002Fgantt\u002FGanttNav.vue","components\u002Freui\u002Fgantt\u002FGanttNav.vue","\u003Cscript setup lang=\"ts\">\n\u002F**\n * Порт ReUI GanttNav (gantt-nav.tsx, MIT) — собранная по умолчанию навигация:\n * Today, переключатель шкалы, prev\u002Fnext, заголовок, спейсер. `TooltipProvider`-\n * обёртка не нужна — ни один дочерний нав-компонент тултип не рендерит\n * (см. GanttNavToday.vue и др.).\n *\u002F\nimport type { HTMLAttributes } from \"vue\"\nimport { Primitive } from \"reka-ui\"\nimport { useGanttViewConfig } from \".\u002Fcontext\"\nimport { cn } from \"@\u002Flib\u002Futils\"\nimport GanttNavToday from \".\u002FGanttNavToday.vue\"\nimport GanttNavPrev from \".\u002FGanttNavPrev.vue\"\nimport GanttNavNext from \".\u002FGanttNavNext.vue\"\nimport GanttTitle from \".\u002FGanttTitle.vue\"\nimport GanttScaleSwitcher from \".\u002FGanttScaleSwitcher.vue\"\n\nconst props = withDefaults(defineProps\u003C{ class?: HTMLAttributes[\"class\"]; asChild?: boolean }>(), { asChild: false })\n\ndefineSlots\u003C{ default?(): unknown }>()\n\nconst viewConfig = useGanttViewConfig()\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003CPrimitive\n    as=\"div\"\n    :as-child=\"props.asChild\"\n    data-slot=\"gantt-nav\"\n    :class=\"\n      cn(\n        'flex min-w-0 flex-wrap items-center gap-2 border-b px-3 py-2',\n        viewConfig.stickyNav && 'bg-background sticky top-0 z-30',\n        viewConfig.classNames?.nav,\n        props.class\n      )\n    \"\n  >\n    \u003Cslot>\n      \u003CGanttNavToday \u002F>\n      \u003CGanttScaleSwitcher \u002F>\n      \u003Cdiv class=\"flex items-center\">\n        \u003CGanttNavPrev \u002F>\n        \u003CGanttNavNext \u002F>\n      \u003C\u002Fdiv>\n      \u003CGanttTitle \u002F>\n      \u003Cdiv class=\"grow\" \u002F>\n    \u003C\u002Fslot>\n  \u003C\u002FPrimitive>\n\u003C\u002Ftemplate>\n",{"path":4819,"type":480,"target":4820,"content":4821},"src\u002Freui\u002Fgantt\u002FGanttNavNext.vue","components\u002Freui\u002Fgantt\u002FGanttNavNext.vue","\u003Cscript setup lang=\"ts\">\n\u002F** Порт ReUI GanttNavNext (gantt-nav.tsx, MIT). Tooltip не перенесён (см. GanttNavToday.vue). *\u002F\nimport type { HTMLAttributes } from \"vue\"\nimport { Button } from \"@\u002Fcomponents\u002Fui\u002Fbutton\"\nimport { useGanttNavigation, useGanttSettings } from \".\u002Fcontext\"\nimport { useGanttNavButtonProps } from \".\u002Fnav\"\nimport { cn } from \"@\u002Flib\u002Futils\"\n\nconst props = withDefaults(defineProps\u003C{ class?: HTMLAttributes[\"class\"]; asChild?: boolean }>(), { asChild: false })\n\nconst { next } = useGanttNavigation()\nconst settings = useGanttSettings()\nconst nav = useGanttNavButtonProps()\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003CButton\n    :variant=\"nav.variant\"\n    :size=\"nav.iconSize\"\n    :as-child=\"props.asChild\"\n    data-slot=\"gantt-nav-next\"\n    :aria-label=\"settings.i18n.labels.next\"\n    :class=\"cn(props.class)\"\n    @click=\"next\"\n  >\n    \u003Cslot>\n      \u003Csvg xmlns=\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\">\u003Cpath d=\"m9 18 6-6-6-6\" \u002F>\u003C\u002Fsvg>\n    \u003C\u002Fslot>\n  \u003C\u002FButton>\n\u003C\u002Ftemplate>\n",{"path":4823,"type":480,"target":4824,"content":4825},"src\u002Freui\u002Fgantt\u002FGanttNavPrev.vue","components\u002Freui\u002Fgantt\u002FGanttNavPrev.vue","\u003Cscript setup lang=\"ts\">\n\u002F** Порт ReUI GanttNavPrev (gantt-nav.tsx, MIT). Tooltip не перенесён (см. GanttNavToday.vue). *\u002F\nimport type { HTMLAttributes } from \"vue\"\nimport { Button } from \"@\u002Fcomponents\u002Fui\u002Fbutton\"\nimport { useGanttNavigation, useGanttSettings } from \".\u002Fcontext\"\nimport { useGanttNavButtonProps } from \".\u002Fnav\"\nimport { cn } from \"@\u002Flib\u002Futils\"\n\nconst props = withDefaults(defineProps\u003C{ class?: HTMLAttributes[\"class\"]; asChild?: boolean }>(), { asChild: false })\n\nconst { prev } = useGanttNavigation()\nconst settings = useGanttSettings()\nconst nav = useGanttNavButtonProps()\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003CButton\n    :variant=\"nav.variant\"\n    :size=\"nav.iconSize\"\n    :as-child=\"props.asChild\"\n    data-slot=\"gantt-nav-prev\"\n    :aria-label=\"settings.i18n.labels.previous\"\n    :class=\"cn(props.class)\"\n    @click=\"prev\"\n  >\n    \u003Cslot>\n      \u003Csvg xmlns=\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\">\u003Cpath d=\"m15 18-6-6 6-6\" \u002F>\u003C\u002Fsvg>\n    \u003C\u002Fslot>\n  \u003C\u002FButton>\n\u003C\u002Ftemplate>\n",{"path":4827,"type":480,"target":4828,"content":4829},"src\u002Freui\u002Fgantt\u002FGanttNavToday.vue","components\u002Freui\u002Fgantt\u002FGanttNavToday.vue","\u003Cscript setup lang=\"ts\">\n\u002F**\n * Порт ReUI GanttNavToday (gantt-nav.tsx, MIT). `Tooltip`-обёртка (наведение\n * -> дата) не перенесена — тот же выбор\u002Fпричина, что и во всём остальном\n * порте (`GanttBar`\u002F`GanttOffscreenChips`\u002F`GanttZoomControl`, 16\u002F16 у всех):\n * не влияет на статичный скриншот.\n *\u002F\nimport type { HTMLAttributes } from \"vue\"\nimport { Button } from \"@\u002Fcomponents\u002Fui\u002Fbutton\"\nimport { useGanttNavigation, useGanttSettings } from \".\u002Fcontext\"\nimport { useGanttNavButtonProps } from \".\u002Fnav\"\nimport { cn } from \"@\u002Flib\u002Futils\"\n\nconst props = withDefaults(defineProps\u003C{ class?: HTMLAttributes[\"class\"]; asChild?: boolean }>(), { asChild: false })\n\nconst { today, isToday } = useGanttNavigation()\nconst settings = useGanttSettings()\nconst nav = useGanttNavButtonProps()\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003CButton\n    :variant=\"nav.variant\"\n    :size=\"nav.size\"\n    :as-child=\"props.asChild\"\n    data-slot=\"gantt-nav-today\"\n    :data-active=\"isToday || undefined\"\n    :class=\"cn(props.class)\"\n    @click=\"today\"\n  >\n    \u003Cslot>{{ settings.i18n.labels.today }}\u003C\u002Fslot>\n  \u003C\u002FButton>\n\u003C\u002Ftemplate>\n",{"path":4831,"type":480,"target":4832,"content":4833},"src\u002Freui\u002Fgantt\u002FGanttNowDot.vue","components\u002Freui\u002Fgantt\u002FGanttNowDot.vue","\u003Cscript setup lang=\"ts\">\n\u002F**\n * Порт ReUI GanttNowDot (gantt-view.tsx, MIT) — now-line's dot cap, pinned\n * inside the sticky header at the header\u002Fbody boundary.\n *\u002F\nimport { computed } from \"vue\"\nimport { useNow } from \".\u002Fuse-now\"\n\nconst props = defineProps\u003C{\n  rangeStartMs: number\n  rangeEndMs: number\n}>()\n\nconst now = useNow()\nconst visible = computed(() => {\n  const ms = now.value.getTime()\n  return ms >= props.rangeStartMs && ms \u003C props.rangeEndMs\n})\nconst fraction = computed(() => (now.value.getTime() - props.rangeStartMs) \u002F (props.rangeEndMs - props.rangeStartMs))\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cspan\n    v-if=\"visible\"\n    aria-hidden\n    data-slot=\"gantt-now-dot\"\n    class=\"bg-destructive absolute -bottom-0.75 z-10 size-1.5 -translate-x-1\u002F2 rounded-full\"\n    :style=\"{ insetInlineStart: `${fraction * 100}%` }\"\n  \u002F>\n\u003C\u002Ftemplate>\n",{"path":4835,"type":480,"target":4836,"content":4837},"src\u002Freui\u002Fgantt\u002FGanttNowLine.vue","components\u002Freui\u002Fgantt\u002FGanttNowLine.vue","\u003Cscript setup lang=\"ts\">\n\u002F** Порт ReUI GanttNowLine (gantt-view.tsx, MIT) — red now-line on the axis. *\u002F\nimport { computed } from \"vue\"\nimport { useNow } from \".\u002Fuse-now\"\n\nconst props = defineProps\u003C{\n  rangeStartMs: number\n  rangeEndMs: number\n}>()\n\nconst now = useNow()\nconst visible = computed(() => {\n  const ms = now.value.getTime()\n  return ms >= props.rangeStartMs && ms \u003C props.rangeEndMs\n})\nconst fraction = computed(() => (now.value.getTime() - props.rangeStartMs) \u002F (props.rangeEndMs - props.rangeStartMs))\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cdiv\n    v-if=\"visible\"\n    data-slot=\"gantt-now-indicator\"\n    class=\"from-destructive\u002F80 via-destructive\u002F45 to-destructive\u002F15 absolute inset-y-0 z-10 w-px bg-linear-to-b\"\n    :style=\"{ insetInlineStart: `${fraction * 100}%` }\"\n  \u002F>\n\u003C\u002Ftemplate>\n",{"path":4839,"type":480,"target":4840,"content":4841},"src\u002Freui\u002Fgantt\u002FGanttOffscreenChips.vue","components\u002Freui\u002Fgantt\u002FGanttOffscreenChips.vue","\u003Cscript setup lang=\"ts\">\n\u002F**\n * Порт ReUI GanttOffscreenChips (gantt-view.tsx, MIT) — edge chips for rows\n * whose bars sit entirely outside the visible timeline; clicking scrolls the\n * bar back into view. Reads geometry straight from the DOM (row data\n * attributes) so scrolling never re-renders the grid.\n *\n * ponytail: `Tooltip`\u002F`TooltipProvider` не перенесены — тот же выбор и та же\n * причина, что уже задокументирована и проверена (16\u002F16) в `GanttBar.vue`\n * (не влияет на статичный скриншот, `renderEventMenu`\u002Fhover-only слой).\n * `IconPlaceholder` (chevron-left\u002Fchevron-right) заменён инлайновым `\u003Csvg>`.\n *\u002F\nimport { onBeforeUnmount, onMounted, ref, watch, type Ref } from \"vue\"\nimport { useGanttSettings } from \".\u002Fcontext\"\nimport { getPaneViewport, getScrollStart } from \".\u002Fview-lib\"\n\nconst props = defineProps\u003C{\n  paneRef: HTMLElement | null\n  \u002F** Any reactive value whose change should re-measure (occurrences length\u002Fidentity). *\u002F\n  refreshKey: string\n}>()\n\nconst settings = useGanttSettings()\n\ninterface OffscreenChip {\n  id: string\n  side: \"start\" | \"end\"\n  top: number\n  color?: string\n  label: string\n  startMs: number | null\n  target: number\n  insetEnd: number\n}\n\nfunction sameChips(a: OffscreenChip[], b: OffscreenChip[]): boolean {\n  return (\n    a.length === b.length &&\n    a.every(\n      (chip, i) =>\n        chip.id === b[i]!.id &&\n        chip.side === b[i]!.side &&\n        chip.top === b[i]!.top &&\n        chip.target === b[i]!.target &&\n        chip.insetEnd === b[i]!.insetEnd\n    )\n  )\n}\n\nconst chips = ref\u003COffscreenChip[]>([]) as Ref\u003COffscreenChip[]>\n\nlet raf = 0\nlet observer: ResizeObserver | undefined\nlet viewportEl: HTMLElement | null = null\n\nfunction measure() {\n  raf = 0\n  const pane = props.paneRef\n  const viewport = getPaneViewport(pane)\n  if (!pane || !viewport) return\n  const paneRect = pane.getBoundingClientRect()\n  const header = viewport.querySelector\u003CHTMLElement>(\"[data-slot=gantt-timeline-header]\")\n  const headerBottom = header ? header.getBoundingClientRect().bottom - paneRect.top : 0\n  const trackW = viewport.scrollWidth\n  const visibleStart = getScrollStart(viewport)\n  const visibleEnd = visibleStart + viewport.clientWidth\n  const zoomEl = pane.querySelector\u003CHTMLElement>(\"[data-slot=gantt-zoom]\")\n  const zoom = zoomEl\n    ? {\n        top: zoomEl.getBoundingClientRect().top - paneRect.top - 8,\n        bottom: zoomEl.getBoundingClientRect().bottom - paneRect.top + 8,\n        inset: paneRect.right - zoomEl.getBoundingClientRect().left + 8,\n      }\n    : null\n  const next: OffscreenChip[] = []\n  for (const rowEl of viewport.querySelectorAll\u003CHTMLElement>(\"[data-gantt-row]\")) {\n    const from = parseFloat(rowEl.dataset.ganttBarMin ?? \"\")\n    const to = parseFloat(rowEl.dataset.ganttBarMax ?? \"\")\n    if (Number.isNaN(from) || Number.isNaN(to)) continue\n    const rect = rowEl.getBoundingClientRect()\n    const top = rect.top - paneRect.top + rect.height \u002F 2\n    if (top \u003C headerBottom + 10 || top > paneRect.height - 16) continue\n    const startPx = from * trackW\n    const endPx = to * trackW\n    const startMs = parseFloat(rowEl.dataset.ganttBarStartMs ?? \"\")\n    const base = {\n      id: rowEl.dataset.ganttRowId ?? \"\",\n      top: Math.round(top),\n      color: rowEl.dataset.ganttBarColor,\n      label: rowEl.dataset.ganttBarLabel ?? \"\",\n      startMs: Number.isNaN(startMs) ? null : startMs,\n    }\n    if (endPx \u003C= visibleStart + 2) {\n      next.push({ ...base, side: \"start\", target: startPx - 24, insetEnd: 14 })\n    } else if (startPx >= visibleEnd - 2) {\n      const overlapsZoom = zoom && top >= zoom.top && top \u003C= zoom.bottom\n      next.push({\n        ...base,\n        side: \"end\",\n        target: endPx - viewport.clientWidth + 24,\n        insetEnd: overlapsZoom ? Math.max(14, zoom.inset) : 14,\n      })\n    }\n  }\n  if (!sameChips(chips.value, next)) chips.value = next\n}\n\nfunction schedule() {\n  if (!raf) raf = requestAnimationFrame(measure)\n}\n\nfunction teardown() {\n  if (viewportEl) viewportEl.removeEventListener(\"scroll\", schedule)\n  observer?.disconnect()\n  if (raf) cancelAnimationFrame(raf)\n  raf = 0\n  observer = undefined\n  viewportEl = null\n}\n\nfunction setup() {\n  teardown()\n  const pane = props.paneRef\n  const viewport = getPaneViewport(pane)\n  if (!pane || !viewport) return\n  viewportEl = viewport\n  viewport.addEventListener(\"scroll\", schedule)\n  observer = new ResizeObserver(schedule)\n  observer.observe(viewport)\n  schedule()\n}\n\nonMounted(setup)\nonBeforeUnmount(teardown)\n\u002F\u002F refreshKey changes (occurrences\u002Frange) or the pane element itself showing\n\u002F\u002F up later both warrant a fresh measurement pass.\nwatch(() => [props.paneRef, props.refreshKey], setup)\n\nfunction scrollTo(chip: OffscreenChip) {\n  const viewport = getPaneViewport(props.paneRef)\n  if (!viewport) return\n  const target = Math.max(0, chip.target)\n  viewport.scrollTo({\n    left: getComputedStyle(viewport).direction === \"rtl\" ? -target : target,\n    behavior: \"smooth\",\n  })\n  const bar = viewport.querySelector\u003CHTMLElement>(\n    `[data-gantt-row-id=\"${CSS.escape(chip.id)}\"] [data-slot=gantt-bar]`\n  )\n  bar?.focus({ preventScroll: true })\n}\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cdiv\n    v-if=\"chips.length > 0\"\n    data-slot=\"gantt-offscreen-chips\"\n    class=\"pointer-events-none absolute inset-0 z-30 overflow-hidden\"\n  >\n    \u003Cbutton\n      v-for=\"chip in chips\"\n      :key=\"`${chip.id}-${chip.side}`\"\n      type=\"button\"\n      data-slot=\"gantt-offscreen-chip\"\n      :data-side=\"chip.side\"\n      :aria-label=\"settings.i18n.labels.jumpToBar(chip.label)\"\n      class=\"bg-background text-muted-foreground hover:text-foreground pointer-events-auto absolute flex size-5 -translate-y-1\u002F2 cursor-pointer items-center justify-center rounded-full border shadow-xs\"\n      :style=\"{\n        top: `${chip.top}px`,\n        ...(chip.side === 'start' ? { insetInlineStart: '0.5rem' } : { insetInlineEnd: `${chip.insetEnd}px` }),\n      }\"\n      @click=\"scrollTo(chip)\"\n    >\n      \u003Csvg\n        v-if=\"chip.side === 'start'\"\n        xmlns=\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\"\n        width=\"24\"\n        height=\"24\"\n        viewBox=\"0 0 24 24\"\n        fill=\"none\"\n        stroke=\"currentColor\"\n        stroke-width=\"2\"\n        stroke-linecap=\"round\"\n        stroke-linejoin=\"round\"\n        class=\"size-3\"\n        aria-hidden=\"true\"\n      >\u003Cpath d=\"m15 18-6-6 6-6\" \u002F>\u003C\u002Fsvg>\n      \u003Csvg\n        v-else\n        xmlns=\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\"\n        width=\"24\"\n        height=\"24\"\n        viewBox=\"0 0 24 24\"\n        fill=\"none\"\n        stroke=\"currentColor\"\n        stroke-width=\"2\"\n        stroke-linecap=\"round\"\n        stroke-linejoin=\"round\"\n        class=\"size-3\"\n        aria-hidden=\"true\"\n      >\u003Cpath d=\"m9 18 6-6-6-6\" \u002F>\u003C\u002Fsvg>\n      \u003Cspan\n        aria-hidden\n        class=\"ring-background absolute -end-px -top-px size-1.5 rounded-full ring-1\"\n        :style=\"{ background: chip.color ?? 'var(--color-primary)' }\"\n      \u002F>\n    \u003C\u002Fbutton>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n",{"path":4843,"type":480,"target":4844,"content":4845},"src\u002Freui\u002Fgantt\u002FGanttScaleSwitcher.vue","components\u002Freui\u002Fgantt\u002FGanttScaleSwitcher.vue","\u003Cscript setup lang=\"ts\">\n\u002F**\n * Порт ReUI GanttScaleSwitcher (gantt-nav.tsx, MIT) — Day\u002FWeek\u002FMonth\u002FQuarter\u002F\n * Year, ghost dropdown-кнопка. `Tooltip` на триггере не перенесён (см.\n * GanttNavToday.vue) — контролируемый `open`\u002F`tipOpen` из оригинала тоже не\n * нужен без него; `DropdownMenu` остаётся управляемым, чтобы выбор пункта\n * закрывал меню в тот же клик.\n *\u002F\nimport type { HTMLAttributes } from \"vue\"\nimport { ref } from \"vue\"\nimport { Button } from \"@\u002Fcomponents\u002Fui\u002Fbutton\"\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuGroup,\n  DropdownMenuItem,\n  DropdownMenuLabel,\n  DropdownMenuTrigger,\n} from \"@\u002Fcomponents\u002Fui\u002Fdropdown-menu\"\nimport { useGanttScale, useGanttSettings } from \".\u002Fcontext\"\nimport { useGanttNavButtonProps, GANTT_SCALES } from \".\u002Fnav\"\nimport { cn } from \"@\u002Flib\u002Futils\"\nimport type { GanttScale } from \".\u002Ftypes\"\n\nconst props = withDefaults(\n  defineProps\u003C{\n    class?: HTMLAttributes[\"class\"]\n    \u002F** The offered scales, in menu order. Default: all five. *\u002F\n    scales?: GanttScale[]\n  }>(),\n  { scales: () => GANTT_SCALES }\n)\n\nconst { scale, setScale } = useGanttScale()\nconst settings = useGanttSettings()\nconst nav = useGanttNavButtonProps()\nconst open = ref(false)\n\nfunction selectScale(next: GanttScale) {\n  open.value = false\n  setScale(next)\n}\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003CDropdownMenu v-model:open=\"open\">\n    \u003CDropdownMenuTrigger as-child>\n      \u003CButton :variant=\"nav.variant\" :size=\"nav.size\" data-slot=\"gantt-scale-switcher\" :aria-label=\"settings.i18n.labels.selectView\" :class=\"cn('gap-1', props.class)\">\n        \u003Cslot>\n          {{ settings.i18n.labels.scales[scale] }}\n          \u003Csvg xmlns=\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4 opacity-60\" aria-hidden=\"true\">\u003Cpath d=\"m6 9 6 6 6-6\" \u002F>\u003C\u002Fsvg>\n        \u003C\u002Fslot>\n      \u003C\u002FButton>\n    \u003C\u002FDropdownMenuTrigger>\n    \u003CDropdownMenuContent align=\"start\" class=\"min-w-36\">\n      \u003CDropdownMenuGroup>\n        \u003CDropdownMenuLabel class=\"text-muted-foreground font-normal\">{{ settings.i18n.labels.selectView }}\u003C\u002FDropdownMenuLabel>\n        \u003CDropdownMenuItem v-for=\"value in props.scales\" :key=\"value\" :data-active=\"scale === value || undefined\" @click=\"selectScale(value)\">\n          {{ settings.i18n.labels.scales[value] }}\n        \u003C\u002FDropdownMenuItem>\n      \u003C\u002FDropdownMenuGroup>\n    \u003C\u002FDropdownMenuContent>\n  \u003C\u002FDropdownMenu>\n\u003C\u002Ftemplate>\n",{"path":4847,"type":480,"target":4848,"content":4849},"src\u002Freui\u002Fgantt\u002FGanttTimelineHeader.vue","components\u002Freui\u002Fgantt\u002FGanttTimelineHeader.vue","\u003Cscript setup lang=\"ts\">\n\u002F**\n * Порт двухрядного заголовка шкалы времени из GanttView (gantt-view.tsx,\n * MIT, разметка строк ~2023-2134). Юниты\u002Fгруппы считает `useGanttTimelineUnits`\n * (..\u002Ftimeline-units.ts) — сама модель шкалы (день\u002Fквартал\u002Fгод\u002Fнеделя\u002Fмесяц\n * с весами по РЕАЛЬНОЙ длительности) уже была перенесена дословно туда.\n *\n * ponytail: `onPointerDown={beginHeaderPan}` (перетаскивание таймлайна за\n * заголовок) не перенесён — это часть секции скролла\u002Fбесконечной прокрутки\n * (следующий кусок GanttView), заголовок сам по себе от неё не зависит.\n * `zoom` принят пропом со значением по умолчанию 1 (сама секция зума ещё не\n * портирована) — когда она появится, `GanttView` прокинет реальное значение\n * без изменений в этом компоненте.\n *\u002F\nimport { computed } from \"vue\"\nimport { useGanttSettings, useGanttViewConfig, resolveTimelineLines } from \".\u002Fcontext\"\nimport { useGanttTimelineUnits } from \".\u002Ftimeline-units\"\nimport { computeUnitFractions, createHintStopResolver } from \".\u002Fview-lib\"\nimport type { GanttDateRange, GanttScale } from \".\u002Ftypes\"\nimport { cn } from \"@\u002Flib\u002Futils\"\n\nconst props = withDefaults(\n  defineProps\u003C{\n    scale: GanttScale\n    range: GanttDateRange\n    \u002F** Day-scale unit interval in minutes; defaults to the interval view config. *\u002F\n    interval?: number\n    \u002F** Zoom multiplier; the zoom section (not yet ported) will drive this. Default 1. *\u002F\n    zoom?: number\n  }>(),\n  { zoom: 1 }\n)\n\nconst settings = useGanttSettings()\nconst viewConfig = useGanttViewConfig()\n\nconst scaleRef = computed(() => props.scale)\nconst rangeRef = computed(() => props.range)\nconst intervalRef = computed(() => Math.min(Math.max(props.interval ?? viewConfig.value.interval, 15), 240))\n\nconst timeline = useGanttTimelineUnits(scaleRef, rangeRef, intervalRef)\nconst units = computed(() => timeline.value.units)\nconst groups = computed(() => timeline.value.groups)\n\nconst trackRemWidth = computed(() => units.value.length * timeline.value.unitWidthRem * props.zoom)\nconst trackWidth = computed(() => `${trackRemWidth.value}rem`)\n\nconst totalWeight = computed(() => units.value.reduce((sum, unit) => sum + unit.weight, 0))\nconst uniform = computed(() =>\n  units.value.every((unit) => Math.abs(unit.weight - (units.value[0]?.weight ?? 0)) \u003C 1e-9)\n)\n\n\u002F** Cumulative start\u002Fwidth fractions per unit, for backdrop stripes\u002Flines. *\u002F\nconst unitFractions = computed(() => computeUnitFractions(units.value))\n\u002F** Snap-to-unit resolver for the hint tile; GanttTimelineRow consumes this. *\u002F\nconst resolveHintStop = computed(() => createHintStopResolver(unitFractions.value))\ndefineExpose({ resolveHintStop, trackWidth, trackRemWidth })\n\n\u002F** Group boundary fractions; spans are in unit-weight terms everywhere. *\u002F\nconst groupBoundaries = computed(() => {\n  const fractions: number[] = []\n  let acc = 0\n  for (let i = 0; i \u003C groups.value.length - 1; i++) {\n    acc += groups.value[i]!.span\n    fractions.push(acc \u002F totalWeight.value)\n  }\n  return fractions\n})\n\nconst gridLines = computed(() => resolveTimelineLines(viewConfig.value.timelineLines))\nconst showUnitLines = computed(() => !uniform.value || gridLines.value.vertical !== null)\nconst offDayClassName = computed(\n  () => (typeof viewConfig.value.offDays === \"object\" && viewConfig.value.offDays.className) || \"bg-muted\u002F40\"\n)\n\nconst rangeStartMs = computed(() => props.range.start.getTime())\nconst rangeEndMs = computed(() => props.range.end.getTime())\nconst snapMin = computed(() => (props.scale === \"day\" ? settings.value.snapDuration : 24 * 60))\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cdiv\n    data-slot=\"gantt-timeline-header\"\n    class=\"bg-background sticky top-0 z-30 shrink-0 border-b\"\n    :style=\"{ minWidth: trackWidth }\"\n  >\n    \u003C!-- group sectors; boundaries painted like the body lines -->\n    \u003Cdiv class=\"relative h-8 border-b\">\n      \u003Cdiv class=\"flex h-full\">\n        \u003Cdiv\n          v-for=\"group in groups\"\n          :key=\"group.key\"\n          data-slot=\"gantt-axis-group\"\n          class=\"text-muted-foreground flex min-w-0 items-center ps-3 pe-2\"\n          :style=\"{ flex: `${group.span} 0 0px` }\"\n        >\n          \u003Cspan data-slot=\"gantt-axis-group-label\" class=\"sticky start-3 max-w-full truncate\">{{ group.label }}\u003C\u002Fspan>\n        \u003C\u002Fdiv>\n      \u003C\u002Fdiv>\n      \u003Cspan\n        v-for=\"fraction in groupBoundaries\"\n        :key=\"fraction\"\n        aria-hidden\n        class=\"bg-border absolute inset-y-0 w-px\"\n        :style=\"{ insetInlineStart: `calc(${fraction * 100}% - 1px)` }\"\n      \u002F>\n    \u003C\u002Fdiv>\n    \u003C!-- units, engine axis = this row -->\n    \u003Cdiv\n      data-gantt-axis=\"\"\n      :data-gantt-range-start=\"rangeStartMs\"\n      :data-gantt-range-end=\"rangeEndMs\"\n      :data-gantt-snap=\"snapMin\"\n      class=\"relative h-8\"\n    >\n      \u003Cdiv aria-hidden class=\"absolute inset-0\">\n        \u003Cspan\n          v-for=\"{ unit, start, width } in unitFractions.filter((f) => f.unit.isOff)\"\n          :key=\"unit.key\"\n          :class=\"cn('absolute inset-y-0', offDayClassName)\"\n          :style=\"{ insetInlineStart: `${start * 100}%`, width: `${width * 100}%` }\"\n        \u002F>\n      \u003C\u002Fdiv>\n      \u003Cdiv class=\"flex h-full\">\n        \u003Cdiv\n          v-for=\"unit in units\"\n          :key=\"unit.key\"\n          :data-today=\"unit.isToday || undefined\"\n          :data-off=\"unit.isOff || undefined\"\n          :class=\"\n            cn(\n              'flex min-w-0 items-center justify-center truncate px-1.5 text-center',\n              'text-muted-foreground',\n              unit.isToday && 'text-primary font-medium'\n            )\n          \"\n          :style=\"{ flex: `${unit.weight} 0 0px` }\"\n        >\n          \u003Cspan v-if=\"unit.isToday\" class=\"bg-primary\u002F10 truncate rounded-full px-1.5 py-px\">{{ unit.label }}\u003C\u002Fspan>\n          \u003Ctemplate v-else>{{ unit.label }}\u003C\u002Ftemplate>\n        \u003C\u002Fdiv>\n      \u003C\u002Fdiv>\n      \u003Ctemplate v-if=\"showUnitLines\">\n        \u003Cspan\n          v-for=\"{ unit, start } in unitFractions.slice(1)\"\n          :key=\"unit.key\"\n          aria-hidden\n          data-slot=\"gantt-grid-line\"\n          data-axis=\"vertical\"\n          :class=\"\n            cn(\n              'absolute inset-y-0 w-px',\n              gridLines.vertical === 'dashed'\n                ? 'bg-[repeating-linear-gradient(to_bottom,var(--color-border)_0,var(--color-border)_3px,transparent_3px,transparent_6px)]'\n                : 'bg-border'\n            )\n          \"\n          :style=\"{ insetInlineStart: `calc(${start * 100}% - 1px)` }\"\n        \u002F>\n      \u003C\u002Ftemplate>\n    \u003C\u002Fdiv>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n",{"path":4851,"type":480,"target":4852,"content":4853},"src\u002Freui\u002Fgantt\u002FGanttTimelineRow.vue","components\u002Freui\u002Fgantt\u002FGanttTimelineRow.vue","\u003Cscript setup lang=\"ts\" generic=\"TData = unknown\">\n\u002F**\n * Порт ReUI GanttTimelineRow (gantt-view.tsx, MIT, разметка ~2842-3579) —\n * дорожка таймлайна одной строки: бары, drag-create hint (наведение),\n * ghost\u002Fdraft\u002Fsummary оверлеи.\n *\n * `ghost`\u002F`draft` (визуальные оверлеи перетаскивания бара\u002Fрезайза\u002F\n * drag-create) читают `instance.state.drag`\u002F`slotDraft`, которые пишет\n * `gestures.ts` (полный порт `gantt-dnd.tsx`) во время реального жеста —\n * в статичном скриншоте оба всегда `null` (нет активного жеста), поэтому\n * оверлеи проверены только интеракционным гейтом\n * (`tools\u002Fvisual-diff\u002Finteractions\u002Fgantt.mjs`), не визуальным дифом.\n *\n * Наведение-hint (кружок под курсором над пустым треком) — чистая\n * математика указателя (`trackPoint`\u002F`resolveHintStop`, ..\u002Fview-lib.ts),\n * без зависимости от dnd-движка. Клик по нему\u002Fпо пустому треку коммитит\n * через `settings.onSlotClick`\u002F`onSelectSlot` (headless-контракт, не dnd).\n * `viewConfig.dragCreate` (press-and-drag вместо click) вызывает\n * `gestures.beginCreate` — реальный жест.\n *\n * `renderSummary`\u002F`renderScheduleHint` (VNodeChild-фактории) рендерятся\n * через `\u003Ccomponent :is=\"() => fn(...)\" \u002F>`, тот же приём, что и в\n * `GanttBar.vue`\u002F`filters.tsx` (см. docs\u002FPORTING.md).\n *\u002F\nimport { computed, ref } from \"vue\"\nimport {\n  useGantt,\n  useGanttSelector,\n  useGanttSettings,\n  useGanttViewConfig,\n  resolveScheduleMode,\n} from \".\u002Fcontext\"\nimport { useGanttGestures, wasRecentDrag } from \".\u002Fgestures\"\nimport { trackFraction, trackPoint, type TimelineRowBars, type TimelineRow } from \".\u002Fview-lib\"\nimport { cn } from \"@\u002Flib\u002Futils\"\nimport GanttBar from \".\u002FGanttBar.vue\"\n\nconst props = defineProps\u003C{\n  row: TimelineRow\n  rowIndex: number\n  bars?: TimelineRowBars\n  rangeStartMs: number\n  rangeEndMs: number\n  trackWidth: string\n  trackRemWidth: number\n  rowBorder: \"solid\" | \"dashed\" | null\n  selected: boolean\n  resolveHintStop: (fraction: number) => { index: number; center: number; ms: number; endMs: number } | null\n  isPanning: boolean\n  laneHeightRem: number\n  laneGapRem: number\n  minRowRem: number\n}>()\n\nconst instance = useGantt\u003CTData>()\nconst settings = useGanttSettings\u003CTData>()\nconst viewConfig = useGanttViewConfig\u003CTData>()\nconst gestures = useGanttGestures\u003CTData>()\n\nconst segments = computed(() => props.bars?.segments ?? [])\nconst schedulable = computed(() => !props.row.isGroup || viewConfig.value.parentScheduling)\nconst heightRem = computed(() => props.bars?.heightRem ?? props.minRowRem)\nconst laneCount = computed(() => props.bars?.laneCount ?? 1)\nconst singleTrack = computed(\n  () => resolveScheduleMode(props.row.resource, viewConfig.value.scheduleMode) === \"single\"\n)\nconst laneOffsetRem = computed(() => props.bars?.laneOffsetRem ?? (props.minRowRem - props.laneHeightRem) \u002F 2)\n\nconst hintStop = ref\u003C{ index: number; center: number; ms: number; endMs: number } | null>(null)\nconst hintSuppressed = useGanttSelector\u003CTData, boolean>(\n  (state) => hintStop.value !== null && (state.drag !== null || state.slotDraft !== null)\n)\nconst canSchedule = useGanttSelector\u003CTData, boolean>((state) => state.interactions.selectSlot)\n\nconst hintFreeLane = computed(() => {\n  if (!hintStop.value) return 0\n  const padMs = 30 * 60000 \u002F\u002F MIN_PACK_SLOT, matches lowestFreeLane's own pad\n  const to = Math.max(hintStop.value.endMs, hintStop.value.ms + padMs)\n  const busy = new Set\u003Cnumber>()\n  for (const segment of segments.value) {\n    const segStart = segment.occurrence.start.getTime()\n    const segEnd = Math.max(segment.occurrence.end.getTime(), segStart + padMs)\n    if (segStart \u003C to && segEnd > hintStop.value.ms) busy.add(segment.column ?? 0)\n  }\n  let lane = 0\n  while (busy.has(lane)) lane += 1\n  return lane\n})\nconst hintLane = computed(() => (props.bars?.scheduleMode === \"single\" ? 0 : hintFreeLane.value))\nconst hintTopRem = computed(() =>\n  Math.min(\n    laneOffsetRem.value + hintLane.value * (props.laneHeightRem + props.laneGapRem) + props.laneHeightRem \u002F 2,\n    Math.max(heightRem.value - props.laneHeightRem \u002F 2, props.laneHeightRem \u002F 2)\n  )\n)\nconst hintVisible = computed(\n  () => hintStop.value !== null && hintFreeLane.value \u003C laneCount.value && !hintSuppressed.value && !props.isPanning\n)\nconst hintLabel = computed(() =>\n  viewConfig.value.dragCreate ? settings.value.i18n.labels.scheduleHintDrag : settings.value.i18n.labels.scheduleHint\n)\nconst showHint = computed(\n  () =>\n    viewConfig.value.displayScheduleHint &&\n    schedulable.value &&\n    canSchedule.value &&\n    !props.isPanning &&\n    !!(settings.value.onSelectSlot || settings.value.onSlotClick)\n)\n\nfunction fractionOf(ms: number): number {\n  return Math.min(Math.max((ms - props.rangeStartMs) \u002F (props.rangeEndMs - props.rangeStartMs), 0), 1)\n}\n\nconst dragTarget = useGanttSelector\u003CTData, \"valid\" | \"invalid\" | null>((state) => {\n  const drag = state.drag\n  if (!drag || drag.proposedResourceId !== props.row.resource.id) return null\n  return drag.valid ? \"valid\" : \"invalid\"\n})\nconst ghost = useGanttSelector\u003C\n  TData,\n  { from: number; to: number; color?: string; valid: boolean; title: string; kind: string; occurrenceKey: string } | null\n>((state) => {\n  const drag = state.drag\n  if (!drag || drag.proposedResourceId !== props.row.resource.id) return null\n  return {\n    from: fractionOf(drag.proposedStart.getTime()),\n    to: fractionOf(drag.proposedEnd.getTime()),\n    color: drag.occurrence.event.color,\n    valid: drag.valid,\n    title: drag.occurrence.event.title,\n    kind: drag.kind,\n    occurrenceKey: drag.occurrence.key,\n  }\n})\nconst draft = useGanttSelector\u003CTData, { from: number; to: number; startMs: number; endMs: number } | null>((state) => {\n  const slotDraft = state.slotDraft\n  if (!slotDraft || slotDraft.resourceId !== props.row.resource.id) return null\n  const startMs = slotDraft.start.getTime()\n  const endMs = slotDraft.end.getTime()\n  return { from: fractionOf(startMs), to: fractionOf(endMs), startMs, endMs }\n})\n\nconst draftLane = computed(() => props.bars?.draftLane ?? 0)\nconst draftTopRem = computed(() =>\n  Math.min(\n    laneOffsetRem.value + draftLane.value * (props.laneHeightRem + props.laneGapRem),\n    Math.max(heightRem.value - props.laneHeightRem, 0)\n  )\n)\nconst draftLabel = computed(() =>\n  draft.value\n    ? settings.value.i18n.functions.formatEventTime(\n        new Date(draft.value.startMs),\n        new Date(draft.value.endMs),\n        false,\n        settings.value.locale\n      )\n    : \"\"\n)\n\nfunction createAt(stop: { ms: number; endMs: number }, e: MouseEvent) {\n  if (settings.value.onSlotClick) {\n    settings.value.onSlotClick({ date: new Date(stop.ms), allDay: false, resourceId: props.row.resource.id }, e)\n  } else {\n    settings.value.onSelectSlot?.({\n      start: new Date(stop.ms),\n      end: new Date(Math.max(stop.endMs, stop.ms + 1)),\n      allDay: false,\n      resourceId: props.row.resource.id,\n    })\n  }\n}\n\nconst ghostLane = computed(() => {\n  if (!ghost.value) return 0\n  return segments.value.find((s) => s.occurrence.key === ghost.value!.occurrenceKey)?.column ?? 0\n})\nconst ghostLaneOffsetRem = computed(\n  () => laneOffsetRem.value + ghostLane.value * (props.laneHeightRem + props.laneGapRem) + (props.laneHeightRem - 1.25) \u002F 2\n)\n\nfunction onRowPointerDown(e: PointerEvent) {\n  const target = e.target as HTMLElement\n  if (\n    viewConfig.value.dragCreate &&\n    e.button === 0 &&\n    schedulable.value &&\n    canSchedule.value &&\n    (target === e.currentTarget || !!target.closest?.(\"[data-slot=gantt-schedule-hint]\")) &&\n    !!settings.value.onSelectSlot\n  ) {\n    const stop = props.resolveHintStop(trackFraction(e.currentTarget as HTMLElement, e.clientX))\n    const allowed =\n      stop !== null &&\n      (settings.value.canSelectSlot?.({\n        start: new Date(stop.ms),\n        end: new Date(Math.max(stop.endMs, stop.ms + 1)),\n        allDay: false,\n        resourceId: props.row.resource.id,\n      }) ?? true)\n    if (!allowed) return\n    e.stopPropagation()\n    gestures.beginCreate(e)\n  }\n}\n\nfunction onRowPointerMove(e: PointerEvent) {\n  const interacting = instance.state.value.drag !== null || instance.state.value.slotDraft !== null\n  if (!showHint.value || e.pointerType !== \"mouse\" || interacting) {\n    if (hintStop.value) hintStop.value = null\n    return\n  }\n  if (e.target !== e.currentTarget) {\n    if (hintStop.value) hintStop.value = null\n    return\n  }\n  const el = e.currentTarget as HTMLElement\n  const { fraction, offset } = trackPoint(el, e.clientX)\n  el.style.setProperty(\"--gantt-hint-x\", `${offset}px`)\n  const stop = props.resolveHintStop(fraction)\n  const allowed =\n    stop !== null &&\n    (settings.value.canSelectSlot?.({\n      start: new Date(stop.ms),\n      end: new Date(Math.max(stop.endMs, stop.ms + 1)),\n      allDay: false,\n      resourceId: props.row.resource.id,\n    }) ?? true)\n  const next = allowed ? stop : null\n  if (next?.index !== hintStop.value?.index) hintStop.value = next\n}\n\nfunction onRowPointerLeave() {\n  if (hintStop.value) hintStop.value = null\n}\n\nfunction onRowClick(e: MouseEvent) {\n  if (!viewConfig.value.dragCreate || e.target !== e.currentTarget) return\n  if (!schedulable.value || !canSchedule.value) return\n  if (wasRecentDrag()) return\n  if (!settings.value.onSlotClick && !settings.value.onSelectSlot) return\n  const stop = props.resolveHintStop(trackFraction(e.currentTarget as HTMLElement, e.clientX))\n  if (!stop) return\n  const allowed =\n    settings.value.canSelectSlot?.({\n      start: new Date(stop.ms),\n      end: new Date(Math.max(stop.endMs, stop.ms + 1)),\n      allDay: false,\n      resourceId: props.row.resource.id,\n    }) ?? true\n  if (!allowed) return\n  createAt(stop, e)\n  hintStop.value = null\n}\n\nfunction segmentGeometry(segment: TimelineRowBars[\"segments\"][number]) {\n  const from = fractionOf(props.rangeStartMs + (segment.startMin ?? 0) * 60000)\n  const to = fractionOf(props.rangeStartMs + (segment.endMin ?? 0) * 60000)\n  const lane = segment.column ?? 0\n  const barRemWidth = (to - from) * props.trackRemWidth\n  const wantsOutside =\n    viewConfig.value.barLabel === \"outside\" ||\n    (viewConfig.value.barLabel === \"auto\" && barRemWidth \u003C (viewConfig.value.metrics?.autoLabelMin ?? 7))\n  const placement = !wantsOutside ? \"inside\" : to \u003C= 0.92 ? \"after\" : from >= 0.08 ? \"before\" : \"inside\"\n  return { from, to, lane, placement }\n}\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cdiv\n    data-gantt-row=\"\"\n    :data-gantt-resource=\"row.resource.id\"\n    :data-gantt-row-id=\"row.resource.id\"\n    :data-gantt-row-static=\"!schedulable || undefined\"\n    :data-gantt-bar-min=\"bars?.extent?.from\"\n    :data-gantt-bar-max=\"bars?.extent?.to\"\n    :data-gantt-bar-color=\"bars?.extent?.color\"\n    :data-gantt-bar-label=\"bars?.extent?.label\"\n    :data-gantt-bar-start-ms=\"bars?.extent?.startMs\"\n    :data-drop-target=\"dragTarget ?? undefined\"\n    :data-selected=\"selected || undefined\"\n    :class=\"\n      cn(\n        'data-hover:bg-muted\u002F30 data-selected:bg-primary\u002F5 data-selected:data-hover:bg-primary\u002F5 relative w-full min-w-0',\n        hintVisible && 'cursor-crosshair',\n        rowBorder !== null && 'border-b',\n        rowBorder === 'dashed' && 'border-dashed',\n        dragTarget === 'valid' && 'bg-muted\u002F40',\n        dragTarget === 'invalid' && 'bg-destructive\u002F10'\n      )\n    \"\n    :style=\"{ height: `${heightRem}rem`, minWidth: trackWidth }\"\n    @pointerdown=\"onRowPointerDown\"\n    @pointermove=\"onRowPointerMove\"\n    @pointerleave=\"onRowPointerLeave\"\n    @click=\"onRowClick\"\n  >\n    \u003Cdiv class=\"pointer-events-none absolute inset-0\" style=\"content-visibility: auto\">\n      \u003Ctemplate v-for=\"(segment, segmentIndex) in segments\" :key=\"segment.occurrence.key\">\n        \u003Cdiv\n          v-if=\"segmentGeometry(segment).to > segmentGeometry(segment).from\"\n          :data-drag-kind=\"ghost && ghost.occurrenceKey === segment.occurrence.key ? ghost.kind : undefined\"\n          :data-lane=\"segmentGeometry(segment).lane\"\n          :data-lane-count=\"laneCount\"\n          class=\"group\u002Fgantt-seg pointer-events-auto absolute px-px data-[drag-kind=move]:opacity-0\"\n          :style=\"{\n            insetInlineStart: `${segmentGeometry(segment).from * 100}%`,\n            width: `${Math.max((segmentGeometry(segment).to - segmentGeometry(segment).from) * 100, 0.5)}%`,\n            top: `${laneOffsetRem + segmentGeometry(segment).lane * (laneHeightRem + laneGapRem)}rem`,\n            height: `${laneHeightRem}rem`,\n            zIndex: segment.occurrence.event.zIndex ?? 10 + (singleTrack ? segmentIndex : segmentGeometry(segment).lane),\n          }\"\n        >\n          \u003CGanttBar :segment=\"segment\" :label-outside=\"segmentGeometry(segment).placement !== 'inside'\" :row-title=\"row.resource.title\" class=\"h-full\" \u002F>\n          \u003Cspan\n            v-if=\"segmentGeometry(segment).placement !== 'inside'\"\n            data-slot=\"gantt-bar-label\"\n            :data-placement=\"segmentGeometry(segment).placement\"\n            :class=\"\n              cn(\n                'text-foreground pointer-events-none absolute top-1\u002F2 z-10 max-w-60 -translate-y-1\u002F2 truncate font-medium',\n                'group-data-[drag-kind^=resize]\u002Fgantt-seg:opacity-0',\n                segmentGeometry(segment).placement === 'after' ? 'start-full ms-2' : 'end-full me-2'\n              )\n            \"\n          >\n            {{ segment.occurrence.event.title }}\n          \u003C\u002Fspan>\n        \u003C\u002Fdiv>\n      \u003C\u002Ftemplate>\n      \u003Cdiv\n        v-if=\"bars?.summary && segments.length === 0\"\n        data-slot=\"gantt-summary\"\n        aria-hidden\n        class=\"pointer-events-none absolute top-1\u002F2 -translate-y-1\u002F2\"\n        :style=\"{\n          insetInlineStart: `${bars.summary.from * 100}%`,\n          width: `${Math.max((bars.summary.to - bars.summary.from) * 100, 0.5)}%`,\n        }\"\n      >\n        \u003Ccomponent\n          :is=\"\n            () =>\n              viewConfig.renderSummary!({\n                resource: row.resource,\n                start: new Date(rangeStartMs + bars!.summary!.from * (rangeEndMs - rangeStartMs)),\n                end: new Date(rangeStartMs + bars!.summary!.to * (rangeEndMs - rangeStartMs)),\n                progress: bars!.summary!.progress,\n              })\n          \"\n          v-if=\"viewConfig.renderSummary\"\n        \u002F>\n        \u003Ctemplate v-else>\n          \u003Cspan aria-hidden class=\"bg-muted-foreground\u002F50 absolute start-0 top-1\u002F2 h-3 w-0.5 -translate-y-1\u002F2 rounded-full\" \u002F>\n          \u003Cspan aria-hidden class=\"bg-muted-foreground\u002F50 absolute end-0 top-1\u002F2 h-3 w-0.5 -translate-y-1\u002F2 rounded-full\" \u002F>\n          \u003Cdiv class=\"bg-muted-foreground\u002F20 relative h-1.5 overflow-hidden rounded-full\">\n            \u003Cdiv\n              v-if=\"bars.summary.progress !== null\"\n              data-slot=\"gantt-summary-progress\"\n              class=\"bg-muted-foreground\u002F50 absolute inset-y-0 start-0 rounded-full\"\n              :style=\"{ width: `${bars.summary.progress}%` }\"\n            \u002F>\n          \u003C\u002Fdiv>\n          \u003Cspan v-if=\"bars.summary.progress !== null\" class=\"text-muted-foreground absolute start-full top-1\u002F2 ms-2 -translate-y-1\u002F2 whitespace-nowrap\">\n            {{ bars.summary.progress }}%\n          \u003C\u002Fspan>\n        \u003C\u002Ftemplate>\n      \u003C\u002Fdiv>\n      \u003Cdiv\n        v-if=\"ghost\"\n        data-slot=\"gantt-drag-ghost\"\n        :data-kind=\"ghost.kind\"\n        :data-drop-invalid=\"!ghost.valid || undefined\"\n        :class=\"\n          cn(\n            'pointer-events-none absolute z-40 h-5 rounded-sm border border-dashed font-medium',\n            !ghost.valid && 'border-destructive bg-destructive\u002F10 text-destructive',\n            ghost.valid && ghost.kind === 'move' && 'border-(--gantt-event-color)\u002F50 bg-(--gantt-event-color)\u002F8',\n            ghost.valid && ghost.kind !== 'move' && 'text-foreground border-(--gantt-event-color)\u002F70 bg-(--gantt-event-color)\u002F22'\n          )\n        \"\n        :style=\"{\n          insetInlineStart: `${ghost.from * 100}%`,\n          width: `${Math.max((ghost.to - ghost.from) * 100, 0.5)}%`,\n          top: `${ghostLaneOffsetRem}rem`,\n          '--gantt-event-color': ghost.color ?? 'var(--color-primary)',\n        }\"\n      >\n        \u003Cspan v-if=\"ghost.kind !== 'move'\" class=\"pointer-events-none absolute start-full top-1\u002F2 ms-2 max-w-60 -translate-y-1\u002F2 truncate whitespace-nowrap\">\n          {{ ghost.title }}\n        \u003C\u002Fspan>\n      \u003C\u002Fdiv>\n      \u003Cdiv\n        v-if=\"draft\"\n        data-slot=\"gantt-slot-draft\"\n        :data-lane=\"draftLane\"\n        class=\"border-primary bg-background pointer-events-none absolute z-40 overflow-hidden rounded-sm border border-dashed\"\n        :style=\"{\n          insetInlineStart: `${draft.from * 100}%`,\n          width: `${Math.max((draft.to - draft.from) * 100, 0.5)}%`,\n          top: `${draftTopRem}rem`,\n          height: `${laneHeightRem}rem`,\n        }\"\n      >\n        \u003Cspan aria-hidden class=\"bg-primary\u002F15 absolute inset-0\" \u002F>\n      \u003C\u002Fdiv>\n    \u003C\u002Fdiv>\n    \u003Cdiv\n      v-if=\"draft && draftLabel\"\n      data-slot=\"gantt-slot-draft-label\"\n      class=\"pointer-events-none absolute z-40\"\n      :style=\"{ insetInlineStart: `${draft.to * 100}%`, top: `${draftTopRem}rem`, height: `${laneHeightRem}rem` }\"\n    >\n      \u003Cspan class=\"bg-foreground text-background absolute start-full top-1\u002F2 ms-2 inline-flex w-max -translate-y-1\u002F2 items-center rounded-md px-2 py-1 text-xs font-medium whitespace-nowrap\">\n        {{ draftLabel }}\n        \u003Cspan aria-hidden class=\"bg-foreground absolute start-0 top-1\u002F2 size-1.5 -translate-x-1\u002F2 -translate-y-1\u002F2 rotate-45 rounded-[1px]\" \u002F>\n      \u003C\u002Fspan>\n    \u003C\u002Fdiv>\n    \u003Cdiv\n      v-if=\"hintVisible && hintStop\"\n      data-slot=\"gantt-schedule-hint\"\n      class=\"pointer-events-none absolute z-30 -translate-x-1\u002F2 -translate-y-1\u002F2 rtl:translate-x-1\u002F2\"\n      :style=\"{ insetInlineStart: 'var(--gantt-hint-x, 50%)', top: `${hintTopRem}rem` }\"\n    >\n      \u003Ccomponent :is=\"() => viewConfig.renderScheduleHint!({ start: new Date(hintStop!.ms), end: new Date(Math.max(hintStop!.endMs, hintStop!.ms + 1)), resource: row.resource })\" v-if=\"viewConfig.renderScheduleHint\" \u002F>\n      \u003Ctemplate v-else>\n        \u003Cbutton\n          type=\"button\"\n          data-slot=\"gantt-schedule-hint-tile\"\n          :aria-label=\"hintLabel\"\n          class=\"border-primary ring-background\u002F80 pointer-events-none block size-5 rounded-full border-2 bg-transparent ring-1\"\n          @pointerdown=\"(e) => { if (!viewConfig.dragCreate) e.stopPropagation() }\"\n          @click=\"(e) => { e.stopPropagation(); if (!wasRecentDrag()) { createAt(hintStop!, e); hintStop = null } }\"\n        \u002F>\n        \u003Cspan\n          data-slot=\"gantt-schedule-hint-bubble\"\n          :class=\"\n            cn(\n              'bg-foreground text-background absolute start-1\u002F2 inline-flex w-max -translate-x-1\u002F2 items-center rounded-md px-2 py-1 text-xs font-medium whitespace-nowrap',\n              rowIndex === 0 ? 'top-full mt-2' : 'bottom-full mb-2'\n            )\n          \"\n        >\n          {{ hintLabel }}\n          \u003Cspan\n            aria-hidden\n            :class=\"cn('bg-foreground absolute start-1\u002F2 size-2.5 -translate-x-1\u002F2 rotate-45 rounded-[2px]', rowIndex === 0 ? '-top-1' : '-bottom-1')\"\n          \u002F>\n        \u003C\u002Fspan>\n      \u003C\u002Ftemplate>\n    \u003C\u002Fdiv>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n",{"path":4855,"type":480,"target":4856,"content":4857},"src\u002Freui\u002Fgantt\u002FGanttTitle.vue","components\u002Freui\u002Fgantt\u002FGanttTitle.vue","\u003Cscript setup lang=\"ts\">\n\u002F** Порт ReUI GanttTitle (gantt-nav.tsx, MIT). *\u002F\nimport type { HTMLAttributes, VNodeChild } from \"vue\"\nimport { computed } from \"vue\"\nimport { Primitive } from \"reka-ui\"\nimport { useGanttNavigation } from \".\u002Fcontext\"\nimport { cn } from \"@\u002Flib\u002Futils\"\n\nconst props = withDefaults(\n  defineProps\u003C{\n    class?: HTMLAttributes[\"class\"]\n    asChild?: boolean\n    format?: (ctx: { title: string }) => VNodeChild\n  }>(),\n  { asChild: false }\n)\n\ndefineSlots\u003C{ default?(): unknown }>()\n\nconst { title } = useGanttNavigation()\nconst formatted = computed(() => props.format?.({ title: title.value }))\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003CPrimitive\n    as=\"div\"\n    :as-child=\"props.asChild\"\n    data-slot=\"gantt-title\"\n    aria-live=\"polite\"\n    :class=\"cn('min-w-0 truncate text-sm font-semibold', props.class)\"\n  >\n    \u003Cslot>\n      \u003Ccomponent :is=\"() => formatted\" v-if=\"formatted\" \u002F>\n      \u003Ctemplate v-else>{{ title }}\u003C\u002Ftemplate>\n    \u003C\u002Fslot>\n  \u003C\u002FPrimitive>\n\u003C\u002Ftemplate>\n",{"path":4859,"type":480,"target":4860,"content":4861},"src\u002Freui\u002Fgantt\u002FGanttToolbar.vue","components\u002Freui\u002Fgantt\u002FGanttToolbar.vue","\u003Cscript setup lang=\"ts\">\n\u002F** Порт ReUI GanttToolbar (gantt-nav.tsx, MIT) — свободный слот для кнопок потребителя, чистая layout-обёртка. *\u002F\nimport type { HTMLAttributes } from \"vue\"\nimport { Primitive } from \"reka-ui\"\nimport { useGanttViewConfig } from \".\u002Fcontext\"\nimport { cn } from \"@\u002Flib\u002Futils\"\n\nconst props = withDefaults(defineProps\u003C{ class?: HTMLAttributes[\"class\"]; asChild?: boolean }>(), { asChild: false })\n\nconst viewConfig = useGanttViewConfig()\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003CPrimitive\n    as=\"div\"\n    :as-child=\"props.asChild\"\n    data-slot=\"gantt-toolbar\"\n    :class=\"cn('flex items-center gap-2', viewConfig.classNames?.toolbar, props.class)\"\n  >\n    \u003Cslot \u002F>\n  \u003C\u002FPrimitive>\n\u003C\u002Ftemplate>\n",{"path":4863,"type":480,"target":4864,"content":4865},"src\u002Freui\u002Fgantt\u002FGanttTreeRow.vue","components\u002Freui\u002Fgantt\u002FGanttTreeRow.vue","\u003Cscript setup lang=\"ts\">\n\u002F**\n * Порт ReUI GanttTreeRow (gantt-view.tsx, MIT, разметка ~2627-2840) — одна\n * строка дерева ресурсов: чекбокс (лист) \u002F шеврон свёртки (группа),\n * отступ по глубине, ручка reorder (опционально), доп. колонки.\n *\n * ponytail: `ContextMenu` (`viewConfig.renderResourceMenu`) не перенесён —\n * тот же выбор\u002Fпричина, что и `GanttBar`\u002F`GanttOffscreenChips`\u002F\n * `GanttZoomControl` (16\u002F16 у всех трёх): не влияет на статичный скриншот,\n * рендерится только при переданном `renderResourceMenu`. Сама РУЧКА\n * reorder рендерится (проп `onGripPointerDown`\u002Fэмит `grip-pointerdown\"`),\n * но жест перетаскивания строки — часть `gantt-dnd.tsx` (последний файл),\n * здесь только разметка и проброс события, как и в оригинале (сам\n * `GanttTreeRow` тоже не владеет жестом, только зовёт переданный колбэк).\n *\u002F\nimport { computed } from \"vue\"\nimport { Button } from \"@\u002Fcomponents\u002Fui\u002Fbutton\"\nimport { Checkbox } from \"@\u002Fcomponents\u002Fui\u002Fcheckbox\"\nimport { useGanttSettings, useGanttViewConfig, DEFAULT_ROW_ALIGN, type GanttColumn } from \".\u002Fcontext\"\nimport { cn } from \"@\u002Flib\u002Futils\"\nimport type { TimelineRow } from \".\u002Fview-lib\"\n\nconst DEFAULT_COLUMN_WIDTH = 96\n\nconst props = defineProps\u003C{\n  row: TimelineRow\n  heightRem: number\n  bandRem: number\n  columns: GanttColumn[]\n  nameWidth: number\n  dimmed: boolean\n  selected: boolean\n  \u002F** Omit to hide the checkbox column entirely (matches `onSelectedChange` absent upstream). *\u002F\n  showCheckbox?: boolean\n  \u002F** Present -> renders the reorder grip; absent -> no grip (matches `onGripPointerDown` absent upstream). *\u002F\n  reorderable?: boolean\n}>()\n\nconst emit = defineEmits\u003C{\n  toggle: [row: TimelineRow]\n  \"selected-change\": [id: string, checked: boolean]\n  \"grip-pointerdown\": [e: PointerEvent, row: TimelineRow]\n}>()\n\nconst settings = useGanttSettings()\nconst viewConfig = useGanttViewConfig()\n\nconst ctx = computed(() => ({\n  resource: props.row.resource,\n  depth: props.row.depth,\n  isGroup: props.row.isGroup,\n  collapsed: props.row.collapsed,\n}))\n\nconst alignStart = computed(() => (viewConfig.value.rowAlign ?? DEFAULT_ROW_ALIGN) === \"start\")\nconst resourceLabel = computed(() => viewConfig.value.renderResourceLabel?.(ctx.value))\n\nfunction onRowClick(e: MouseEvent) {\n  if (!settings.value.onResourceClick) return\n  if ((e.target as HTMLElement).closest(\"button, [role=checkbox]\")) return\n  settings.value.onResourceClick(ctx.value, e)\n}\nfunction onRowDoubleClick(e: MouseEvent) {\n  if (!settings.value.onResourceDoubleClick) return\n  if ((e.target as HTMLElement).closest(\"button, [role=checkbox]\")) return\n  settings.value.onResourceDoubleClick(ctx.value, e)\n}\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cdiv\n    data-slot=\"gantt-row-group\"\n    :data-gantt-row-id=\"row.resource.id\"\n    :data-selected=\"selected || undefined\"\n    :class=\"\n      cn(\n        'group\u002Fgantt-row data-hover:bg-muted\u002F40 data-selected:bg-primary\u002F5 data-selected:data-hover:bg-primary\u002F5 flex border-b',\n        dimmed && 'opacity-50'\n      )\n    \"\n    :style=\"{ height: `${heightRem}rem` }\"\n    @click=\"onRowClick\"\n    @dblclick=\"onRowDoubleClick\"\n  >\n    \u003Cdiv class=\"flex h-full w-full min-w-0\">\n      \u003Cdiv\n        data-slot=\"gantt-tree-cell\"\n        :class=\"cn('flex shrink-0 ps-3 pe-3', alignStart ? 'items-start' : 'items-center', row.isGroup && 'font-medium')\"\n        :style=\"{ width: `${nameWidth}px` }\"\n      >\n        \u003Cdiv class=\"flex w-full min-w-0 items-center\" :style=\"{ height: `${bandRem}rem` }\">\n          \u003Cbutton\n            v-if=\"reorderable\"\n            type=\"button\"\n            data-slot=\"gantt-row-grip\"\n            :aria-label=\"settings.i18n.labels.reorder\"\n            class=\"text-muted-foreground\u002F60 hover:text-foreground -ms-1.5 me-1.5 flex w-3.5 shrink-0 cursor-grab touch-none items-center justify-center opacity-0 group-hover\u002Fgantt-row:opacity-100 group-data-hover\u002Fgantt-row:opacity-100 focus-visible:opacity-100 pointer-coarse:opacity-100\"\n            @pointerdown=\"(e) => emit('grip-pointerdown', e, row)\"\n            @click.stop\n          >\n            \u003Csvg xmlns=\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-3\" aria-hidden=\"true\">\u003Ccircle cx=\"9\" cy=\"5\" r=\"1\" \u002F>\u003Ccircle cx=\"9\" cy=\"12\" r=\"1\" \u002F>\u003Ccircle cx=\"9\" cy=\"19\" r=\"1\" \u002F>\u003Ccircle cx=\"15\" cy=\"5\" r=\"1\" \u002F>\u003Ccircle cx=\"15\" cy=\"12\" r=\"1\" \u002F>\u003Ccircle cx=\"15\" cy=\"19\" r=\"1\" \u002F>\u003C\u002Fsvg>\n          \u003C\u002Fbutton>\n          \u003Cspan aria-hidden class=\"shrink-0\" :style=\"{ width: `${row.depth * 0.875}rem` }\" \u002F>\n          \u003Cspan class=\"me-1 flex w-5 shrink-0 items-center justify-start\">\n            \u003CButton\n              v-if=\"row.isGroup\"\n              variant=\"ghost\"\n              size=\"icon-xs\"\n              :aria-expanded=\"!row.collapsed\"\n              :aria-label=\"row.resource.title\"\n              :class=\"\n                cn(\n                  'size-5! aria-expanded:bg-transparent!',\n                  row.collapsed ? 'text-foreground' : 'text-muted-foreground aria-expanded:text-muted-foreground! hover:text-foreground!'\n                )\n              \"\n              @click=\"emit('toggle', row)\"\n            >\n              \u003Csvg xmlns=\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" :class=\"cn('size-3.5 transition-transform', !row.collapsed && 'rotate-90')\" aria-hidden=\"true\">\u003Cpath d=\"m9 18 6-6-6-6\" \u002F>\u003C\u002Fsvg>\n            \u003C\u002FButton>\n            \u003CCheckbox\n              v-else-if=\"viewConfig.rowCheckboxes && showCheckbox\"\n              data-slot=\"gantt-row-checkbox\"\n              :model-value=\"selected\"\n              @update:model-value=\"(checked) => emit('selected-change', row.resource.id, !!checked)\"\n              :aria-label=\"row.resource.title\"\n              :class=\"cn('size-3.5 opacity-0 transition-opacity group-hover\u002Fgantt-row:opacity-100 group-data-hover\u002Fgantt-row:opacity-100 focus-visible:opacity-100', selected && 'opacity-100')\"\n            \u002F>\n          \u003C\u002Fspan>\n          \u003Ccomponent :is=\"() => resourceLabel\" v-if=\"resourceLabel\" \u002F>\n          \u003Cspan v-else class=\"truncate\">{{ row.resource.title }}\u003C\u002Fspan>\n        \u003C\u002Fdiv>\n      \u003C\u002Fdiv>\n      \u003Cdiv\n        v-for=\"column in columns\"\n        :key=\"column.id\"\n        data-slot=\"gantt-tree-column-cell\"\n        :data-column=\"column.id\"\n        :class=\"cn('flex shrink-0 px-2', alignStart ? 'items-start' : 'items-center', column.className)\"\n        :style=\"{ width: `${column.width ?? DEFAULT_COLUMN_WIDTH}px` }\"\n      >\n        \u003Cdiv\n          :class=\"cn('flex w-full min-w-0 items-center', column.align === 'center' && 'justify-center', column.align === 'end' && 'justify-end')\"\n          :style=\"{ height: `${bandRem}rem` }\"\n        >\n          \u003Ccomponent :is=\"() => column.render?.(ctx)\" v-if=\"column.render\" \u002F>\n        \u003C\u002Fdiv>\n      \u003C\u002Fdiv>\n      \u003Cdiv class=\"min-w-0 flex-1\" \u002F>\n    \u003C\u002Fdiv>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n",{"path":4867,"type":480,"target":4868,"content":4869},"src\u002Freui\u002Fgantt\u002FGanttTreeSplitPanes.vue","components\u002Freui\u002Fgantt\u002FGanttTreeSplitPanes.vue","\u003Cscript setup lang=\"ts\">\n\u002F**\n * Порт секции \"split panes\" из GanttView (gantt-view.tsx, MIT, строки\n * ~911-1087 и ~2337-2392) — резинка\u002Fсплиттер между деревом ресурсов и\n * шкалой времени: ширина панели дерева (управляемая\u002Fнеуправляемая,\n * `treePanel.width`\u002F`onWidthChange`), перетаскивание указателем, изменение\n * стрелками с фокуса, сброс по двойному клику, и адаптивное сжатие дерева\n * на узком контейнере (`ResizeObserver` на теле, чтобы шкала времени\n * никогда не падала ниже `minTimelineWidth`).\n *\n * ponytail: сама разметка строк дерева (`GanttTreeRow`, ~950 строк:\n * чекбоксы, шеврон свёртки, доп. колонки, drag-reorder ресурсов, hint\n * \"добавить задачу\") НЕ входит в этот срез — контент обеих панелей отдаётся\n * именованными слотами (`#tree`\u002F`#timeline`), это следующий, самый большой\n * кусок `GanttView`. Здесь портирован только механизм сплиттера, который\n * от содержимого панелей не зависит.\n *\n * Живая ширина при перетаскивании пишется напрямую в DOM\n * (`treePaneRef.value.style.width`), как и в оригинале — состояние\n * коммитится только при отпускании, иначе каждый `pointermove` перерисовал\n * бы дерево и шкалу целиком.\n *\n * Секция 3 (скролл, gantt-view.tsx ~1089-1174): вертикальная синхронизация\n * панелей — какая бы панель ни скроллилась, вторая зеркалит `scrollTop`.\n * ponytail: обе панели здесь — сами себе \"viewport\" (`overflow-y-auto` +\n * `data-slot=\"scroll-area-viewport\"` прямо на `treePaneRef`\u002F`timelinePaneRef`),\n * а не `ScrollArea` -> вложенный viewport, как в оригинале (тот кастомный\n * скроллбар появится вместе с реальным содержимым строк). Wheel-блокировка\n * \"обе панели в одном кадре\" (оригинал, комментарий про флик при инерции\n * трекпада) не перенесена — она чинит эффект, видимый только в живом\n * скролле, а не на статичном скриншоте; сам факт синхронизации (через\n * `scroll`-событие) работает и без неё. Добавить, когда появится\n * интеракционный кейс. Linked row hover (`data-hover` между близнецами строк\n * в двух панелях, gantt-view.tsx ~1176-1209) перенесён туда же, дословно.\n *\n * Автоцентрирование + infinite-scroll по краю (gantt-view.tsx ~1211-1440,\n * ~1442-1548 частично) — портированы, читают\u002Fпишут `timelinePaneRef`\n * НАПРЯМУЮ как единственный viewport (не через `getPaneViewport`, см.\n * секцию 3 выше). `[data-gantt-axis]` уже есть на юнит-ряде\n * `GanttTimelineHeader.vue`, `instance.internals.extendRange`\u002F\n * `setViewportCenter` уже реальны (context.ts) — обе стороны состояния\n * были готовы, не хватало только этого файла.\n *\n * Переанкеровка при зуме (`anchorZoomCenter`\u002F`anchorZoomPointer`\u002Fwheel-zoom,\n * gantt-view.tsx ~1490-1622) теперь портирована здесь — этот файл\n * единственный владелец `pendingRestore` и реального `timelinePaneRef`,\n * которые ей нужны. `zoom` приходит пропом от `GanttView` (единственный\n * потребитель, который держит `useGanttZoom()`); `anchorZoomCenter`\u002F\n * `anchorZoomPointer` экспонированы через `defineExpose`, чтобы\n * `GanttZoomControl` (сосед по дереву, не потомок) мог анкорить перед\n * своими кнопками через `GanttView`'s template-ref (тот же приём, что уже\n * применён для `GanttOffscreenChips`'s `pane-ref`). Ctrl\u002FCmd+wheel зум —\n * `onWheelZoom`-проп, вызываемый с уже вычисленным (не обязательно\n * клэмпнутым — `zoom.ts`'s `setZoom` клэмпит сам) целевым значением;\n * сам множитель зума этот файл не хранит, только реагирует на его смену.\n *\u002F\nimport { computed, onBeforeUnmount, onMounted, ref, watch, type HTMLAttributes } from \"vue\"\nimport { format } from \"date-fns\"\nimport { useGantt, useGanttSettings, useGanttViewConfig, type GanttTreePanelConfig } from \".\u002Fcontext\"\nimport { getScrollStart, setScrollStart, trackPoint } from \".\u002Fview-lib\"\nimport { toZoned } from \".\u002Flib\"\nimport { DEFAULT_ZOOM_RANGE } from \".\u002Fzoom\"\nimport { cn } from \"@\u002Flib\u002Futils\"\n\nconst DEFAULT_TREE_PANEL: Required\u003CGanttTreePanelConfig> = {\n  width: 288,\n  minWidth: 180,\n  maxWidth: 640,\n  resizable: true,\n  nameColumnWidth: 208,\n  onWidthChange: () => {},\n}\n\nconst props = withDefaults(\n  defineProps\u003C{\n    class?: HTMLAttributes[\"class\"]\n    treePanel?: GanttTreePanelConfig\n    \u002F** Timeline pane never shrinks below this so it stays usable on narrow screens. *\u002F\n    minTimelineWidth?: number\n    \u002F** Current effective zoom multiplier (from `useGanttZoom()`, owned by `GanttView`). Drives the zoom re-anchor watcher below. *\u002F\n    zoom?: number\n    \u002F** Ctrl\u002FCmd+wheel or pinch zoom target, already computed by the caller (gantt-view.tsx ~1593-1625) - forwarded to `GanttView`'s `useGanttZoom().setZoom`. *\u002F\n    onWheelZoom?: (next: number) => void\n  }>(),\n  { minTimelineWidth: 200, zoom: undefined, onWheelZoom: undefined }\n)\n\ndefineSlots\u003C{ tree?(): unknown; timeline?(): unknown }>()\n\nconst settings = useGanttSettings()\nconst instance = useGantt()\nconst viewConfig = useGanttViewConfig()\n\nconst treeConfig = computed(() => ({ ...DEFAULT_TREE_PANEL, ...props.treePanel }))\n\nfunction clampTree(width: number): number {\n  return Math.min(Math.max(width, treeConfig.value.minWidth), treeConfig.value.maxWidth)\n}\n\nconst treeWidth = ref(treeConfig.value.width)\nconst configuredTreeWidth = computed(() => clampTree(treeWidth.value))\n\nconst bodyRef = ref\u003CHTMLElement | null>(null)\nconst treePaneRef = ref\u003CHTMLElement | null>(null)\nconst timelinePaneRef = ref\u003CHTMLElement | null>(null)\nconst containerWidth = ref(0)\n\nfunction clampContainer(width: number, container: number): number {\n  if (container \u003C= 0) return width\n  const ceiling = container - props.minTimelineWidth - 1\n  \u002F\u002F The tree's own minWidth is a PREFERENCE, not a licence to squeeze the\n  \u002F\u002F timeline out of existence: cap it by what the container can actually\n  \u002F\u002F spare.\n  const floor = Math.min(treeConfig.value.minWidth, Math.max(ceiling, 0))\n  return Math.max(Math.min(width, ceiling), Math.min(floor, container - 1))\n}\n\nconst clampedTreeWidth = computed(() => clampContainer(configuredTreeWidth.value, containerWidth.value))\n\nlet observer: ResizeObserver | undefined\nonMounted(() => {\n  const body = bodyRef.value\n  if (!body) return\n  const update = () => {\n    containerWidth.value = body.clientWidth\n  }\n  update()\n  observer = new ResizeObserver(update)\n  observer.observe(body)\n})\nonBeforeUnmount(() => observer?.disconnect())\n\n\u002F\u002F Both panes scroll vertically; whichever moves drives the other.\nlet unlinkScroll: (() => void) | undefined\nonMounted(() => {\n  const treeViewport = treePaneRef.value\n  const timelineViewport = timelinePaneRef.value\n  if (!treeViewport || !timelineViewport) return\n  const link = (source: HTMLElement, target: HTMLElement) => {\n    \u002F\u002F Mirror only when the source's own vertical position changed - assign\n    \u002F\u002F only on drift, so the mirrored handler then no-ops (no loop).\n    let lastTop = source.scrollTop\n    const onScroll = () => {\n      if (source.scrollTop === lastTop) return\n      lastTop = source.scrollTop\n      if (target.scrollTop !== source.scrollTop) target.scrollTop = source.scrollTop\n    }\n    source.addEventListener(\"scroll\", onScroll)\n    return () => source.removeEventListener(\"scroll\", onScroll)\n  }\n  const unlinkTree = link(treeViewport, timelineViewport)\n  const unlinkTimeline = link(timelineViewport, treeViewport)\n  unlinkScroll = () => {\n    unlinkTree()\n    unlinkTimeline()\n  }\n})\nonBeforeUnmount(() => unlinkScroll?.())\n\n\u002F\u002F Linked row hover: mirror data-hover onto the row's twin in the other pane\n\u002F\u002F (gantt-view.tsx ~1176-1209, dословно).\nlet unlinkHover: (() => void) | undefined\nonMounted(() => {\n  const body = bodyRef.value\n  if (!body) return\n  let current: string | null = null\n  const apply = (id: string | null) => {\n    if (id === current) return\n    if (current) {\n      body.querySelectorAll(`[data-gantt-row-id=\"${CSS.escape(current)}\"]`).forEach((el) => el.removeAttribute(\"data-hover\"))\n    }\n    if (id) {\n      body.querySelectorAll(`[data-gantt-row-id=\"${CSS.escape(id)}\"]`).forEach((el) => el.setAttribute(\"data-hover\", \"\"))\n    }\n    current = id\n  }\n  const onOver = (e: PointerEvent) => {\n    const row = (e.target as HTMLElement | null)?.closest?.(\"[data-gantt-row-id]\")\n    apply(row?.getAttribute(\"data-gantt-row-id\") ?? null)\n  }\n  const onLeave = () => apply(null)\n  body.addEventListener(\"pointerover\", onOver)\n  body.addEventListener(\"pointerleave\", onLeave)\n  unlinkHover = () => {\n    body.removeEventListener(\"pointerover\", onOver)\n    body.removeEventListener(\"pointerleave\", onLeave)\n    apply(null)\n  }\n})\nonBeforeUnmount(() => unlinkHover?.())\n\n\u002F\u002F ----- auto-center + infinite-scroll bookkeeping (not template-bound, so\n\u002F\u002F plain mutable locals rather than ref() - matches the original's useRef,\n\u002F\u002F which also does not trigger a re-render on write). -----\nlet lastUserScrollAt = 0\nlet extendLock = false\nlet pendingRestore: { ms: number; align: \"start\" | \"center\"; offsetPx?: number } | null = null\nconst manage = { key: \"\", buffered: false, userTook: false }\n\u002F\u002F Fine (sub-period) viewport-center instant, refreshed by\n\u002F\u002F setupViewportCenterReport's measure() on every scroll frame - the anchor\n\u002F\u002F a consumer-driven (controlled) zoom change falls back to when nothing\n\u002F\u002F already staged a `pendingRestore` (gantt-view.tsx's `fineCenterRef`,\n\u002F\u002F ~1470-1503).\nlet fineCenterMs: number | null = null\nlet lastZoom: number | null = null\n\n\u002F** Only user gestures may extend the range - programmatic scrolls must never grow it. *\u002F\nfunction setupUserScrollIntent() {\n  const pane = timelinePaneRef.value\n  if (!pane) return () => {}\n  const markIntent = () => {\n    lastUserScrollAt = performance.now()\n  }\n  const markWheelIntent = (e: WheelEvent) => {\n    if (!e.ctrlKey && !e.metaKey) markIntent()\n  }\n  const onPointerDown = (e: PointerEvent) => {\n    const target = e.target as HTMLElement | null\n    if (target?.closest(\"[data-slot=scroll-area-scrollbar], [data-slot=gantt-timeline-header], [data-gantt-native-scroll]\")) {\n      markIntent()\n    }\n  }\n  pane.addEventListener(\"wheel\", markWheelIntent, { passive: true })\n  pane.addEventListener(\"pointerdown\", onPointerDown)\n  pane.addEventListener(\"touchstart\", markIntent, { passive: true })\n  pane.addEventListener(\"keydown\", markIntent)\n  return () => {\n    pane.removeEventListener(\"wheel\", markWheelIntent)\n    pane.removeEventListener(\"pointerdown\", onPointerDown)\n    pane.removeEventListener(\"touchstart\", markIntent)\n    pane.removeEventListener(\"keydown\", markIntent)\n  }\n}\n\n\u002F** Grow the range near an edge, keeping the position anchored to a timestamp. *\u002F\nfunction setupInfiniteScroll() {\n  if (!viewConfig.value.infiniteScroll) return () => {}\n  const viewport = timelinePaneRef.value\n  if (!viewport) return () => {}\n  const infiniteEdgePx = viewConfig.value.metrics?.infiniteScrollEdge ?? 160\n  const tryExtend = (direction: \"before\" | \"after\") => {\n    const axis = viewport.querySelector\u003CHTMLElement>(\"[data-gantt-axis]\")\n    const liveStart = Number(axis?.dataset.ganttRangeStart)\n    const liveEnd = Number(axis?.dataset.ganttRangeEnd)\n    if (!axis || Number.isNaN(liveStart) || Number.isNaN(liveEnd)) return\n    extendLock = true\n    manage.userTook = true\n    pendingRestore = {\n      ms: liveStart + (getScrollStart(viewport) \u002F viewport.scrollWidth) * (liveEnd - liveStart),\n      align: \"start\",\n    }\n    if (!instance.internals.extendRange(direction)) {\n      pendingRestore = null\n      extendLock = false\n    }\n  }\n  const isRtl = getComputedStyle(viewport).direction === \"rtl\"\n  const onScroll = () => {\n    if (extendLock) return\n    if (performance.now() - lastUserScrollAt > 1200) return\n    if (viewport.scrollWidth \u003C= viewport.clientWidth + 8) return\n    const fromStart = getScrollStart(viewport)\n    const fromEnd = viewport.scrollWidth - fromStart - viewport.clientWidth\n    const direction = fromStart \u003C infiniteEdgePx ? \"before\" : fromEnd \u003C infiniteEdgePx ? \"after\" : null\n    if (!direction) return\n    tryExtend(direction)\n  }\n  const onWheel = (e: WheelEvent) => {\n    if (e.ctrlKey || e.metaKey) return\n    if (extendLock || e.deltaX === 0) return\n    if (viewport.scrollWidth \u003C= viewport.clientWidth + 8) return\n    const towardStart = isRtl ? e.deltaX > 0 : e.deltaX \u003C 0\n    if (towardStart && getScrollStart(viewport) \u003C= 0) tryExtend(\"before\")\n    else if (!towardStart && getScrollStart(viewport) + viewport.clientWidth >= viewport.scrollWidth - 1) tryExtend(\"after\")\n  }\n  viewport.addEventListener(\"scroll\", onScroll)\n  viewport.addEventListener(\"wheel\", onWheel, { passive: true })\n  return () => {\n    viewport.removeEventListener(\"scroll\", onScroll)\n    viewport.removeEventListener(\"wheel\", onWheel)\n  }\n}\n\n\u002F** Report the visible-center instant so the nav title names what you are looking at. *\u002F\nfunction setupViewportCenterReport() {\n  const viewport = timelinePaneRef.value\n  if (!viewport) return () => {}\n  const scale = instance.state.value.scale\n  const keyFmt =\n    scale === \"day\" ? \"yyyy-MM-dd\" : scale === \"week\" ? \"RRRR-'W'II\" : scale === \"month\" ? \"yyyy-MM\" : scale === \"quarter\" ? \"yyyy-qqq\" : \"yyyy\"\n  let raf = 0\n  let lastKey = \"\"\n  const measure = () => {\n    raf = 0\n    const axis = viewport.querySelector\u003CHTMLElement>(\"[data-gantt-axis]\")\n    const liveStart = Number(axis?.dataset.ganttRangeStart)\n    const liveEnd = Number(axis?.dataset.ganttRangeEnd)\n    if (!axis || Number.isNaN(liveStart) || Number.isNaN(liveEnd)) return\n    const fraction = (getScrollStart(viewport) + viewport.clientWidth \u002F 2) \u002F Math.max(1, viewport.scrollWidth)\n    const centerMs = liveStart + fraction * (liveEnd - liveStart)\n    fineCenterMs = centerMs\n    const center = new Date(centerMs)\n    const key = format(toZoned(center, settings.value.timeZone), keyFmt)\n    if (key === lastKey) return\n    lastKey = key\n    instance.internals.setViewportCenter(center)\n  }\n  const schedule = () => {\n    if (!raf) raf = requestAnimationFrame(measure)\n  }\n  viewport.addEventListener(\"scroll\", schedule)\n  schedule()\n  return () => {\n    viewport.removeEventListener(\"scroll\", schedule)\n    if (raf) cancelAnimationFrame(raf)\n  }\n}\n\n\u002F**\n * Re-seat the viewport on its anchored instant (consumes `pendingRestore`,\n * set by `setupInfiniteScroll`'s `tryExtend` and by the initial\n * auto-centering below). The upstream also sets `pendingRestore` here when\n * `zoom` changes (anchoring across a zoom step) - not ported, see file\n * header: no zoom-owning component shares this scope yet.\n *\u002F\nfunction seat() {\n  let raf: number | null = null\n  let attempts = 0\n  const run = () => {\n    raf = null\n    const viewport = timelinePaneRef.value\n    if (viewport && pendingRestore) {\n      if (viewport.clientWidth === 0 && attempts++ \u003C 20) {\n        raf = requestAnimationFrame(run)\n        return\n      }\n      const { ms, align, offsetPx } = pendingRestore\n      pendingRestore = null\n      const state = instance.state.value\n      const rangeStartMs = state.visibleRange.start.getTime()\n      const rangeEndMs = state.visibleRange.end.getTime()\n      const fraction = Math.min(Math.max((ms - rangeStartMs) \u002F (rangeEndMs - rangeStartMs), 0), 1)\n      const offset = offsetPx ?? (align === \"center\" ? viewport.clientWidth \u002F 2 : 0)\n      setScrollStart(viewport, Math.max(0, fraction * viewport.scrollWidth - offset))\n    }\n    extendLock = false\n  }\n  run()\n  return () => {\n    if (raf !== null) cancelAnimationFrame(raf)\n  }\n}\n\n\u002F**\n * Auto-manage the horizontal position for the current anchor until the user\n * scrolls: pre-buffer one period per side (infinite scroll), then centre the\n * target instant (now \u002F anchor \u002F an explicit `initialCenter` instant).\n *\u002F\nfunction autoCenter() {\n  const state = instance.state.value\n  const anchorMs = state.date.getTime()\n  const initialCenter =\n    viewConfig.value.initialCenter instanceof Date ? viewConfig.value.initialCenter.getTime() : viewConfig.value.initialCenter\n  const key = `${state.scale}:${anchorMs}:${viewConfig.value.scrollbars}`\n  if (manage.key !== key) {\n    const slideContinuation = manage.userTook && instance.internals.didAnchorSlide()\n    Object.assign(manage, slideContinuation ? { key, buffered: manage.buffered, userTook: true } : { key, buffered: false, userTook: false })\n  }\n  if (manage.userTook) return () => {}\n\n  let waiter: ResizeObserver | null = null\n  const run = () => {\n    waiter?.disconnect()\n    waiter = null\n    const viewport = timelinePaneRef.value\n    const axis = viewport?.querySelector\u003CHTMLElement>(\"[data-gantt-axis]\")\n    if (!viewport || !axis) return\n    if (viewport.clientWidth === 0) {\n      waiter = new ResizeObserver(() => {\n        if (viewport.clientWidth > 0) run()\n      })\n      waiter.observe(viewport)\n      return\n    }\n    if (viewConfig.value.infiniteScroll && !manage.buffered) {\n      manage.buffered = true\n      extendLock = true\n      instance.internals.extendRange(\"before\")\n      instance.internals.extendRange(\"after\")\n      return\n    }\n    extendLock = false\n    if (viewport.scrollWidth \u003C= viewport.clientWidth) return\n    const active = state.activeRange\n    let target: number\n    if (typeof initialCenter === \"number\") {\n      target = initialCenter\n    } else if (initialCenter === \"anchor\") {\n      target = anchorMs\n    } else {\n      const nowMs = Date.now()\n      target = nowMs >= active.start.getTime() && nowMs \u003C active.end.getTime() ? nowMs : anchorMs\n    }\n    const rangeStartMs = state.visibleRange.start.getTime()\n    const rangeEndMs = state.visibleRange.end.getTime()\n    const fraction = Math.min(Math.max((target - rangeStartMs) \u002F (rangeEndMs - rangeStartMs), 0), 1)\n    setScrollStart(viewport, Math.max(0, fraction * viewport.scrollWidth - viewport.clientWidth \u002F 2))\n  }\n  run()\n  return () => waiter?.disconnect()\n}\n\n\u002F** Keep the view centered on the same instant across a zoom step (button click, uncontrolled). Mirrors gantt-view.tsx's `anchorZoomCenter` (~1550-1566). *\u002F\nfunction anchorZoomCenter() {\n  const viewport = timelinePaneRef.value\n  const axis = viewport?.querySelector\u003CHTMLElement>(\"[data-gantt-axis]\")\n  if (!viewport || !axis) return\n  const liveStart = Number(axis.dataset.ganttRangeStart)\n  const liveEnd = Number(axis.dataset.ganttRangeEnd)\n  if (Number.isNaN(liveStart) || Number.isNaN(liveEnd)) return\n  pendingRestore = {\n    ms: liveStart + ((getScrollStart(viewport) + viewport.clientWidth \u002F 2) \u002F viewport.scrollWidth) * (liveEnd - liveStart),\n    align: \"center\",\n  }\n}\n\n\u002F**\n * Keep the instant under the POINTER pinned across a zoom step (wheel\u002Fpinch\n * gesture). Mirrors gantt-view.tsx's `anchorZoomPointer` (~1568-1591).\n *\u002F\nfunction anchorZoomPointer(clientX: number) {\n  const viewport = timelinePaneRef.value\n  const axis = viewport?.querySelector\u003CHTMLElement>(\"[data-gantt-axis]\")\n  if (!viewport || !axis) return\n  const liveStart = Number(axis.dataset.ganttRangeStart)\n  const liveEnd = Number(axis.dataset.ganttRangeEnd)\n  if (Number.isNaN(liveStart) || Number.isNaN(liveEnd)) return\n  const offsetPx = trackPoint(viewport, clientX).offset\n  pendingRestore = {\n    ms: liveStart + ((getScrollStart(viewport) + offsetPx) \u002F viewport.scrollWidth) * (liveEnd - liveStart),\n    align: \"start\",\n    offsetPx,\n  }\n}\n\n\u002F**\n * ctrl\u002Fcmd + wheel (and trackpad pinch) zooms the time range - mirrors\n * gantt-view.tsx's `wheelZoomRef` effect (~1593-1625). `passive: false`\n * because it must `preventDefault()` (otherwise the page itself zooms).\n *\u002F\nfunction setupWheelZoom() {\n  const viewport = timelinePaneRef.value\n  if (!viewport) return () => {}\n  const onWheel = (e: WheelEvent) => {\n    if (!viewConfig.value.wheelZoom) return\n    if (!e.ctrlKey && !e.metaKey) return\n    if (props.zoom === undefined || !props.onWheelZoom) return\n    const range = { ...DEFAULT_ZOOM_RANGE, ...viewConfig.value.zoomRange }\n    const clampZoom = (v: number) => Math.min(Math.max(v, range.min), range.max)\n    const lines = e.deltaMode === 1 ? 16 : e.deltaMode === 2 ? 400 : 1\n    const next = clampZoom(props.zoom * Math.exp(-e.deltaY * lines * 0.002))\n    if (Math.abs(next - props.zoom) \u003C 1e-4) return\n    e.preventDefault()\n    if (viewConfig.value.zoom === undefined) anchorZoomPointer(e.clientX)\n    props.onWheelZoom(+next.toFixed(4))\n  }\n  viewport.addEventListener(\"wheel\", onWheel, { passive: false })\n  return () => viewport.removeEventListener(\"wheel\", onWheel)\n}\n\nlet teardownScrollIntent: (() => void) | undefined\nlet teardownInfiniteScroll: (() => void) | undefined\nlet teardownCenterReport: (() => void) | undefined\nlet teardownSeat: (() => void) | undefined\nlet teardownAutoCenter: (() => void) | undefined\nlet teardownWheelZoom: (() => void) | undefined\nlet teardownZoomSeat: (() => void) | undefined\n\nfunction rewireAutoScroll() {\n  teardownInfiniteScroll?.()\n  teardownCenterReport?.()\n  teardownSeat?.()\n  teardownAutoCenter?.()\n  teardownInfiniteScroll = setupInfiniteScroll()\n  teardownCenterReport = setupViewportCenterReport()\n  teardownSeat = seat()\n  teardownAutoCenter = autoCenter()\n}\n\nonMounted(() => {\n  teardownScrollIntent = setupUserScrollIntent()\n  teardownWheelZoom = setupWheelZoom()\n  rewireAutoScroll()\n})\nonBeforeUnmount(() => {\n  teardownScrollIntent?.()\n  teardownInfiniteScroll?.()\n  teardownCenterReport?.()\n  teardownSeat?.()\n  teardownAutoCenter?.()\n  teardownWheelZoom?.()\n  teardownZoomSeat?.()\n})\n\n\u002F\u002F Re-seat on a zoom step (gantt-view.tsx's re-seat useLayoutEffect,\n\u002F\u002F ~1493-1548, restricted to the `zoom` dependency - the rest of that\n\u002F\u002F effect's deps are already covered by `rewireAutoScroll`'s watcher above).\n\u002F\u002F A button\u002Fwheel-driven change already staged `pendingRestore` itself\n\u002F\u002F (anchorZoomCenter\u002FanchorZoomPointer, called before the value changes);\n\u002F\u002F this only supplies the fallback for a zoom change with no such call - a\n\u002F\u002F consumer moving `viewConfig.zoom` on its own.\nwatch(\n  () => props.zoom,\n  (zoom) => {\n    if (zoom === undefined) return\n    if (lastZoom !== null && lastZoom !== zoom && !pendingRestore && fineCenterMs !== null) {\n      pendingRestore = { ms: fineCenterMs, align: \"center\" }\n    }\n    lastZoom = zoom\n    teardownZoomSeat?.()\n    teardownZoomSeat = seat()\n  }\n)\n\u002F\u002F Re-run whenever the axis range\u002Fscale\u002Fscrollbars mode changes - mirrors the\n\u002F\u002F original's dependency arrays across its several effects.\nwatch(\n  () => [\n    instance.state.value.scale,\n    instance.state.value.date.getTime(),\n    instance.state.value.visibleRange.start.getTime(),\n    instance.state.value.visibleRange.end.getTime(),\n    viewConfig.value.scrollbars,\n    viewConfig.value.infiniteScroll,\n  ],\n  rewireAutoScroll\n)\n\nfunction resetWidth() {\n  treeWidth.value = treeConfig.value.width\n  treeConfig.value.onWidthChange?.(treeConfig.value.width)\n}\n\nfunction beginSplit(e: PointerEvent) {\n  if (e.button !== 0) return\n  e.preventDefault()\n  const pointerId = e.pointerId\n  const startX = e.clientX\n  const startWidth = clampedTreeWidth.value\n  const splitter = e.currentTarget as HTMLElement\n  \u002F\u002F in RTL the tree pane sits on the right: pointer deltas invert\n  const dir = getComputedStyle(splitter).direction === \"rtl\" ? -1 : 1\n  splitter.setAttribute(\"data-resizing\", \"\")\n  document.body.style.cursor = \"col-resize\"\n  document.body.style.userSelect = \"none\"\n  let liveWidth = startWidth\n  const onMove = (ev: PointerEvent) => {\n    if (ev.pointerId !== pointerId) return\n    liveWidth = clampContainer(clampTree(startWidth + (ev.clientX - startX) * dir), bodyRef.value?.clientWidth ?? 0)\n    if (treePaneRef.value) treePaneRef.value.style.width = `${liveWidth}px`\n  }\n  const finish = (ev?: PointerEvent) => {\n    if (ev && ev.pointerId !== pointerId) return\n    window.removeEventListener(\"pointermove\", onMove)\n    window.removeEventListener(\"pointerup\", finish)\n    window.removeEventListener(\"pointercancel\", finish)\n    splitter.removeAttribute(\"data-resizing\")\n    document.body.style.cursor = \"\"\n    document.body.style.userSelect = \"\"\n    if (liveWidth !== startWidth) {\n      treeWidth.value = liveWidth\n      treeConfig.value.onWidthChange?.(liveWidth)\n    }\n  }\n  window.addEventListener(\"pointermove\", onMove)\n  window.addEventListener(\"pointerup\", finish)\n  window.addEventListener(\"pointercancel\", finish)\n}\n\nfunction onSplitterKeydown(e: KeyboardEvent) {\n  if (e.key !== \"ArrowLeft\" && e.key !== \"ArrowRight\") return\n  e.preventDefault()\n  const dir = getComputedStyle(e.currentTarget as HTMLElement).direction === \"rtl\" ? -1 : 1\n  const delta = (e.key === \"ArrowLeft\" ? -16 : 16) * dir\n  const next = clampTree(clampedTreeWidth.value + delta)\n  treeWidth.value = next\n  treeConfig.value.onWidthChange?.(next)\n}\n\ndefineExpose({\n  treePaneRef,\n  timelinePaneRef,\n  bodyRef,\n  clampedTreeWidth,\n  nameColumnWidth: computed(() => treeConfig.value.nameColumnWidth),\n  anchorZoomCenter,\n  anchorZoomPointer,\n})\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cdiv ref=\"bodyRef\" data-slot=\"gantt-body\" :class=\"cn('flex h-full min-h-0 min-w-0', props.class)\">\n    \u003Cdiv\n      ref=\"treePaneRef\"\n      data-slot=\"gantt-tree-pane\"\n      data-gantt-native-scroll=\"\"\n      class=\"h-full shrink-0 overflow-y-auto overflow-x-hidden\"\n      :style=\"{ width: `${clampedTreeWidth}px` }\"\n    >\n      \u003Cslot name=\"tree\" \u002F>\n    \u003C\u002Fdiv>\n    \u003Cdiv\n      v-if=\"treeConfig.resizable\"\n      role=\"separator\"\n      aria-orientation=\"vertical\"\n      :aria-label=\"settings.i18n.labels.resizePanel\"\n      :aria-valuenow=\"Math.round(clampedTreeWidth)\"\n      :aria-valuemin=\"Math.round(Math.min(treeConfig.minWidth, clampedTreeWidth))\"\n      :aria-valuemax=\"Math.round(Math.max(treeConfig.maxWidth, clampedTreeWidth))\"\n      tabindex=\"0\"\n      data-slot=\"gantt-splitter\"\n      class=\"group\u002Fgantt-splitter bg-border hover:bg-primary\u002F60 data-resizing:bg-primary relative z-30 w-px shrink-0 cursor-col-resize touch-none outline-none focus-visible:ring-ring\u002F50 focus-visible:ring-2 after:absolute after:inset-y-0 after:-start-1 after:-end-1\"\n      @pointerdown=\"beginSplit\"\n      @dblclick=\"resetWidth\"\n      @keydown=\"onSplitterKeydown\"\n    >\n      \u003Cspan\n        aria-hidden\n        data-slot=\"gantt-splitter-grip\"\n        class=\"bg-primary\u002F60 group-data-resizing\u002Fgantt-splitter:bg-primary absolute top-1\u002F2 left-1\u002F2 h-6 w-0.75 -translate-x-1\u002F2 -translate-y-1\u002F2 rounded-full opacity-0 transition-opacity duration-150 group-hover\u002Fgantt-splitter:opacity-100 group-focus-visible\u002Fgantt-splitter:opacity-100 group-data-resizing\u002Fgantt-splitter:opacity-100\"\n      \u002F>\n    \u003C\u002Fdiv>\n    \u003Cdiv v-else aria-hidden class=\"bg-border w-px shrink-0\" \u002F>\n    \u003Cdiv ref=\"timelinePaneRef\" data-slot=\"gantt-timeline-pane\" class=\"relative h-full min-w-0 flex-1 overflow-auto\">\n      \u003Cslot name=\"timeline\" \u002F>\n    \u003C\u002Fdiv>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n",{"path":4871,"type":480,"target":4872,"content":4873},"src\u002Freui\u002Fgantt\u002FGanttView.vue","components\u002Freui\u002Fgantt\u002FGanttView.vue","\u003Cscript setup lang=\"ts\" generic=\"TData = unknown\">\n\u002F**\n * Порт (частичный, прагматично упрощённый) корневой сборки GanttView\n * (gantt-view.tsx, MIT, ~3800 строк) — минимальная РЕАЛЬНАЯ сборка уже\n * портированных кусков (`GanttTreeSplitPanes`, `GanttTreeRow`,\n * `GanttTimelineHeader`, `GanttTimelineRow`, `GanttNowLine`\u002F`Dot`,\n * `GanttZoomControl`, `GanttOffscreenChips`, `GanttCustomDragLayer`), а не\n * построчная копия всей функции. Появилась не по плану \"секция за секцией\",\n * а потому что жестам (`gestures.ts`\u002F`beginGesture`) нужен реальный предок\n * `[data-slot=\"gantt-view\"]` (`origin.closest(...)`, см. `collectSurface`) —\n * без него `beginMove`\u002F`beginResize`\u002F`beginCreate` находят `viewRoot === null`\n * и молча не делают ничего. Ручная сборка кейса (раздельные статичные пропы\n * на `GanttTimelineHeader`, ручной перебор `RESOURCES`) этот корень не\n * создавала, и это первый компонент во всём порте, которому он\n * понадобился для настоящего теста, а не только для статичного скриншота.\n *\n * Строит `rows`\u002F`rowBars` из РЕАЛЬНЫХ `settings.resources`\u002F\n * `useGanttOccurrences()` (не хардкод кейса) — то же дерево-обход с учётом\n * `collapsedGroups`, что и в оригинале (~369-386), и та же паковка через\n * `packTimedSegments` (lib.ts), что уже используется в кейсе вручную.\n *\n * Переанкеровка при зуме (`anchorZoomCenter`\u002F`anchorZoomPointer`\u002Fwheel-zoom)\n * теперь портирована: этот компонент — единственный держатель\n * `useGanttZoom()`, поэтому `zoom`\u002F`setZoom` идут пропами в\n * `GanttTreeSplitPanes` (владеет `pendingRestore`\u002Fреальным осью-элементом),\n * а `anchorZoomCenter` того же экземпляра — колбэком в `GanttZoomControl`\n * через `splitPanesRef` (соседи по дереву, не предок\u002Fпотомок).\n *\n * ponytail — сознательно НЕ портировано (объём\u002Fриск дороже пользы в этом\n * срезе, но контракт под них уже есть, добавить можно без переделки):\n *  - Заголовок дерева (\"Resources\" + доп.колонки в шапке, ~1909-1955),\n *    drag-reorder ресурсов, hint \"добавить задачу\", `renderResourceMenu`,\n *    `renderNoResources` — не входили в предыдущие секции, не вошли и сюда.\n *  - `loading`-оверлей, `columnsMenu`, `asChild` на корне.\n *\u002F\nimport { computed, ref } from \"vue\"\nimport type { HTMLAttributes } from \"vue\"\nimport {\n  useGantt,\n  useGanttSettings,\n  useGanttViewConfig,\n  useGanttOccurrences,\n  resolveScheduleMode,\n} from \".\u002Fcontext\"\nimport { useGanttZoom } from \".\u002Fzoom\"\nimport { packTimedSegments } from \".\u002Flib\"\nimport type { TimelineRow, TimelineRowBars } from \".\u002Fview-lib\"\nimport { cn } from \"@\u002Flib\u002Futils\"\nimport GanttTreeSplitPanes from \".\u002FGanttTreeSplitPanes.vue\"\nimport GanttTreeRow from \".\u002FGanttTreeRow.vue\"\nimport GanttTimelineHeader from \".\u002FGanttTimelineHeader.vue\"\nimport GanttTimelineRow from \".\u002FGanttTimelineRow.vue\"\nimport GanttNowLine from \".\u002FGanttNowLine.vue\"\nimport GanttNowDot from \".\u002FGanttNowDot.vue\"\nimport GanttZoomControl from \".\u002FGanttZoomControl.vue\"\nimport GanttOffscreenChips from \".\u002FGanttOffscreenChips.vue\"\nimport GanttCustomDragLayer from \".\u002FGanttCustomDragLayer.vue\"\nimport type { GanttResource, GanttSegment } from \".\u002Ftypes\"\n\nconst LANE_HEIGHT_REM = 1.25\nconst LANE_GAP_REM = 0.1875\n\nconst props = defineProps\u003C{ class?: HTMLAttributes[\"class\"] }>()\n\nconst instance = useGantt\u003CTData>()\nconst settings = useGanttSettings\u003CTData>()\nconst viewConfig = useGanttViewConfig\u003CTData>()\nconst occurrences = useGanttOccurrences\u003CTData>()\nconst { zoom, setZoom } = useGanttZoom\u003CTData>()\n\n\u002F\u002F ----- tree: flatten with collapse (uncontrolled unless viewConfig.collapsedGroups) -----\nconst internalCollapsed = ref\u003Cstring[]>(viewConfig.value.defaultCollapsedGroups ?? [])\nconst collapsedIds = computed(() => viewConfig.value.collapsedGroups ?? internalCollapsed.value)\nconst collapsedSet = computed(() => new Set(collapsedIds.value))\n\nconst rows = computed\u003CTimelineRow[]>(() => {\n  const result: TimelineRow[] = []\n  const walk = (resources: GanttResource[], depth: number, parentId: string | null) => {\n    for (const resource of resources) {\n      const isGroup = !!resource.children?.length\n      const collapsed = collapsedSet.value.has(resource.id)\n      result.push({ resource, parentId, depth, isGroup, collapsed })\n      if (isGroup && !collapsed) walk(resource.children!, depth + 1, resource.id)\n    }\n  }\n  walk(settings.value.resources, 0, null)\n  return result\n})\n\nfunction onToggle(row: TimelineRow) {\n  const id = row.resource.id\n  const next = collapsedIds.value.includes(id) ? collapsedIds.value.filter((x) => x !== id) : [...collapsedIds.value, id]\n  if (viewConfig.value.collapsedGroups === undefined) internalCollapsed.value = next\n  viewConfig.value.onCollapsedGroupsChange?.(next)\n}\n\n\u002F\u002F ----- leaf-row checkbox selection (uncontrolled unless viewConfig.selectedRows) -----\nconst internalSelected = ref\u003Cstring[]>([])\nconst selectedRows = computed(() => viewConfig.value.selectedRows ?? internalSelected.value)\nconst selectedSet = computed(() => new Set(selectedRows.value))\nfunction onSelectedChange(id: string, checked: boolean) {\n  const next = checked ? [...selectedRows.value.filter((x) => x !== id), id] : selectedRows.value.filter((x) => x !== id)\n  if (viewConfig.value.selectedRows === undefined) internalSelected.value = next\n  viewConfig.value.onSelectedRowsChange?.(next)\n}\n\n\u002F\u002F ----- per-row bar packing (real packTimedSegments, mirrors the case's manual version) -----\nconst minRowRem = computed(() => viewConfig.value.metrics?.minRowHeight ?? 2.5)\n\nconst rowBars = computed\u003CMap\u003Cstring, TimelineRowBars>>(() => {\n  const map = new Map\u003Cstring, TimelineRowBars>()\n  const rangeStartMs = instance.state.value.visibleRange.start.getTime()\n  for (const row of rows.value) {\n    const mode = resolveScheduleMode(row.resource, viewConfig.value.scheduleMode)\n    const segments: GanttSegment\u003CTData>[] = occurrences.value\n      .filter((occ) => occ.event.resourceId === row.resource.id)\n      .map((occ) => ({\n        occurrence: occ,\n        day: occ.start,\n        isStart: true,\n        isEnd: true,\n        continuesBefore: false,\n        continuesAfter: false,\n        startMin: (occ.start.getTime() - rangeStartMs) \u002F 60000,\n        endMin: (occ.end.getTime() - rangeStartMs) \u002F 60000,\n      }))\n    packTimedSegments(segments, { mode })\n    const laneCount = segments.length ? Math.max(...segments.map((s) => s.column ?? 0)) + 1 : 1\n    const laneOffsetRem = (minRowRem.value - LANE_HEIGHT_REM) \u002F 2\n    const heightRem = Math.max(minRowRem.value, laneCount * LANE_HEIGHT_REM + (laneCount - 1) * LANE_GAP_REM + laneOffsetRem * 2)\n    map.set(row.resource.id, {\n      segments,\n      laneCount,\n      draftLane: null,\n      scheduleMode: mode,\n      heightRem,\n      laneOffsetRem,\n      bandRem: minRowRem.value,\n      extent: null,\n      summary: null,\n    })\n  }\n  return map\n})\n\nconst headerRef = ref\u003CInstanceType\u003Ctypeof GanttTimelineHeader> | null>(null)\nconst splitPanesRef = ref\u003CInstanceType\u003Ctypeof GanttTreeSplitPanes> | null>(null)\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cdiv data-slot=\"gantt-view\" :class=\"cn('relative flex min-h-0 min-w-0 flex-1 flex-col', props.class)\">\n    \u003CGanttCustomDragLayer \u002F>\n    \u003CGanttTreeSplitPanes ref=\"splitPanesRef\" :tree-panel=\"viewConfig.treePanel\" :zoom=\"zoom\" :on-wheel-zoom=\"setZoom\" class=\"min-h-0 flex-1\">\n      \u003Ctemplate #tree>\n        \u003Cdiv class=\"h-16\" \u002F>\n        \u003CGanttTreeRow\n          v-for=\"row in rows\"\n          :key=\"row.resource.id\"\n          :row=\"row\"\n          :height-rem=\"rowBars.get(row.resource.id)?.heightRem ?? minRowRem\"\n          :band-rem=\"rowBars.get(row.resource.id)?.bandRem ?? minRowRem\"\n          :columns=\"viewConfig.columns ?? []\"\n          :name-width=\"viewConfig.treePanel?.nameColumnWidth ?? 208\"\n          :dimmed=\"false\"\n          :selected=\"selectedSet.has(row.resource.id)\"\n          :show-checkbox=\"!row.isGroup\"\n          @selected-change=\"onSelectedChange\"\n          @toggle=\"onToggle\"\n        \u002F>\n      \u003C\u002Ftemplate>\n      \u003Ctemplate #timeline>\n        \u003CGanttTimelineHeader ref=\"headerRef\" :scale=\"instance.state.value.scale\" :range=\"instance.state.value.visibleRange\" :zoom=\"zoom\" \u002F>\n        \u003CGanttNowLine :range-start-ms=\"instance.state.value.visibleRange.start.getTime()\" :range-end-ms=\"instance.state.value.visibleRange.end.getTime()\" \u002F>\n        \u003CGanttNowDot :range-start-ms=\"instance.state.value.visibleRange.start.getTime()\" :range-end-ms=\"instance.state.value.visibleRange.end.getTime()\" \u002F>\n        \u003CGanttTimelineRow\n          v-for=\"(row, rowIndex) in rows\"\n          :key=\"row.resource.id\"\n          :row=\"row\"\n          :row-index=\"rowIndex\"\n          :bars=\"rowBars.get(row.resource.id)\"\n          :range-start-ms=\"instance.state.value.visibleRange.start.getTime()\"\n          :range-end-ms=\"instance.state.value.visibleRange.end.getTime()\"\n          :track-width=\"headerRef?.trackWidth ?? '0rem'\"\n          :track-rem-width=\"headerRef?.trackRemWidth ?? 0\"\n          row-border=\"solid\"\n          :selected=\"selectedSet.has(row.resource.id)\"\n          :resolve-hint-stop=\"headerRef?.resolveHintStop ?? (() => null)\"\n          :is-panning=\"false\"\n          :lane-height-rem=\"LANE_HEIGHT_REM\"\n          :lane-gap-rem=\"LANE_GAP_REM\"\n          :min-row-rem=\"minRowRem\"\n        \u002F>\n        \u003C!--\n          Размещение здесь (сиблинг рядов, не вынесенный в отдельный\n          нескроллящийся \"хром\"-слой) — то же упрощение, что уже\n          задокументировано в кейсе для GanttZoomControl\u002FGanttOffscreenChips:\n          разделение \"хром пейна \u002F скроллируемый контент\" внутри\n          GanttTreeSplitPanes ещё не сделано.\n        -->\n        \u003CGanttZoomControl v-if=\"viewConfig.zoomControl\" :anchor=\"() => splitPanesRef?.anchorZoomCenter()\" \u002F>\n        \u003CGanttOffscreenChips\n          v-if=\"viewConfig.offscreenIndicators\"\n          :pane-ref=\"splitPanesRef?.timelinePaneRef ?? null\"\n          :refresh-key=\"`${instance.state.value.scale}:${instance.state.value.visibleRange.start.getTime()}:${zoom}:${rows.length}`\"\n        \u002F>\n      \u003C\u002Ftemplate>\n    \u003C\u002FGanttTreeSplitPanes>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n",{"path":4875,"type":480,"target":4876,"content":4877},"src\u002Freui\u002Fgantt\u002FGanttZoomControl.vue","components\u002Freui\u002Fgantt\u002FGanttZoomControl.vue","\u003Cscript setup lang=\"ts\">\n\u002F**\n * Порт плавающего контрола зума из GanttView (gantt-view.tsx, MIT, разметка\n * ~2393-2467). Состояние — в `.\u002Fzoom.ts` (`useGanttZoom`).\n *\n * `anchor` — опциональный колбэк от `GanttView` (`splitPanesRef.\n * anchorZoomCenter`, см. `GanttTreeSplitPanes.vue`), вызываемый ПЕРЕД\n * изменением зума, только когда зум неуправляем (`viewConfig.zoom ===\n * undefined`) — дословно условие `if (viewConfig.zoom === undefined)\n * anchorZoomCenter()` у обеих кнопок в оригинале (~2409-2449). Без него\n * (стандалон-использование вне `GanttView`) кнопки по-прежнему меняют\n * множитель, просто без переанкеровки видимого диапазона.\n *\n * ponytail: `Tooltip`\u002F`TooltipProvider` (подпись при наведении) не\n * перенесены — тот же выбор и та же причина, что уже задокументирована и\n * проверена (16\u002F16) в `GanttBar.vue`\u002F`GanttOffscreenChips.vue`: не влияет\n * на статичный скриншот. `IconPlaceholder` (plus\u002Fminus) заменён инлайновым\n * `\u003Csvg>`.\n *\u002F\nimport { useGanttSettings, useGanttViewConfig } from \".\u002Fcontext\"\nimport { useGanttZoom } from \".\u002Fzoom\"\nimport { Button } from \"@\u002Fcomponents\u002Fui\u002Fbutton\"\n\nconst props = withDefaults(defineProps\u003C{ anchor?: () => void }>(), { anchor: undefined })\n\nconst settings = useGanttSettings()\nconst viewConfig = useGanttViewConfig()\nconst { canZoomIn, canZoomOut, zoomIn, zoomOut } = useGanttZoom()\n\nfunction onZoomIn() {\n  if (!canZoomIn.value) return\n  if (viewConfig.value.zoom === undefined) props.anchor?.()\n  zoomIn()\n}\nfunction onZoomOut() {\n  if (!canZoomOut.value) return\n  if (viewConfig.value.zoom === undefined) props.anchor?.()\n  zoomOut()\n}\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cdiv data-slot=\"gantt-zoom\" class=\"bg-background absolute end-3 bottom-5 z-40 flex flex-col rounded-md border shadow-sm\">\n    \u003CButton\n      variant=\"ghost\"\n      size=\"icon-xs\"\n      :aria-label=\"settings.i18n.labels.zoomIn\"\n      :aria-disabled=\"!canZoomIn || undefined\"\n      class=\"text-muted-foreground hover:text-foreground size-5! rounded-b-none aria-disabled:cursor-not-allowed aria-disabled:opacity-50 aria-disabled:hover:bg-transparent\"\n      @click=\"onZoomIn\"\n    >\n      \u003Csvg xmlns=\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-3\" aria-hidden=\"true\">\u003Cpath d=\"M5 12h14\" \u002F>\u003Cpath d=\"M12 5v14\" \u002F>\u003C\u002Fsvg>\n    \u003C\u002FButton>\n    \u003CButton\n      variant=\"ghost\"\n      size=\"icon-xs\"\n      :aria-label=\"settings.i18n.labels.zoomOut\"\n      :aria-disabled=\"!canZoomOut || undefined\"\n      class=\"text-muted-foreground hover:text-foreground size-5! rounded-t-none border-t aria-disabled:cursor-not-allowed aria-disabled:opacity-50 aria-disabled:hover:bg-transparent\"\n      @click=\"onZoomOut\"\n    >\n      \u003Csvg xmlns=\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-3\" aria-hidden=\"true\">\u003Cpath d=\"M5 12h14\" \u002F>\u003C\u002Fsvg>\n    \u003C\u002FButton>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n",{"path":4879,"type":480,"target":4880,"content":4881},"src\u002Freui\u002Fgantt\u002Fcontext.ts","components\u002Freui\u002Fgantt\u002Fcontext.ts","\u002F**\n * Порт ReUI Gantt headless-стора (registry-reui\u002Fbases\u002Fradix\u002Freui\u002Fgantt\u002Fgantt.tsx, MIT).\n *\n * Оригинал держит состояние во внешнем сторе (`useSyncExternalStore` +\n * ручные `notify`\u002F`invalidate`\u002Fsnapshot-кэш), потому что React больше ничем\n * не даёт дешёвую тонкую подписку без лишних ре-рендеров. Vue-реактивность\n * (`ref`\u002F`computed`) решает ровно эту задачу нативно: `computed` уже кэширует\n * результат до изменения зависимостей и не пересчитывается, пока никто его\n * не читает — весь механизм `listeners`\u002F`subscribe`\u002F`getSnapshot`\u002F\n * `useGanttSelector`-мемоизации из оригинала здесь не нужен и не портируется\n * (см. тот же выбор в `reui\u002Fdata-grid\u002Fcontext.ts`: `table` из `useVueTable`\n * реактивен сам по себе).\n *\n * `useGanttSelector` тем не менее сохранён как тонкая обёртка над `computed`\n * ради совместимости вызывающего кода будущих файлов (gantt-view.tsx и др.),\n * которые в оригинале импортируют его по имени.\n *\u002F\nimport type { ComputedRef, InjectionKey, Ref, VNodeChild } from \"vue\"\nimport { computed, inject, reactive, ref, watch } from \"vue\"\nimport type { Locale } from \"date-fns\"\nimport {\n  buildEventIndex,\n  defaultEventOrder,\n  eventsOverlap,\n  findResource,\n  getGanttDateRange,\n  getRangeKey,\n  stepGanttDate,\n  toZoned,\n  type GanttIndex,\n  type WeekStartsOn,\n} from \".\u002Flib\"\nimport { mergeGanttI18n, type GanttI18nConfig, type GanttI18nOverrides } from \".\u002Fi18n\"\nimport type {\n  GanttBarId,\n  GanttDateRange,\n  GanttDragState,\n  GanttEvent,\n  GanttInteractions,\n  GanttOccurrence,\n  GanttOffDaysConfig,\n  GanttOverlapPolicy,\n  GanttProposedUpdate,\n  GanttRangeInfo,\n  GanttResource,\n  GanttResourceReorder,\n  GanttRowAlign,\n  GanttScale,\n  GanttScheduleMode,\n  GanttSegment,\n  GanttSelection,\n  GanttSlotDraft,\n  GanttSlotInfo,\n  GanttState,\n  GanttUpdateResult,\n} from \".\u002Ftypes\"\n\nconst DEFAULT_INTERACTIONS: GanttInteractions = {\n  drag: true,\n  resize: true,\n  selectSlot: true,\n}\n\n\u002F** Infinite-scroll growth cap, in whole periods per side. *\u002F\nconst MAX_RANGE_WINDOW = 12\n\n\u002F** A node holds as many concurrent schedules as it needs unless told otherwise. *\u002F\nconst DEFAULT_SCHEDULE_MODE: GanttScheduleMode = \"multiple\"\n\n\u002F** Tree label sits on the first schedule's baseline, not the grown row's middle. *\u002F\nconst DEFAULT_ROW_ALIGN: GanttRowAlign = \"start\"\n\n\u002F**\n * A node's cardinality: its own override wins over the view-level default.\n * Shared by the layout pass and the gesture engine so both read one rule.\n *\u002F\nfunction resolveScheduleMode(\n  node: GanttResource | null | undefined,\n  scheduleMode: GanttScheduleMode | undefined\n): GanttScheduleMode {\n  return node?.scheduleMode ?? scheduleMode ?? DEFAULT_SCHEDULE_MODE\n}\n\nconst EMPTY_SELECTION: GanttSelection = { eventKeys: [], slot: null }\n\nexport interface GanttCallbacks\u003CTData = unknown> {\n  onEventClick?: (occurrence: GanttOccurrence\u003CTData>, e: MouseEvent) => void\n  onEventDoubleClick?: (occurrence: GanttOccurrence\u003CTData>, e: MouseEvent) => void\n  onEventUpdate?: (update: GanttProposedUpdate\u003CTData>) => GanttUpdateResult\n  canDropEvent?: (update: GanttProposedUpdate\u003CTData>) => boolean\n  onSlotClick?: (slot: GanttSlotInfo, e: MouseEvent) => void\n  onSelectSlot?: (slot: GanttSlotDraft) => void\n  canSelectSlot?: (slot: GanttSlotDraft) => boolean\n  \u002F** Fires when the \"add task\" hint is activated; create a new tree row. *\u002F\n  onCreateTask?: (ctx: { parentId: string | null; index: number }) => void\n  \u002F**\n   * Gates the \"add task\" hint. The shipped view offers root-level creation\n   * only (parentId = null); parentId stays in the contract for group-level\n   * affordances a consumer builds via its own UI + onCreateTask.\n   *\u002F\n  canCreateTask?: (ctx: { parentId: string | null }) => boolean\n  \u002F** Click on a tree row's surface (chevron\u002Fcheckbox\u002Fgrip clicks excluded). *\u002F\n  onResourceClick?: (ctx: GanttColumnContext, e: MouseEvent) => void\n  onResourceDoubleClick?: (ctx: GanttColumnContext, e: MouseEvent) => void\n  onRangeChange?: (info: GanttRangeInfo) => void\n  onScaleChange?: (scale: GanttScale) => void\n  onDateChange?: (date: Date) => void\n  onSelectionChange?: (selection: GanttSelection) => void\n  onInteractionsChange?: (interactions: GanttInteractions) => void\n  onEventsChange?: (events: GanttEvent\u003CTData>[]) => void\n  \u002F**\n   * Commit gate for timeline resource-row drag reorder. Return false to\n   * reject; apply the move by adopting proposal.resources into your\n   * `resources` state (controlled - the calendar never self-mutates).\n   *\u002F\n  onResourceReorder?: (proposal: GanttResourceReorder) => void | false\n  \u002F** Live validity predicate while a resource row is being dragged. *\u002F\n  canReorderResource?: (proposal: GanttResourceReorder) => boolean\n  \u002F**\n   * Fires when a reorder gesture is released on a position rejected by\n   * `canReorderResource` (e.g. a pinned row). Use it to explain the rejection\n   * (a toast) - the destructive drop indicator already shows it live.\n   *\u002F\n  onResourceReorderReject?: (proposal: GanttResourceReorder) => void\n}\n\n\u002F** Pointer-activation thresholds; unset keys keep the dnd-kit parity defaults. *\u002F\nexport interface GanttActivationConfig {\n  \u002F** Mouse travel (px) before a bar move starts. Default 5. *\u002F\n  moveDistancePx?: number\n  \u002F** Mouse travel (px) before a drag-create starts. Default 4. *\u002F\n  createDistancePx?: number\n  \u002F** Touch long-press delay in ms. Default 250. *\u002F\n  touchDelayMs?: number\n  \u002F** Touch movement tolerance (px) during the long-press. Default 5. *\u002F\n  touchTolerancePx?: number\n}\n\nexport interface UseGanttStateOptions\u003CTData = unknown> extends GanttCallbacks\u003CTData> {\n  events?: GanttEvent\u003CTData>[]\n  defaultEvents?: GanttEvent\u003CTData>[]\n  scale?: GanttScale\n  defaultScale?: GanttScale\n  date?: Date\n  defaultDate?: Date\n  selection?: GanttSelection\n  defaultSelection?: GanttSelection\n  interactions?: Partial\u003CGanttInteractions>\n  defaultInteractions?: Partial\u003CGanttInteractions>\n  loading?: boolean\n  timeZone?: string\n  locale?: Locale\n  weekStartsOn?: WeekStartsOn\n  slotDuration?: number\n  snapDuration?: number\n  i18n?: GanttI18nOverrides\n  \u002F** Hard travel bounds for infinite scrolling; either side may be omitted. *\u002F\n  rangeBounds?: { min?: Date; max?: Date }\n  \u002F** Pointer-activation threshold overrides for drag\u002Fresize\u002Fcreate. *\u002F\n  activation?: GanttActivationConfig\n  \u002F** Infinite-scroll growth cap in whole periods per side. Default 12. *\u002F\n  maxRangeWindow?: number\n  \u002F** Tree nodes of the gantt (GanttNode is the preferred type name). *\u002F\n  resources?: GanttResource[]\n  \u002F**\n   * What a gesture may do when it would overlap another schedule in the SAME\n   * node: \"allow\" (default), \"clamp\" to the neighbour's edge, or \"reject\".\n   *\u002F\n  overlap?: GanttOverlapPolicy\n  getEventPriority?: (event: GanttEvent\u003CTData>) => number\n  eventOrder?: (a: GanttOccurrence\u003CTData>, b: GanttOccurrence\u003CTData>) => number\n  getOccurrences?: (\n    event: GanttEvent\u003CTData>,\n    range: GanttDateRange,\n    ctx: { timeZone: string }\n  ) => Array\u003C{ start: Date; end: Date }> | null\n}\n\n\u002F** Resolved configuration; every UseGanttStateOptions field with defaults applied and i18n merged. *\u002F\nexport interface GanttSettings\u003CTData = unknown> extends GanttCallbacks\u003CTData> {\n  timeZone: string\n  locale?: Locale\n  weekStartsOn: WeekStartsOn\n  slotDuration: number\n  snapDuration: number\n  i18n: GanttI18nConfig\n  rangeBounds?: { min?: Date; max?: Date }\n  activation?: GanttActivationConfig\n  maxRangeWindow?: number\n  resources: GanttResource[]\n  overlap: GanttOverlapPolicy\n  getEventPriority: (event: GanttEvent\u003CTData>) => number\n  eventOrder: (a: GanttOccurrence\u003CTData>, b: GanttOccurrence\u003CTData>) => number\n  getOccurrences?: (\n    event: GanttEvent\u003CTData>,\n    range: GanttDateRange,\n    ctx: { timeZone: string }\n  ) => Array\u003C{ start: Date; end: Date }> | null\n}\n\nexport interface GanttApi\u003CTData = unknown> {\n  next(): void\n  prev(): void\n  today(): void\n  goTo(date: Date): void\n  setScale(scale: GanttScale): void\n  getEvents(): GanttEvent\u003CTData>[]\n  getEvent(id: GanttBarId): GanttEvent\u003CTData> | undefined\n  setEvents(events: GanttEvent\u003CTData>[]): void\n  addEvent(event: GanttEvent\u003CTData>): void\n  updateEvent(id: GanttBarId, patch: Partial\u003CGanttEvent\u003CTData>>): void\n  removeEvent(id: GanttBarId): void\n  getOccurrences(range?: GanttDateRange): GanttOccurrence\u003CTData>[]\n  findOverlapping(candidate: {\n    start: Date\n    end: Date\n    excludeEventId?: string\n  }): GanttOccurrence\u003CTData>[]\n  select(selection: Partial\u003CGanttSelection>): void\n  selectEvent(key: string, opts?: { additive?: boolean }): void\n  clearSelection(): void\n  setInteractions(patch: Partial\u003CGanttInteractions>): void\n  getVisibleRange(): GanttDateRange\n  getActiveRange(): GanttDateRange\n  \u002F** TZDate in the gantt's display time zone. *\u002F\n  toZoned(date: Date): Date\n}\n\n\u002F** Cross-file plumbing for sibling view\u002Finteraction modules; not public API. *\u002F\nexport interface GanttInternals\u003CTData = unknown> {\n  getIndex(): GanttIndex\u003CTData>\n  setDrag(drag: GanttDragState\u003CTData> | null): void\n  setSlotDraft(draft: GanttSlotDraft | null): void\n  applyProposedUpdate(\n    update: GanttProposedUpdate\u003CTData>,\n    extra?: Partial\u003CGanttEvent\u003CTData>>\n  ): boolean\n  \u002F** Grow visibleRange by whole periods; resets on date\u002Fscale changes. *\u002F\n  extendRange(direction: \"before\" | \"after\"): boolean\n  \u002F** True when the LAST anchor-date change was an extendRange window slide. *\u002F\n  didAnchorSlide(): boolean\n  \u002F** View reports the visible-center instant (or null) for the nav title. *\u002F\n  setViewportCenter(date: Date | null): void\n}\n\nexport interface GanttInstance\u003CTData = unknown> {\n  state: ComputedRef\u003CGanttState\u003CTData>>\n  api: GanttApi\u003CTData>\n  settings: ComputedRef\u003CGanttSettings\u003CTData>>\n  internals: GanttInternals\u003CTData>\n}\n\nfunction resolveSettings\u003CTData>(\n  options: UseGanttStateOptions\u003CTData>\n): GanttSettings\u003CTData> {\n  const getEventPriority =\n    options.getEventPriority ?? ((event: GanttEvent\u003CTData>) => event.priority ?? 0)\n  return {\n    onEventClick: options.onEventClick,\n    onEventDoubleClick: options.onEventDoubleClick,\n    onEventUpdate: options.onEventUpdate,\n    canDropEvent: options.canDropEvent,\n    onSlotClick: options.onSlotClick,\n    onSelectSlot: options.onSelectSlot,\n    canSelectSlot: options.canSelectSlot,\n    onCreateTask: options.onCreateTask,\n    canCreateTask: options.canCreateTask,\n    onResourceClick: options.onResourceClick,\n    onResourceDoubleClick: options.onResourceDoubleClick,\n    onRangeChange: options.onRangeChange,\n    onScaleChange: options.onScaleChange,\n    onDateChange: options.onDateChange,\n    onSelectionChange: options.onSelectionChange,\n    onInteractionsChange: options.onInteractionsChange,\n    onEventsChange: options.onEventsChange,\n    onResourceReorder: options.onResourceReorder,\n    canReorderResource: options.canReorderResource,\n    onResourceReorderReject: options.onResourceReorderReject,\n    timeZone: options.timeZone ?? Intl.DateTimeFormat().resolvedOptions().timeZone,\n    locale: options.locale,\n    \u002F\u002F locale-first default: a de\u002Ffr locale gets Monday weeks without also\n    \u002F\u002F having to set weekStartsOn; an explicit weekStartsOn always wins\n    weekStartsOn:\n      options.weekStartsOn ?? (options.locale?.options?.weekStartsOn as WeekStartsOn | undefined) ?? 0,\n    slotDuration: options.slotDuration ?? 30,\n    snapDuration: options.snapDuration ?? 15,\n    i18n: mergeGanttI18n(options.i18n),\n    rangeBounds: options.rangeBounds,\n    activation: options.activation,\n    maxRangeWindow: options.maxRangeWindow,\n    resources: options.resources ?? [],\n    overlap: options.overlap ?? \"allow\",\n    getEventPriority,\n    \u002F\u002F priority-aware default: higher getEventPriority packs\u002Forders first\n    eventOrder:\n      options.eventOrder ??\n      ((a, b) => getEventPriority(b.event) - getEventPriority(a.event) || defaultEventOrder(a, b)),\n    getOccurrences: options.getOccurrences,\n  }\n}\n\n\u002F**\n * Headless root store. `options` MUST be the caller's own reactive props\n * object (a component's `props`, or a `reactive()`) - every field is read\n * inside `computed()`, so Vue's dependency tracking does the rest; a plain\n * snapshot object would freeze the gantt at its initial props forever.\n *\u002F\nfunction useGanttState\u003CTData = unknown>(\n  options: UseGanttStateOptions\u003CTData> = {}\n): GanttInstance\u003CTData> {\n  const settings = computed(() => resolveSettings(options))\n\n  \u002F\u002F Uncontrolled fallback state - only used while the matching option is undefined.\n  const _scale = ref\u003CGanttScale>(options.defaultScale ?? \"day\") as Ref\u003CGanttScale>\n  const _date = ref\u003CDate>(options.defaultDate ?? new Date()) as Ref\u003CDate>\n  const _events = ref\u003CGanttEvent\u003CTData>[]>(options.defaultEvents ?? []) as Ref\u003C\n    GanttEvent\u003CTData>[]\n  >\n  const _selection = ref\u003CGanttSelection>(options.defaultSelection ?? EMPTY_SELECTION) as Ref\u003CGanttSelection>\n  const _interactions = ref\u003CGanttInteractions>({\n    ...DEFAULT_INTERACTIONS,\n    ...options.defaultInteractions,\n  }) as Ref\u003CGanttInteractions>\n  const _drag = ref\u003CGanttDragState\u003CTData> | null>(null) as Ref\u003CGanttDragState\u003CTData> | null>\n  const _slotDraft = ref\u003CGanttSlotDraft | null>(null) as Ref\u003CGanttSlotDraft | null>\n  const _rangeWindow = reactive({ before: 0, after: 0 })\n  const _viewportCenter = ref\u003CDate | null>(null) as Ref\u003CDate | null>\n  \u002F** Whether the last anchor change came from an extendRange window slide. *\u002F\n  let lastAnchorChangeWasSlide = false\n\n  \u002F\u002F External (controlled) anchor changes re-anchor the axis: drop any\n  \u002F\u002F infinite-scroll growth and let the title follow the anchor again, same\n  \u002F\u002F as the uncontrolled setters below - but these fire for a controlled\n  \u002F\u002F `date`\u002F`scale` prop the PARENT changed, which the setters never see.\n  watch(\n    () => [options.date?.getTime(), options.scale] as const,\n    () => {\n      if (lastAnchorChangeWasSlide) {\n        \u002F\u002F this change is our own extendRange proposal echoed back; consume once\n        lastAnchorChangeWasSlide = false\n        return\n      }\n      _rangeWindow.before = 0\n      _rangeWindow.after = 0\n      _viewportCenter.value = null\n    }\n  )\n\n  const rangeOpts = () => ({\n    timeZone: settings.value.timeZone,\n    weekStartsOn: settings.value.weekStartsOn,\n  })\n\n  const state = computed\u003CGanttState\u003CTData>>(() => {\n    const scale = options.scale ?? _scale.value\n    const date = options.date ?? _date.value\n    const opts = rangeOpts()\n    const { visibleRange: baseRange, activeRange } = getGanttDateRange(scale, date, opts)\n    const { before, after } = _rangeWindow\n    let visibleRange = baseRange\n    if (before > 0 || after > 0) {\n      let earlier = date\n      for (let i = 0; i \u003C before; i++) earlier = stepGanttDate(scale, earlier, -1, opts)\n      let later = date\n      for (let i = 0; i \u003C after; i++) later = stepGanttDate(scale, later, 1, opts)\n      visibleRange = {\n        start: getGanttDateRange(scale, earlier, opts).visibleRange.start,\n        end: getGanttDateRange(scale, later, opts).visibleRange.end,\n      }\n    }\n    return {\n      scale,\n      date,\n      visibleRange,\n      activeRange,\n      events: options.events ?? _events.value,\n      selection: options.selection ?? _selection.value,\n      interactions: options.interactions\n        ? { ...DEFAULT_INTERACTIONS, ...options.interactions }\n        : _interactions.value,\n      loading: options.loading ?? false,\n      drag: _drag.value,\n      slotDraft: _slotDraft.value,\n      viewportCenter: _viewportCenter.value,\n    }\n  })\n\n  let lastEmittedRangeKey: string | null = null\n  watch(\n    () => [state.value.scale, getRangeKey(state.value.visibleRange)] as const,\n    () => {\n      if (!settings.value.onRangeChange) return\n      const s = state.value\n      const key = `${s.scale}:${getRangeKey(s.visibleRange)}:${settings.value.timeZone}`\n      if (key === lastEmittedRangeKey) return\n      lastEmittedRangeKey = key\n      settings.value.onRangeChange({\n        range: s.visibleRange,\n        activeRange: s.activeRange,\n        scale: s.scale,\n        date: s.date,\n        timeZone: settings.value.timeZone,\n      })\n    },\n    { immediate: true }\n  )\n\n  const commitDate = (next: Date) => {\n    const current = state.value.date\n    if (current.getTime() === next.getTime()) return\n    _rangeWindow.before = 0\n    _rangeWindow.after = 0\n    _viewportCenter.value = null\n    lastAnchorChangeWasSlide = false\n    if (options.date === undefined) _date.value = next\n    settings.value.onDateChange?.(next)\n  }\n  const commitScale = (next: GanttScale) => {\n    if (state.value.scale === next) return\n    _rangeWindow.before = 0\n    _rangeWindow.after = 0\n    _viewportCenter.value = null\n    lastAnchorChangeWasSlide = false\n    if (options.scale === undefined) _scale.value = next\n    settings.value.onScaleChange?.(next)\n  }\n  const commitEvents = (next: GanttEvent\u003CTData>[]) => {\n    if (options.events === undefined) _events.value = next\n    settings.value.onEventsChange?.(next)\n  }\n  const commitSelection = (next: GanttSelection) => {\n    if (options.selection === undefined) _selection.value = next\n    settings.value.onSelectionChange?.(next)\n  }\n  const setInteractionsField = (next: GanttInteractions) => {\n    if (options.interactions === undefined) _interactions.value = next\n    settings.value.onInteractionsChange?.(next)\n  }\n\n  const applyProposedUpdate = (\n    update: GanttProposedUpdate\u003CTData>,\n    extra?: Partial\u003CGanttEvent\u003CTData>>\n  ): boolean => {\n    const result = settings.value.onEventUpdate?.(update)\n    if (result === false) return false\n    const adjusted: Partial\u003CGanttEvent\u003CTData>> =\n      result && typeof result === \"object\"\n        ? {\n            start: result.start ?? update.start,\n            end: result.end ?? update.end,\n            allDay: result.allDay ?? update.allDay,\n          }\n        : { start: update.start, end: update.end, allDay: update.allDay }\n    if (update.resourceId !== undefined) adjusted.resourceId = update.resourceId\n    const merged = extra ? { ...extra, ...adjusted } : adjusted\n    const next = state.value.events.map((event) =>\n      event.id === update.event.id ? { ...event, ...merged } : event\n    )\n    commitEvents(next)\n    return true\n  }\n\n  const getIndex = computed\u003CGanttIndex\u003CTData>>(() =>\n    buildEventIndex(state.value.events, state.value.visibleRange, {\n      timeZone: settings.value.timeZone,\n      eventOrder: settings.value.eventOrder,\n      getOccurrences: settings.value.getOccurrences,\n    })\n  )\n\n  \u002F** Anchor clamp: navigation may never leave the configured bounds. *\u002F\n  const clampToBounds = (date: Date): Date => {\n    const bounds = settings.value.rangeBounds\n    if (!bounds) return date\n    if (bounds.min && date.getTime() \u003C bounds.min.getTime()) return bounds.min\n    if (bounds.max && date.getTime() > bounds.max.getTime()) return bounds.max\n    return date\n  }\n\n  const api: GanttApi\u003CTData> = {\n    next() {\n      commitDate(clampToBounds(stepGanttDate(state.value.scale, state.value.date, 1, rangeOpts())))\n    },\n    prev() {\n      commitDate(clampToBounds(stepGanttDate(state.value.scale, state.value.date, -1, rangeOpts())))\n    },\n    today() {\n      commitDate(clampToBounds(new Date()))\n    },\n    goTo(date) {\n      commitDate(clampToBounds(date))\n    },\n    setScale(scale) {\n      commitScale(scale)\n    },\n    getEvents() {\n      return state.value.events\n    },\n    getEvent(id) {\n      return state.value.events.find((event) => event.id === id)\n    },\n    setEvents(events) {\n      commitEvents(events)\n    },\n    addEvent(event) {\n      commitEvents([...state.value.events, event])\n    },\n    updateEvent(id, patch) {\n      const event = api.getEvent(id)\n      if (!event) return\n      const merged = { ...event, ...patch }\n      const timingChanged =\n        patch.start !== undefined || patch.end !== undefined || patch.allDay !== undefined\n      if (timingChanged && settings.value.onEventUpdate) {\n        const rest: Partial\u003CGanttEvent\u003CTData>> = { ...patch }\n        delete rest.start\n        delete rest.end\n        delete rest.allDay\n        applyProposedUpdate(\n          {\n            event: merged,\n            occurrence: null,\n            start: merged.start,\n            end: merged.end,\n            allDay: merged.allDay ?? false,\n            source: \"api\",\n          },\n          Object.keys(rest).length > 0 ? rest : undefined\n        )\n        return\n      }\n      commitEvents(state.value.events.map((e) => (e.id === id ? merged : e)))\n    },\n    removeEvent(id) {\n      commitEvents(state.value.events.filter((event) => event.id !== id))\n    },\n    getOccurrences(range) {\n      if (!range) return getIndex.value.occurrences\n      const s = state.value\n      const within = range.start >= s.visibleRange.start && range.end \u003C= s.visibleRange.end\n      if (within) {\n        return getIndex.value.occurrences.filter((occ) => eventsOverlap(occ, range))\n      }\n      return buildEventIndex(s.events, range, {\n        timeZone: settings.value.timeZone,\n        eventOrder: settings.value.eventOrder,\n        getOccurrences: settings.value.getOccurrences,\n      }).occurrences\n    },\n    findOverlapping({ start, end, excludeEventId }) {\n      return api.getOccurrences({ start, end }).filter((occ) => occ.eventId !== excludeEventId)\n    },\n    select(partial) {\n      const current = state.value.selection\n      commitSelection({\n        eventKeys: partial.eventKeys ?? current.eventKeys,\n        slot: partial.slot !== undefined ? partial.slot : current.slot,\n      })\n    },\n    selectEvent(key, opts) {\n      const current = state.value.selection\n      const eventKeys = opts?.additive\n        ? current.eventKeys.includes(key)\n          ? current.eventKeys.filter((k) => k !== key)\n          : [...current.eventKeys, key]\n        : [key]\n      commitSelection({ ...current, eventKeys })\n    },\n    clearSelection() {\n      commitSelection(EMPTY_SELECTION)\n    },\n    setInteractions(patch) {\n      setInteractionsField({ ...state.value.interactions, ...patch })\n    },\n    getVisibleRange() {\n      return state.value.visibleRange\n    },\n    getActiveRange() {\n      return state.value.activeRange\n    },\n    toZoned(date) {\n      return toZoned(date, settings.value.timeZone)\n    },\n  }\n\n  const internals: GanttInternals\u003CTData> = {\n    getIndex() {\n      return getIndex.value\n    },\n    setDrag(drag) {\n      _drag.value = drag\n    },\n    setSlotDraft(draft) {\n      _slotDraft.value = draft\n    },\n    setViewportCenter(date) {\n      if (_viewportCenter.value?.getTime() === date?.getTime()) return\n      _viewportCenter.value = date\n    },\n    applyProposedUpdate,\n    extendRange(direction) {\n      const s = state.value\n      const bounds = settings.value.rangeBounds\n      if (\n        direction === \"before\" &&\n        bounds?.min &&\n        s.visibleRange.start.getTime() \u003C= bounds.min.getTime()\n      ) {\n        return false\n      }\n      if (\n        direction === \"after\" &&\n        bounds?.max &&\n        s.visibleRange.end.getTime() >= bounds.max.getTime()\n      ) {\n        return false\n      }\n      const cap = Math.max(1, settings.value.maxRangeWindow ?? MAX_RANGE_WINDOW)\n      const { before, after } = _rangeWindow\n      const grow = direction === \"before\" ? before \u003C cap : after \u003C cap\n      if (grow) {\n        if (direction === \"before\") _rangeWindow.before = before + 1\n        else _rangeWindow.after = after + 1\n      } else {\n        \u002F\u002F window is at capacity: SLIDE the anchor one period instead, so\n        \u002F\u002F travel stays unbounded while the DOM stays bounded\n        const next = stepGanttDate(s.scale, s.date, direction === \"before\" ? -1 : 1, {\n          timeZone: settings.value.timeZone,\n        })\n        if (options.date !== undefined) {\n          \u002F\u002F controlled anchor: propose the slide; nothing changes until the\n          \u002F\u002F parent adopts it\n          lastAnchorChangeWasSlide = true\n          settings.value.onDateChange?.(next)\n          return false\n        }\n        _date.value = next\n        lastAnchorChangeWasSlide = true\n        settings.value.onDateChange?.(next)\n      }\n      return true\n    },\n    didAnchorSlide() {\n      return lastAnchorChangeWasSlide\n    },\n  }\n\n  return { state, api, settings, internals }\n}\n\nexport const GanttContextKey: InjectionKey\u003CGanttInstance\u003Cunknown>> = Symbol(\"GanttContext\")\n\n\u002F** The stable calendar instance; throws outside \u003CGantt>. *\u002F\nfunction useGantt\u003CTData = unknown>(): GanttInstance\u003CTData> {\n  const instance = inject(GanttContextKey, undefined)\n  if (!instance) {\n    throw new Error(\"useGantt must be used within \u003CGantt>\")\n  }\n  return instance as unknown as GanttInstance\u003CTData>\n}\n\ninterface UseGanttSelectorOptions\u003CTData> {\n  calendar?: GanttInstance\u003CTData>\n}\n\n\u002F**\n * Thin `computed()` wrapper kept for call-site parity with the original\n * (gantt-view.tsx and friends import it by name). `isEqual` from the\n * original is not needed: Vue's computed caching already skips re-evaluation\n * unless a tracked dependency actually changed.\n *\u002F\nfunction useGanttSelector\u003CTData = unknown, TSelected = unknown>(\n  selector: (state: GanttState\u003CTData>) => TSelected,\n  options?: UseGanttSelectorOptions\u003CTData>\n): ComputedRef\u003CTSelected> {\n  const instance = options?.calendar ?? useGantt\u003CTData>()\n  return computed(() => selector(instance.state.value))\n}\n\nfunction useGanttScale(): { scale: ComputedRef\u003CGanttScale>; setScale: (scale: GanttScale) => void } {\n  const instance = useGantt()\n  return { scale: computed(() => instance.state.value.scale), setScale: instance.api.setScale }\n}\n\nexport interface GanttNavigation {\n  date: ComputedRef\u003CDate>\n  \u002F** i18n.functions.formatTitle output for the current view. *\u002F\n  title: ComputedRef\u003Cstring>\n  visibleRange: ComputedRef\u003CGanttDateRange>\n  activeRange: ComputedRef\u003CGanttDateRange>\n  next: () => void\n  prev: () => void\n  today: () => void\n  goTo: (date: Date) => void\n  \u002F** True when the anchor period contains now in the display time zone. *\u002F\n  isToday: ComputedRef\u003Cboolean>\n}\n\nfunction useGanttNavigation(): GanttNavigation {\n  const instance = useGantt()\n  const { settings } = instance\n  const title = computed(() => {\n    const s = instance.state.value\n    \u002F\u002F The title names what you are LOOKING at: the visible-center period\n    \u002F\u002F when the view reports one, otherwise the anchor period.\n    const titleDate = s.viewportCenter ?? s.date\n    const titleActive = s.viewportCenter\n      ? getGanttDateRange(s.scale, s.viewportCenter, {\n          timeZone: settings.value.timeZone,\n          weekStartsOn: settings.value.weekStartsOn,\n        }).activeRange\n      : s.activeRange\n    return settings.value.i18n.functions.formatTitle(s.scale, {\n      date: toZoned(titleDate, settings.value.timeZone),\n      activeRange: titleActive,\n      visibleRange: s.visibleRange,\n      locale: settings.value.locale,\n    })\n  })\n  return {\n    date: computed(() => instance.state.value.date),\n    title,\n    visibleRange: computed(() => instance.state.value.visibleRange),\n    activeRange: computed(() => instance.state.value.activeRange),\n    next: instance.api.next,\n    prev: instance.api.prev,\n    today: instance.api.today,\n    goTo: instance.api.goTo,\n    isToday: computed(() => {\n      const now = new Date()\n      const s = instance.state.value\n      return now >= s.activeRange.start && now \u003C s.activeRange.end\n    }),\n  }\n}\n\nfunction useGanttSelection(): {\n  selection: ComputedRef\u003CGanttSelection>\n  select: (selection: Partial\u003CGanttSelection>) => void\n  selectEvent: (key: string, opts?: { additive?: boolean }) => void\n  clearSelection: () => void\n} {\n  const instance = useGantt()\n  return {\n    selection: computed(() => instance.state.value.selection),\n    select: instance.api.select,\n    selectEvent: instance.api.selectEvent,\n    clearSelection: instance.api.clearSelection,\n  }\n}\n\nfunction useGanttInteractions(): {\n  interactions: ComputedRef\u003CGanttInteractions>\n  setInteractions: (patch: Partial\u003CGanttInteractions>) => void\n} {\n  const instance = useGantt()\n  return {\n    interactions: computed(() => instance.state.value.interactions),\n    setInteractions: instance.api.setInteractions,\n  }\n}\n\n\u002F** Expanded, sorted occurrences; defaults to the visible range. *\u002F\nfunction useGanttOccurrences\u003CTData = unknown>(\n  range?: GanttDateRange\n): ComputedRef\u003CGanttOccurrence\u003CTData>[]> {\n  const instance = useGantt\u003CTData>()\n  return computed(() => instance.api.getOccurrences(range))\n}\n\n\u002F** Resolved settings incl. merged i18n. *\u002F\nfunction useGanttSettings\u003CTData = unknown>(): ComputedRef\u003CGanttSettings\u003CTData>> {\n  return useGantt\u003CTData>().settings\n}\n\nexport interface GanttClassNames {\n  nav?: string\n  toolbar?: string\n  \u002F** The gantt body (tree + track). *\u002F\n  view?: string\n  event?: string\n}\n\n\u002F** Row context handed to tree-panel column and label renderers. *\u002F\nexport interface GanttColumnContext {\n  resource: GanttResource\n  depth: number\n  isGroup: boolean\n  collapsed: boolean\n}\n\n\u002F** One extra tree-panel column after the built-in name column. *\u002F\nexport interface GanttColumn {\n  \u002F** Stable id; doubles as the default header label. *\u002F\n  id: string\n  \u002F** Header label. *\u002F\n  title?: VNodeChild\n  \u002F** Fixed column width in px. Default 96. *\u002F\n  width?: number\n  \u002F** Cell content alignment. Default \"start\". *\u002F\n  align?: \"start\" | \"center\" | \"end\"\n  \u002F** Cell content per row; omit or return null for an empty cell. *\u002F\n  render?: (ctx: GanttColumnContext) => VNodeChild\n  \u002F** Extra classes on every cell of this column (header included). *\u002F\n  className?: string\n}\n\nexport type GanttGridLine = boolean | \"solid\" | \"dashed\"\n\nexport interface GanttTimelineLines {\n  \u002F** Unit boundary lines running down the timeline. Default solid. *\u002F\n  vertical?: GanttGridLine\n  \u002F** Row separator lines running across the timeline. Default solid. *\u002F\n  horizontal?: GanttGridLine\n}\n\n\u002F** Resolved stroke per axis; null means the axis draws nothing. *\u002F\nexport interface GanttResolvedLines {\n  vertical: \"solid\" | \"dashed\" | null\n  horizontal: \"solid\" | \"dashed\" | null\n}\n\n\u002F**\n * One place decides what the grid draws, so the header lines, the body lines\n * and the row separators can never disagree.\n *\u002F\nfunction resolveTimelineLines(\n  value: GanttTimelineLines | \"vertical\" | \"both\" | \"none\" | undefined\n): GanttResolvedLines {\n  if (value === \"none\") return { vertical: null, horizontal: null }\n  if (value === \"vertical\") return { vertical: \"solid\", horizontal: null }\n  if (value === \"both\" || value === undefined) {\n    return { vertical: \"solid\", horizontal: \"solid\" }\n  }\n  const stroke = (line: GanttGridLine | undefined) =>\n    line === false ? null : line === true || line === undefined ? \"solid\" : line\n  return { vertical: stroke(value.vertical), horizontal: stroke(value.horizontal) }\n}\n\nexport interface GanttMetrics {\n  \u002F** Height of one schedule bar. Default 1.25. *\u002F\n  laneHeight?: number\n  \u002F** Gap between stacked schedules in one node. Default 0.1875. *\u002F\n  laneGap?: number\n  \u002F** Vertical inset between the row's edges and its block of schedules. Default 0.5. *\u002F\n  rowPadding?: number\n  \u002F** Minimum row height. Default 2.5. *\u002F\n  minRowHeight?: number\n  \u002F** barLabel \"auto\" flips the title outside below this bar width. Default 7. *\u002F\n  autoLabelMin?: number\n  \u002F** Unit width at zoom 1, per scale. Day scale = width per interval unit. *\u002F\n  unitWidths?: Partial\u003CRecord\u003CGanttScale, number>>\n  \u002F** Minimum timeline pane width in px. Default 200. *\u002F\n  minTimelineWidth?: number\n  \u002F** Scroll distance (px) from an edge that grows the range. Default 160. *\u002F\n  infiniteScrollEdge?: number\n}\n\n\u002F** Live gesture snapshot handed to the drag\u002Fresize indicator render props. *\u002F\nexport interface GanttDragIndicatorProps\u003CTData = unknown> {\n  occurrence: GanttOccurrence\u003CTData>\n  kind: \"move\" | \"resize-start\" | \"resize-end\"\n  \u002F** Proposed (snapped) range of the current gesture step. *\u002F\n  start: Date\n  end: Date\n  valid: boolean\n}\n\n\u002F** Slot handed to a custom schedule-hint renderer. *\u002F\nexport interface GanttScheduleHintProps {\n  start: Date\n  end: Date\n  resource: GanttResource\n}\n\n\u002F** Parent rollup handed to a custom summary renderer. *\u002F\nexport interface GanttSummaryProps {\n  resource: GanttResource\n  start: Date\n  end: Date\n  progress: number | null\n}\n\n\u002F** Left tree-panel sizing and splitter behavior. *\u002F\nexport interface GanttTreePanelConfig {\n  \u002F** Initial panel width in px. Default 288. *\u002F\n  width?: number\n  \u002F** Splitter lower bound in px. Default 180. *\u002F\n  minWidth?: number\n  \u002F** Splitter upper bound in px. Default 640. *\u002F\n  maxWidth?: number\n  \u002F** Drag\u002Fkeyboard splitter between the panels. Default true. *\u002F\n  resizable?: boolean\n  \u002F** Width of the sticky name column in px. Default 208. *\u002F\n  nameColumnWidth?: number\n  \u002F** Fires after any user resize (drag release, keyboard, double-click reset). *\u002F\n  onWidthChange?: (width: number) => void\n}\n\nexport interface GanttRenderEventProps\u003CTData = unknown> {\n  occurrence: GanttOccurrence\u003CTData>\n  segment: GanttSegment\u003CTData>\n  isDragging: boolean\n  isSelected: boolean\n}\n\n\u002F**\n * View-layer configuration: display props and render overrides. These live on\n * \u003CGantt> (and per-view components), never in the headless options.\n *\u002F\nexport interface GanttViewConfig\u003CTData = unknown> {\n  \u002F** Red now-line on the axis. *\u002F\n  nowIndicator: boolean\n  \u002F** Day-scale unit interval in minutes: axis units and gridlines follow it. *\u002F\n  interval: number\n  \u002F** Scroll implementation for the gantt body: \"custom\" (shadcn ScrollArea) or \"native\". *\u002F\n  scrollbars: \"custom\" | \"native\"\n  \u002F** Placement hint over empty timeline track. Default off. *\u002F\n  displayScheduleHint: boolean\n  \u002F** Where the viewport opens: \"now\" (default), \"anchor\", or an explicit instant. *\u002F\n  initialCenter: \"now\" | \"anchor\" | Date\n  \u002F** Empty-track presses on schedulable rows start a drag-create gesture. Default off. *\u002F\n  dragCreate: boolean\n  \u002F** \"Add task\" affordance at the foot of the tree. Default off. *\u002F\n  displayCreateTaskHint: boolean\n  \u002F** Floating zoom in\u002Fout control over the track. Default on. *\u002F\n  zoomControl: boolean\n  \u002F** Ctrl\u002FCmd + wheel over the timeline zooms the time range. Default on. *\u002F\n  wheelZoom: boolean\n  \u002F** Nav button variant; all nav buttons follow it. Default \"ghost\". *\u002F\n  navButtonVariant: \"ghost\" | \"outline\" | \"secondary\" | \"default\"\n  \u002F** Nav button size; icon buttons use the icon twin. Default \"sm\". *\u002F\n  navButtonSize: \"sm\" | \"default\"\n  \u002F** Off-day (non-working day) marking on day\u002Fweek\u002Fmonth scales. *\u002F\n  offDays?: boolean | GanttOffDaysConfig\n  \u002F** Extra tree-panel columns after the built-in name column. *\u002F\n  columns?: GanttColumn[]\n  \u002F** Consumer slot pinned at the end of the tree-panel header. *\u002F\n  columnsMenu?: VNodeChild\n  \u002F** Tree-panel width, splitter bounds, and resizability. *\u002F\n  treePanel?: GanttTreePanelConfig\n  \u002F** Timeline gridlines; the object form controls the two axes independently. *\u002F\n  timelineLines: GanttTimelineLines | \"vertical\" | \"both\" | \"none\"\n  \u002F** Bar title placement: \"inside\" (default), \"outside\", or \"auto\". *\u002F\n  barLabel: \"inside\" | \"outside\" | \"auto\"\n  \u002F** Edge chips that scroll to bars outside the visible timeline. Default true. *\u002F\n  offscreenIndicators: boolean\n  \u002F** Extend the timeline into the past\u002Ffuture while scrolling near an edge. Default true. *\u002F\n  infiniteScroll: boolean\n  \u002F** Zoom bounds and button step for the floating control. Default 0.5 - 3, step 0.25. *\u002F\n  zoomRange?: { min?: number; max?: number; step?: number }\n  \u002F** Layout metric overrides (row\u002Flane\u002Funit geometry, thresholds). *\u002F\n  metrics?: GanttMetrics\n  \u002F** Sticky nav bar. Default false. *\u002F\n  stickyNav: boolean\n  \u002F** Leaf-row selection checkboxes in the tree panel. Default true. *\u002F\n  rowCheckboxes: boolean\n  \u002F** Controlled selected row ids; pairs with onSelectedRowsChange. *\u002F\n  selectedRows?: string[]\n  onSelectedRowsChange?: (ids: string[]) => void\n  \u002F** Controlled collapsed group ids; pairs with onCollapsedGroupsChange. *\u002F\n  collapsedGroups?: string[]\n  \u002F** Initial collapsed group ids (uncontrolled). *\u002F\n  defaultCollapsedGroups?: string[]\n  onCollapsedGroupsChange?: (ids: string[]) => void\n  \u002F** Controlled zoom multiplier; pairs with onZoomChange. *\u002F\n  zoom?: number\n  \u002F** Initial zoom multiplier (uncontrolled). Default 1. *\u002F\n  defaultZoom?: number\n  onZoomChange?: (zoom: number) => void\n  \u002F** Allow drag-create and slot clicks on rows that have children. Default false. *\u002F\n  parentScheduling: boolean\n  \u002F** Rollup strips on parent rows without bars of their own. Default true. *\u002F\n  summaryBars: boolean\n  \u002F** How many schedules a tree node may hold. \"multiple\" (default) or \"single\". *\u002F\n  scheduleMode: GanttScheduleMode\n  \u002F** Vertical placement of a row's content once a node holds several lanes. *\u002F\n  rowAlign: GanttRowAlign\n  classNames?: GanttClassNames\n  renderEvent?: (props: GanttRenderEventProps\u003CTData>) => VNodeChild\n  \u002F**\n   * Right-click menu for a bar: return ContextMenu items. Read the occurrence\n   * for the subject and drive actions through the gantt api (useGantt) or\n   * your own state - fully headless. Omit for no menu.\n   *\u002F\n  renderEventMenu?: (props: GanttRenderEventProps\u003CTData>) => VNodeChild\n  \u002F** Tree-node label. Receives the resource with its tree position. *\u002F\n  renderResourceLabel?: (props: {\n    resource: GanttResource\n    depth: number\n    isGroup: boolean\n    collapsed: boolean\n  }) => VNodeChild\n  \u002F** Right-click menu for a tree row (same contract as renderEventMenu). *\u002F\n  renderResourceMenu?: (ctx: GanttColumnContext) => VNodeChild\n  \u002F** Rendered in the timeline body when there are no resources. *\u002F\n  renderNoResources?: () => VNodeChild\n  \u002F** Replaces the smooth cursor-following MOVE clone. *\u002F\n  renderDragPreview?: (props: GanttDragIndicatorProps\u003CTData>) => VNodeChild\n  \u002F** Replaces the RESIZE edge line + status chip. *\u002F\n  renderResizeIndicator?: (props: GanttDragIndicatorProps\u003CTData>) => VNodeChild\n  \u002F** Replaces the schedule-hint tile + bubble. *\u002F\n  renderScheduleHint?: (props: GanttScheduleHintProps) => VNodeChild\n  \u002F** Replaces the parent rollup strip (the positioned wrapper stays gantt-owned). *\u002F\n  renderSummary?: (props: GanttSummaryProps) => VNodeChild\n  \u002F** Replaces the rollup MATH: return 0-100 (or null to hide). *\u002F\n  getSummaryProgress?: (ctx: { resource: GanttResource; events: GanttEvent\u003CTData>[] }) => number | null\n}\n\nexport const DEFAULT_VIEW_CONFIG: GanttViewConfig = {\n  nowIndicator: true,\n  interval: 60,\n  scrollbars: \"custom\",\n  displayScheduleHint: false,\n  initialCenter: \"now\",\n  displayCreateTaskHint: false,\n  dragCreate: false,\n  zoomControl: true,\n  wheelZoom: true,\n  navButtonVariant: \"ghost\",\n  navButtonSize: \"sm\",\n  timelineLines: \"vertical\",\n  barLabel: \"inside\",\n  offscreenIndicators: true,\n  infiniteScroll: true,\n  stickyNav: false,\n  rowCheckboxes: true,\n  parentScheduling: false,\n  summaryBars: true,\n  scheduleMode: \"multiple\",\n  rowAlign: \"start\",\n}\n\nexport const GanttViewConfigContextKey: InjectionKey\u003CComputedRef\u003CGanttViewConfig\u003Cunknown>>> =\n  Symbol(\"GanttViewConfigContext\")\n\nconst DEFAULT_VIEW_CONFIG_REF = computed(() => DEFAULT_VIEW_CONFIG)\n\n\u002F**\n * Root-level display props + render overrides, for view components.\n * Returns a `ComputedRef` (like `useGanttSettings`) - read fields as\n * `viewConfig.value.xxx`, not `viewConfig.xxx`, so a `\u003CGantt>` prop change\n * (e.g. `zoom`, `metrics`) actually reaches every consumer instead of being\n * frozen at the value seen when the component first called this composable.\n *\u002F\nfunction useGanttViewConfig\u003CTData = unknown>(): ComputedRef\u003CGanttViewConfig\u003CTData>> {\n  return (inject(GanttViewConfigContextKey, DEFAULT_VIEW_CONFIG_REF) as ComputedRef\u003CGanttViewConfig\u003CTData>>)\n}\n\nexport interface GanttNodeSchedules\u003CTData = unknown> {\n  \u002F** The node itself, or null when the id is not in the tree. *\u002F\n  node: GanttResource | null\n  \u002F** Cardinality in force for this node (its own override, else the default). *\u002F\n  scheduleMode: GanttScheduleMode\n  \u002F** The node's occurrences in the visible range, in axis order. *\u002F\n  schedules: GanttOccurrence\u003CTData>[]\n  \u002F** Pairs of the node's schedules that overlap in time. *\u002F\n  conflicts: Array\u003C[GanttOccurrence\u003CTData>, GanttOccurrence\u003CTData>]>\n}\n\n\u002F**\n * Everything a consumer needs to MANAGE one node's schedules without\n * re-deriving layout: the node, its resolved cardinality, its schedules in\n * order, and the pairs that collide.\n *\u002F\nfunction useGanttNodeSchedules\u003CTData = unknown>(\n  nodeId: string\n): ComputedRef\u003CGanttNodeSchedules\u003CTData>> {\n  const settings = useGanttSettings\u003CTData>()\n  const viewConfig = useGanttViewConfig\u003CTData>()\n  const occurrences = useGanttOccurrences\u003CTData>()\n\n  return computed(() => {\n    const node = findResource(settings.value.resources, nodeId)\n    const schedules = occurrences.value.filter((occurrence) => occurrence.event.resourceId === nodeId)\n    const conflicts: Array\u003C[GanttOccurrence\u003CTData>, GanttOccurrence\u003CTData>]> = []\n    for (let i = 0; i \u003C schedules.length; i++) {\n      for (let j = i + 1; j \u003C schedules.length; j++) {\n        const a = schedules[i]!\n        const b = schedules[j]!\n        if (eventsOverlap(a, b)) conflicts.push([a, b])\n      }\n    }\n    return {\n      node,\n      scheduleMode: resolveScheduleMode(node, viewConfig.value.scheduleMode),\n      schedules,\n      conflicts,\n    }\n  })\n}\n\n\u002F**\n * Часть порта gantt-bar.tsx (MIT) — контекст, не сам бар-компонент\n * (GanttBar.vue). Оригинал определяет его в gantt-bar.tsx; здесь он живёт\n * рядом с остальными контекстами, тот же приём, что и в reui\u002Fkanban\u002Fcontext.ts.\n *\u002F\nexport interface GanttBarContextValue\u003CTData = unknown> {\n  occurrence: GanttOccurrence\u003CTData>\n  segment: GanttSegment\u003CTData>\n  isDragging: boolean\n  isSelected: boolean\n}\n\nexport const GanttBarContextKey: InjectionKey\u003CGanttBarContextValue\u003Cunknown>> =\n  Symbol(\"GanttBarContext\")\n\n\u002F** The bar's subject; usable inside renderEvent content and bar children. *\u002F\nfunction useGanttBarContext\u003CTData = unknown>(): GanttBarContextValue\u003CTData> {\n  const ctx = inject(GanttBarContextKey, undefined)\n  if (!ctx) {\n    throw new Error(\"useGanttBarContext must be used within \u003CGanttBar>\")\n  }\n  return ctx as unknown as GanttBarContextValue\u003CTData>\n}\n\n\u002F**\n * Effective Tailwind palette presets for bar colors; every entry works on\n * light and dark surfaces through the bar's alpha background + accent border.\n *\u002F\nexport const GANTT_COLORS: Array\u003C{ name: string; value: string }> = [\n  { name: \"Blue\", value: \"var(--color-blue-500)\" },\n  { name: \"Emerald\", value: \"var(--color-emerald-500)\" },\n  { name: \"Violet\", value: \"var(--color-violet-500)\" },\n  { name: \"Rose\", value: \"var(--color-rose-500)\" },\n  { name: \"Amber\", value: \"var(--color-amber-500)\" },\n  { name: \"Cyan\", value: \"var(--color-cyan-500)\" },\n  { name: \"Orange\", value: \"var(--color-orange-500)\" },\n  { name: \"Pink\", value: \"var(--color-pink-500)\" },\n  { name: \"Teal\", value: \"var(--color-teal-500)\" },\n  { name: \"Indigo\", value: \"var(--color-indigo-500)\" },\n]\n\nexport {\n  DEFAULT_ROW_ALIGN,\n  DEFAULT_SCHEDULE_MODE,\n  resolveScheduleMode,\n  resolveTimelineLines,\n  useGantt,\n  useGanttBarContext,\n  useGanttInteractions,\n  useGanttNavigation,\n  useGanttNodeSchedules,\n  useGanttOccurrences,\n  useGanttScale,\n  useGanttSelection,\n  useGanttSelector,\n  useGanttSettings,\n  useGanttState,\n  useGanttViewConfig,\n}\n",{"path":4883,"type":480,"target":4884,"content":4885},"src\u002Freui\u002Fgantt\u002Fgestures.ts","components\u002Freui\u002Fgantt\u002Fgestures.ts","\u002F**\n * Порт ReUI Gantt dnd-движка (gantt-dnd.tsx, MIT) — ПОЛНЫЙ, не переписывание:\n * подтверждено (эта же сессия, event-calendar параллельно пришёл к тому же\n * выводу), что файл не использует dnd-kit вообще — чистые pointer-события\n * на raw DOM (`window.addEventListener(\"pointermove\"\u002F\"pointerup\"\u002F...)`,\n * `element.setPointerCapture`, ручной оверлей через `document.createElement`).\n * ADR-002 (dnd-kit -> Pragmatic Drag and Drop) сюда неприменим: нечего\n * заменять, весь `beginGesture` перенесён почти дословно.\n *\n * Единственные правки relative к оригиналу:\n *  - `instance.getState()` -> `instance.state.value` (наш `GanttInstance`\n *    хранит state как `ComputedRef`, не как функцию-геттер).\n *  - `settings.xxx` -> `settings.value.xxx` (тот же ComputedRef).\n *  - `React.PointerEvent`\u002F`e.nativeEvent` -> голый DOM `PointerEvent` (Vue\n *    не оборачивает события в синтетические, `e.currentTarget` работает\n *    так же, пока обработчик висит прямо в шаблоне на элементе-источнике).\n *  - `useCallback` не перенесён — не нужен, Vue не помнит идентичность\n *    функций между рендерами тем же способом, что и React (см. context.ts\n *    header про `useMemo`\u002F`computed`, тот же приём).\n *\u002F\nimport { useGantt, useGanttViewConfig, resolveScheduleMode, type GanttInstance } from \".\u002Fcontext\"\nimport { findResource, snapMinutes, toZoned, zonedStartOfDay } from \".\u002Flib\"\nimport type { GanttProposedUpdate, GanttScheduleMode, GanttSegment } from \".\u002Ftypes\"\nimport { addDays, differenceInCalendarDays } from \"date-fns\"\n\n\u002F**\n * Activation policy (dnd-kit parity where proven):\n * mouse move 5px before a drag starts (below = click), create 4px;\n * touch long-press 250ms with 5px tolerance (movement past tolerance\n * before the delay cancels the drag so taps stay taps).\n *\u002F\nconst GANTT_ACTIVATION = {\n  moveDistancePx: 5,\n  createDistancePx: 4,\n  touchDelayMs: 250,\n  touchTolerancePx: 5,\n} as const\n\ntype GestureKind = \"move\" | \"resize-start\" | \"resize-end\" | \"create\"\n\ninterface GanttSurface {\n  rect: DOMRect\n  rangeStart: number\n  rangeEnd: number\n  snapMin: number\n  \u002F** Mirrored axis: in RTL the range START sits at the rect's RIGHT edge. *\u002F\n  isRtl: boolean\n  rows: Array\u003C{ resourceId: string; rect: DOMRect }>\n}\n\n\u002F**\n * Pointer x (viewport px) to minutes from the range start, clamped to the\n * track. The single place the horizontal axis direction is resolved: every\n * gesture mapping (move, both resizes, create, grab offset) goes through it.\n *\u002F\nfunction surfaceMinutesAt(tl: GanttSurface, x: number): number {\n  const clamped = Math.min(Math.max(x, tl.rect.left), tl.rect.right)\n  const traveled = tl.isRtl ? tl.rect.right - clamped : clamped - tl.rect.left\n  return (traveled \u002F tl.rect.width) * ((tl.rangeEnd - tl.rangeStart) \u002F 60000)\n}\n\n\u002F** Module flag so bar onClick can ignore the click that ends a drag. *\u002F\nlet lastGestureEndedAt = 0\nfunction wasRecentDrag(): boolean {\n  return performance.now() - lastGestureEndedAt \u003C 250\n}\n\n\u002F** Mark a non-dnd gesture (e.g. a timeline pan) so the click it ends is ignored. *\u002F\nfunction markGestureEnd(): void {\n  lastGestureEndedAt = performance.now()\n}\n\n\u002F**\n * Registry of in-flight gesture cancels. A gesture measures its surface\n * (axis + row rects) once at activation, so the VIEW - not the bar, bars\n * legitimately unmount mid-gesture - must be able to abort gestures when it\n * unmounts or when the measured geometry changes under them (zoom, scale,\n * range growth, splitter). Cancel fully reverts: listeners, overlays and the\n * body drag state all clear, and no update is committed.\n *\u002F\nconst activeGestureCancels = new Set\u003C() => void>()\n\n\u002F** Cancel (and fully revert) every in-flight gantt pointer gesture. *\u002F\nfunction cancelActiveGanttGestures(): void {\n  for (const cancel of [...activeGestureCancels]) cancel()\n}\n\n\u002F**\n * View-level teardown: aborts any in-flight gesture so window listeners,\n * body-appended overlays and the gantt-dragging body class never outlive\n * the gantt. ponytail: not wired to an onUnmount hook yet - no single\n * \"view root\" component owns the gantt's lifecycle end-to-end (GanttView's\n * own assembly is still ahead); the function it needs to call already\n * exists and works, wiring it is a one-line addition once that root exists.\n *\u002F\nfunction useGanttGestureTeardown(): void {\n  cancelActiveGanttGestures()\n}\n\n\u002F**\n * Snap a translate offset to the device pixel grid. The cursor-following\n * overlays (the move clone and the resize indicator) are their own\n * `will-change: transform` compositing layers: the GPU rasterizes their text\n * once and repositions that texture each frame, so a subpixel translate\n * (getBoundingClientRect and raw clientX\u002FY are routinely fractional) resamples\n * the texture and blurs the text. Rounding each offset to a whole device pixel\n * lands the layer on the grid so glyphs stay crisp, without giving up the\n * per-frame GPU transform.\n *\u002F\nfunction snapToPixel(value: number): number {\n  const dpr = typeof window !== \"undefined\" ? window.devicePixelRatio || 1 : 1\n  return Math.round(value * dpr) \u002F dpr\n}\n\nfunction collectSurface(root: HTMLElement | null): GanttSurface | null {\n  if (!root) return null\n  const axis = root.querySelector\u003CHTMLElement>(\"[data-gantt-axis]\")\n  if (!axis) return null\n  return {\n    rect: axis.getBoundingClientRect(),\n    rangeStart: Number(axis.dataset.ganttRangeStart),\n    rangeEnd: Number(axis.dataset.ganttRangeEnd),\n    snapMin: Number(axis.dataset.ganttSnap) || 15,\n    isRtl: getComputedStyle(axis).direction === \"rtl\",\n    rows: [...root.querySelectorAll\u003CHTMLElement>(\"[data-gantt-row]\")]\n      \u002F\u002F static rows (parents that aggregate their subtree) take no drops\n      .filter((row) => row.dataset.ganttRowStatic === undefined)\n      .map((row) => ({\n        resourceId: row.dataset.ganttResource ?? \"\",\n        rect: row.getBoundingClientRect(),\n      })),\n  }\n}\n\ninterface BeginGestureConfig\u003CTData> {\n  instance: GanttInstance\u003CTData>\n  kind: GestureKind\n  origin: HTMLElement\n  startEvent: PointerEvent\n  segment?: GanttSegment\u003CTData>\n  \u002F** Consumer renders the move preview (renderDragPreview); the engine only positions it. *\u002F\n  customMoveOverlay?: boolean\n  \u002F** Consumer renders the resize indicator; the engine only positions it. *\u002F\n  customResizeOverlay?: boolean\n  \u002F** View-level cardinality default; a node's own scheduleMode wins. *\u002F\n  scheduleMode?: GanttScheduleMode\n}\n\nfunction beginGesture\u003CTData>(config: BeginGestureConfig\u003CTData>) {\n  const { instance, kind, origin, startEvent, segment, customMoveOverlay, customResizeOverlay } = config\n  const { settings, internals, api } = instance\n  const activation = { ...GANTT_ACTIVATION, ...settings.value.activation }\n  const startX = startEvent.clientX\n  const startY = startEvent.clientY\n  const pointerId = startEvent.pointerId\n  \u002F\u002F stable ancestors: bar nodes may be replaced by re-renders mid-gesture\n  const viewRoot = origin.closest\u003CHTMLElement>(\"[data-slot=gantt-view]\")\n  const ganttRoot = origin.closest\u003CHTMLElement>(\"[data-slot=gantt]\")\n  const announcer = ganttRoot?.querySelector\u003CHTMLElement>(\"[data-slot=gantt-announcer]\")\n\n  \u002F**\n   * The cursor-following overlays are appended to document.body so no ancestor\n   * transform or overflow can clip them - which also cuts them off from the\n   * gantt root, and the root is what OWNS the type scale (its `text-xs` is\n   * what every resting label inherits). Without this the clone's label jumps\n   * to the document default and reads visibly bigger than the bar it left.\n   * Copying the ROOT's resolved metrics - rather than hardcoding a size -\n   * keeps the documented contract that one class on the root (e.g.\n   * className=\"text-sm\") rescales the whole gantt, drag clone included.\n   *\u002F\n  const adoptRootTypography = (el: HTMLElement) => {\n    if (!ganttRoot) return\n    const rootStyle = getComputedStyle(ganttRoot)\n    el.style.fontSize = rootStyle.fontSize\n    el.style.lineHeight = rootStyle.lineHeight\n    el.style.fontFamily = rootStyle.fontFamily\n    el.style.letterSpacing = rootStyle.letterSpacing\n    \u002F\u002F physical positioning, logical content: the flex row mirrors so the\n    \u002F\u002F label lands on the same side of the bar as the resting one in RTL\n    el.style.direction = rootStyle.direction\n  }\n\n  const isTouch = startEvent.pointerType === \"touch\"\n  \u002F\u002F resize activates immediately on precise pointers; on touch it waits for\n  \u002F\u002F the same long-press as a move, so a stray brush over a bar edge can\n  \u002F\u002F never start an accidental resize\n  let active = kind.startsWith(\"resize\") && !isTouch\n  let surface: GanttSurface | null = active ? collectSurface(viewRoot) : null\n  let lastProposalKey = \"\"\n  let touchTimer: ReturnType\u003Ctypeof setTimeout> | null = null\n  let lastPointer: PointerEvent = startEvent\n\n  const occurrence = segment?.occurrence\n\n  \u002F\u002F ----- neighbour awareness: the other schedules in the SAME node -----\n  \u002F\u002F A node in \"single\" mode rejects any concurrency regardless of the\n  \u002F\u002F overlap option; otherwise the option decides. \"allow\" short-circuits\n  \u002F\u002F everything below, so the default gesture path is untouched.\n  const nodeId = occurrence?.event.resourceId\n  const nodeMode = resolveScheduleMode(nodeId === undefined ? null : findResource(settings.value.resources, nodeId), config.scheduleMode)\n  const overlapPolicy = nodeMode === \"single\" ? (\"reject\" as const) : settings.value.overlap\n  \u002F\u002F Read once per gesture: the gantt never mutates events mid-drag, so the\n  \u002F\u002F neighbours cannot move under us.\n  let neighbourCache: Array\u003C{ start: number; end: number }> | null = null\n  const getNeighbours = () => {\n    if (neighbourCache) return neighbourCache\n    neighbourCache =\n      !occurrence || nodeId === undefined || overlapPolicy === \"allow\"\n        ? []\n        : api\n            .getOccurrences()\n            .filter((other) => other.event.resourceId === nodeId && other.key !== occurrence.key)\n            .map((other) => ({ start: other.start.getTime(), end: other.end.getTime() }))\n    return neighbourCache\n  }\n  const overlapsNeighbour = (start: Date, end: Date) =>\n    getNeighbours().some((other) => other.start \u003C end.getTime() && other.end > start.getTime())\n  \u002F**\n   * Stop the gesture at the neighbour's edge. Runs AFTER snapping so the\n   * clamp always wins, and only against neighbours that sit clear of the\n   * bar's CURRENT span - a pre-existing overlap has no edge to stop at.\n   *\u002F\n  const clampToNeighbours = (start: Date, end: Date): { start: Date; end: Date } => {\n    if (overlapPolicy !== \"clamp\" || !occurrence) return { start, end }\n    const anchorStart = occurrence.start.getTime()\n    const anchorEnd = occurrence.end.getTime()\n    let floor = -Infinity\n    let ceiling = Infinity\n    for (const other of getNeighbours()) {\n      if (other.end \u003C= anchorStart) floor = Math.max(floor, other.end)\n      else if (other.start >= anchorEnd) ceiling = Math.min(ceiling, other.start)\n    }\n    if (floor === -Infinity && ceiling === Infinity) return { start, end }\n    let from = start.getTime()\n    let to = end.getTime()\n    if (kind === \"resize-start\") {\n      from = Math.min(Math.max(from, floor), to)\n    } else if (kind === \"resize-end\") {\n      to = Math.max(Math.min(to, ceiling), from)\n    } else {\n      \u002F\u002F a move keeps its duration and parks against whichever edge it meets\n      const duration = to - from\n      if (from \u003C floor) {\n        from = floor\n        to = from + duration\n      }\n      if (to > ceiling) {\n        to = ceiling\n        from = to - duration\n      }\n      \u002F\u002F window narrower than the bar itself: park at the earlier edge\n      if (from \u003C floor) {\n        from = floor\n        to = from + duration\n      }\n    }\n    return { start: new Date(from), end: new Date(to) }\n  }\n  \u002F\u002F Set by applyProposal when a \"reject\" policy refuses the current proposal;\n  \u002F\u002F read on pointerup so the commit is actually blocked, not merely styled.\n  let overlapRejected = false\n\n  \u002F\u002F Preserve the grab offset so the bar does not jump to the pointer\n  let grabOffsetMin = 0\n  \u002F\u002F Smooth cursor-following clone for a move: a real-looking bar that tracks\n  \u002F\u002F the pointer's x via transform (no per-frame re-render), lifted with a shadow.\n  let overlay: HTMLDivElement | null = null\n  let grabOffsetPx = 0\n  let barTop = 0\n  let barWidth = 0\n  let barHeight = 0\n\n  const createMoveOverlay = () => {\n    if (kind !== \"move\" || !occurrence || overlay || barWidth === 0) return\n    \u002F\u002F consumer-rendered preview (renderDragPreview): the view mounts it from\n    \u002F\u002F drag state; positionOverlay adopts it lazily and only writes transforms\n    if (customMoveOverlay) return\n    const color = occurrence.event.color ?? \"var(--color-primary)\"\n    overlay = document.createElement(\"div\")\n    overlay.setAttribute(\"data-slot\", \"gantt-drag-overlay\")\n    \u002F\u002F container: the bar + its label ride together; the label stays OUTSIDE\n    \u002F\u002F the bar (to the right), matching the resting look - no in-bar text\n    overlay.className =\n      \u002F\u002F physical left-0 anchor: the clone is positioned by translate3d from\n      \u002F\u002F raw clientX, which is physical - a logical start-0 anchor would pin\n      \u002F\u002F it to the RIGHT edge in RTL and fling the clone off screen\n      \"pointer-events-none fixed top-0 left-0 z-100 flex items-center gap-2 will-change-transform\"\n    adoptRootTypography(overlay)\n    overlay.style.height = `${barHeight}px`\n    const barEl = document.createElement(\"div\")\n    barEl.className = \"shrink-0 rounded-sm shadow-lg\"\n    barEl.style.width = `${barWidth}px`\n    barEl.style.height = \"100%\"\n    barEl.style.background = `color-mix(in oklab, ${color} 22%, var(--color-background))`\n    barEl.style.outline = `1px solid color-mix(in oklab, ${color} 55%, transparent)`\n    overlay.appendChild(barEl)\n    const label = document.createElement(\"span\")\n    label.className = \"text-foreground truncate font-medium whitespace-nowrap\"\n    label.textContent = occurrence.event.title\n    overlay.appendChild(label)\n    document.body.appendChild(overlay)\n    positionOverlay(lastPointer)\n  }\n\n  const positionOverlay = (e: PointerEvent) => {\n    if (!overlay && customMoveOverlay && active && kind === \"move\") {\n      overlay = document.querySelector\u003CHTMLDivElement>(\"[data-slot=gantt-drag-overlay][data-custom]\")\n      if (overlay) overlay.style.visibility = \"visible\"\n    }\n    if (!overlay) return\n    \u002F\u002F x follows the pointer freely (smooth); y stays on the bar's own row\n    overlay.style.transform = `translate3d(${snapToPixel(e.clientX - grabOffsetPx)}px, ${snapToPixel(barTop)}px, 0)`\n  }\n\n  \u002F\u002F Resize status indicator: a smooth cursor-following edge line plus a live\n  \u002F\u002F range + duration chip. The dashed ghost still shows the SNAPPED landing;\n  \u002F\u002F this overlay is the continuous feedback between snap steps.\n  let resizeOverlay: HTMLDivElement | null = null\n  let resizeLine: HTMLDivElement | null = null\n  let resizeRange: HTMLSpanElement | null = null\n  let resizeDot: HTMLSpanElement | null = null\n  let resizeDuration: HTMLSpanElement | null = null\n\n  const positionResizeOverlay = (e: PointerEvent) => {\n    if (!resizeOverlay && customResizeOverlay && kind.startsWith(\"resize\")) {\n      resizeOverlay = document.querySelector\u003CHTMLDivElement>(\"[data-slot=gantt-resize-indicator][data-custom]\")\n      if (resizeOverlay) resizeOverlay.style.visibility = \"visible\"\n    }\n    if (!resizeOverlay || !surface) return\n    \u002F\u002F x follows the pointer freely (clamped to the track); y stays on the bar\n    const x = Math.min(Math.max(e.clientX, surface.rect.left), surface.rect.right)\n    resizeOverlay.style.transform = `translate3d(${snapToPixel(x)}px, ${snapToPixel(barTop)}px, 0)`\n  }\n\n  const createResizeOverlay = () => {\n    if (!kind.startsWith(\"resize\") || !occurrence || resizeOverlay) return\n    const barEl = origin.closest\u003CHTMLElement>(\"[data-slot=gantt-bar]\")\n    const rect = (barEl ?? origin).getBoundingClientRect()\n    barTop = rect.top\n    barHeight = rect.height\n    \u002F\u002F consumer-rendered indicator: rect capture above still runs (the engine\n    \u002F\u002F positions the consumer's wrapper), only the default DOM is skipped\n    if (customResizeOverlay) return\n    const color = occurrence.event.color ?? \"var(--color-primary)\"\n    resizeOverlay = document.createElement(\"div\")\n    resizeOverlay.setAttribute(\"data-slot\", \"gantt-resize-indicator\")\n    resizeOverlay.className =\n      \u002F\u002F physical left-0 anchor, same reason as the move clone above\n      \"pointer-events-none fixed top-0 left-0 z-100 will-change-transform\"\n    adoptRootTypography(resizeOverlay)\n    resizeOverlay.style.height = `${barHeight}px`\n    resizeLine = document.createElement(\"div\")\n    resizeLine.className = \"h-full w-0.5 -translate-x-1\u002F2 rounded-full\"\n    resizeLine.style.background = color\n    resizeOverlay.appendChild(resizeLine)\n    const chip = document.createElement(\"div\")\n    chip.className =\n      \u002F\u002F physical left-0: centered with a physical translate on a physical anchor\n      \u002F\u002F no text size of its own: it inherits the root scale adopted above, so\n      \u002F\u002F the chip tracks a consumer rescale instead of pinning itself to 12px\n      \"bg-foreground text-background absolute bottom-full left-0 mb-1.5 flex -translate-x-1\u002F2 items-center gap-1.5 rounded-md px-2 py-1 font-medium whitespace-nowrap\"\n    resizeRange = document.createElement(\"span\")\n    chip.appendChild(resizeRange)\n    resizeDot = document.createElement(\"span\")\n    resizeDot.className = \"bg-background\u002F40 size-1 shrink-0 rounded-full\"\n    resizeDot.setAttribute(\"aria-hidden\", \"true\")\n    chip.appendChild(resizeDot)\n    resizeDuration = document.createElement(\"span\")\n    chip.appendChild(resizeDuration)\n    \u002F\u002F The arrow. Every other bubble in the gantt has one pointing at what it\n    \u002F\u002F describes; this chip had none, so a resize looked like a different\n    \u002F\u002F component from the hover hint. Physical left-1\u002F2 to match the chip's own\n    \u002F\u002F physical anchor, and out of flow so the chip's flex gap ignores it.\n    const chipArrow = document.createElement(\"span\")\n    chipArrow.setAttribute(\"aria-hidden\", \"true\")\n    chipArrow.className = \"bg-foreground absolute -bottom-1 left-1\u002F2 size-2.5 -translate-x-1\u002F2 rotate-45 rounded-[2px]\"\n    chip.appendChild(chipArrow)\n    resizeOverlay.appendChild(chip)\n    document.body.appendChild(resizeOverlay)\n    \u002F\u002F seed the chip with the CURRENT range so it never flashes empty;\n    \u002F\u002F zoned so the label names the same day the grid shows\n    resizeRange.textContent = settings.value.i18n.functions.formatEventTime(\n      toZoned(occurrence.start, settings.value.timeZone),\n      toZoned(occurrence.end, settings.value.timeZone),\n      occurrence.allDay ?? false,\n      settings.value.locale\n    )\n    const days = Math.round((occurrence.end.getTime() - occurrence.start.getTime()) \u002F 86_400_000)\n    if (days >= 1) {\n      resizeDuration.textContent = settings.value.i18n.labels.durationDays(days)\n    } else {\n      resizeDot.style.display = \"none\"\n      resizeDuration.style.display = \"none\"\n    }\n    positionResizeOverlay(startEvent)\n  }\n\n  \u002F\u002F resize activates immediately, so its indicator mounts with the gesture\n  if (active) createResizeOverlay()\n\n  const activationDistance = kind === \"create\" ? activation.createDistancePx : activation.moveDistancePx\n\n  \u002F\u002F Each gesture keeps its own cursor: a resize must stay ew-resize for the\n  \u002F\u002F whole drag (flipping to grabbing reads as a move), a move grabs.\n  const gestureCursor = kind.startsWith(\"resize\") ? \"ew-resize\" : \"grabbing\"\n  const setBodyDragging = (on: boolean, invalid = false) => {\n    document.body.classList.toggle(\"gantt-dragging\", on)\n    document.body.style.cursor = on ? (invalid ? \"not-allowed\" : gestureCursor) : \"\"\n    document.body.style.userSelect = on ? \"none\" : \"\"\n    if (!on) document.body.style.removeProperty(\"-webkit-user-select\")\n  }\n\n  const activate = () => {\n    if (active) return\n    active = true\n    surface = collectSurface(viewRoot)\n    \u002F\u002F touch resize activates here (long-press) instead of at gesture start,\n    \u002F\u002F so its indicator mounts now; the guard inside makes this a no-op for\n    \u002F\u002F every other path\n    createResizeOverlay()\n    if (kind === \"move\" && occurrence && surface) {\n      const pointerMin = surfaceMinutesAt(surface, startX)\n      \u002F\u002F TRUE start, never clamped to the range: a bar that begins before the\n      \u002F\u002F visible window (negative minutes) must keep its real grab offset, or\n      \u002F\u002F the first snapped proposal teleports its start to the range edge\n      const occStartMin = (occurrence.start.getTime() - surface.rangeStart) \u002F 60000\n      grabOffsetMin = pointerMin - occStartMin\n      const rect = origin.getBoundingClientRect()\n      barTop = rect.top\n      barWidth = rect.width\n      barHeight = rect.height\n      grabOffsetPx = startX - rect.left\n      createMoveOverlay()\n    }\n    setBodyDragging(true)\n  }\n\n  const computeProposal = (e: PointerEvent): { start: Date; end: Date; allDay: boolean; resourceId?: string } | null => {\n    if (!surface) return null\n    const tl = surface\n    const rangeMinutes = (tl.rangeEnd - tl.rangeStart) \u002F 60000\n    const minutesAt = (x: number) => surfaceMinutesAt(tl, x)\n    \u002F\u002F Day-grid scales snap to real zoned midnights, not 1440-minute\n    \u002F\u002F multiples from the range start - those drift by an hour across DST\n    const snapMin = (minutes: number) => {\n      if (tl.snapMin \u003C 24 * 60) return snapMinutes(minutes, tl.snapMin)\n      const ms = tl.rangeStart + minutes * 60000\n      const dayStart = zonedStartOfDay(new Date(ms), settings.value.timeZone)\n      const dayEnd = zonedStartOfDay(addDays(toZoned(new Date(ms), settings.value.timeZone), 1), settings.value.timeZone)\n      const snapped = ms - dayStart.getTime() \u003C dayEnd.getTime() - ms ? dayStart : dayEnd\n      return (snapped.getTime() - tl.rangeStart) \u002F 60000\n    }\n    const rowAt = (y: number) => {\n      let best = tl.rows[0]\n      for (const row of tl.rows) {\n        if (y >= row.rect.top && y \u003C row.rect.bottom) return row\n        if (best && Math.abs(y - (row.rect.top + row.rect.height \u002F 2)) \u003C Math.abs(y - (best.rect.top + best.rect.height \u002F 2))) {\n          best = row\n        }\n      }\n      return best\n    }\n    const at = (minutes: number) => new Date(tl.rangeStart + minutes * 60000)\n\n    if (kind === \"create\") {\n      const anchorMin = snapMin(minutesAt(startX))\n      const curMin = snapMin(minutesAt(e.clientX))\n      const lo = Math.min(anchorMin, curMin)\n      \u002F\u002F a bare click still yields a usable slot: at least slotDuration long\n      const hi = Math.max(anchorMin, curMin, lo + Math.max(tl.snapMin, settings.value.slotDuration))\n      return { start: at(lo), end: at(hi), allDay: false, resourceId: rowAt(startY)?.resourceId }\n    }\n    if (!occurrence) return null\n    const midnightAligned = (d: Date) => zonedStartOfDay(d, settings.value.timeZone).getTime() === d.getTime()\n    if (kind === \"move\") {\n      \u002F\u002F x-axis only: the bar slides along its OWN row, never across rows.\n      \u002F\u002F The proposal preserves the pointer DELTA - no clamping to the visible\n      \u002F\u002F range, or bars crossing the window edge would teleport to it.\n      const start = at(snapMin(minutesAt(e.clientX) - grabOffsetMin))\n      \u002F\u002F Day-snapped scales preserve the CALENDAR span for day-aligned bars:\n      \u002F\u002F a 3-day bar dragged across a DST change stays midnight-to-midnight\n      \u002F\u002F (72h +\u002F- 1h), never drifting to a 23:00 end. Sub-day events keep\n      \u002F\u002F their exact ms duration.\n      let end: Date\n      if (tl.snapMin >= 24 * 60 && (occurrence.allDay || (midnightAligned(occurrence.start) && midnightAligned(occurrence.end)))) {\n        const daySpan = Math.max(\n          differenceInCalendarDays(toZoned(occurrence.end, settings.value.timeZone), toZoned(occurrence.start, settings.value.timeZone)),\n          1\n        )\n        end = zonedStartOfDay(addDays(toZoned(start, settings.value.timeZone), daySpan), settings.value.timeZone)\n      } else {\n        end = new Date(start.getTime() + (occurrence.end.getTime() - occurrence.start.getTime()))\n      }\n      const bounded = clampToNeighbours(start, end)\n      return { start: bounded.start, end: bounded.end, allDay: occurrence.allDay, resourceId: occurrence.event.resourceId }\n    }\n    const min = snapMin(minutesAt(e.clientX))\n    if (kind === \"resize-start\") {\n      const endMin = (occurrence.end.getTime() - tl.rangeStart) \u002F 60000\n      \u002F\u002F Minimum length = one snap unit; on day grids that unit is the LAST\n      \u002F\u002F zoned midnight before the end (raw 1440-minute arithmetic lands off\n      \u002F\u002F the midnight grid across DST changes).\n      const maxStartMin =\n        tl.snapMin >= 24 * 60\n          ? (zonedStartOfDay(\n              midnightAligned(occurrence.end) ? addDays(toZoned(occurrence.end, settings.value.timeZone), -1) : occurrence.end,\n              settings.value.timeZone\n            ).getTime() -\n              tl.rangeStart) \u002F\n            60000\n          : endMin - tl.snapMin\n      const clamped = Math.min(Math.max(min, 0), maxStartMin)\n      const bounded = clampToNeighbours(at(clamped), occurrence.end)\n      return { start: bounded.start, end: bounded.end, allDay: occurrence.allDay, resourceId: occurrence.event.resourceId }\n    }\n    const startMin = (occurrence.start.getTime() - tl.rangeStart) \u002F 60000\n    \u002F\u002F Mirror of the resize-start bound: the FIRST zoned midnight after the\n    \u002F\u002F start on day grids, plain snap arithmetic otherwise.\n    const minEndMin =\n      tl.snapMin >= 24 * 60\n        ? (zonedStartOfDay(addDays(toZoned(occurrence.start, settings.value.timeZone), 1), settings.value.timeZone).getTime() - tl.rangeStart) \u002F 60000\n        : startMin + tl.snapMin\n    const clamped = Math.max(Math.min(min, rangeMinutes), minEndMin)\n    const bounded = clampToNeighbours(occurrence.start, at(clamped))\n    return { start: bounded.start, end: bounded.end, allDay: occurrence.allDay, resourceId: occurrence.event.resourceId }\n  }\n\n  const applyProposal = (e: PointerEvent) => {\n    const proposal = computeProposal(e)\n    if (!proposal) return\n    const key = `${proposal.start.getTime()}-${proposal.end.getTime()}-${proposal.allDay}-${proposal.resourceId ?? \"\"}`\n    if (key === lastProposalKey) return\n    lastProposalKey = key\n\n    if (kind === \"create\") {\n      const draft = { ...proposal }\n      if (settings.value.canSelectSlot && !settings.value.canSelectSlot(draft)) return\n      internals.setSlotDraft(draft)\n      return\n    }\n    const update: GanttProposedUpdate\u003CTData> = {\n      event: occurrence!.event,\n      occurrence: occurrence!,\n      ...proposal,\n      source: kind === \"move\" ? \"drag\" : (kind as \"resize-start\" | \"resize-end\"),\n    }\n    \u002F\u002F \"reject\" is the one veto the engine owns: it both styles the ghost AND\n    \u002F\u002F blocks the commit below. canDropEvent stays advisory, as documented.\n    overlapRejected = overlapPolicy === \"reject\" && overlapsNeighbour(proposal.start, proposal.end)\n    const valid = !overlapRejected && (settings.value.canDropEvent ? settings.value.canDropEvent(update) : true)\n    \u002F\u002F live status: the indicator chip always names the CURRENT proposed\n    \u002F\u002F range; the edge line flips to destructive on an invalid drop\n    if (resizeRange && resizeDot && resizeDuration) {\n      resizeRange.textContent = settings.value.i18n.functions.formatEventTime(\n        toZoned(proposal.start, settings.value.timeZone),\n        toZoned(proposal.end, settings.value.timeZone),\n        proposal.allDay,\n        settings.value.locale\n      )\n      const days = Math.round((proposal.end.getTime() - proposal.start.getTime()) \u002F 86_400_000)\n      const showDays = days >= 1\n      resizeDot.style.display = showDays ? \"\" : \"none\"\n      resizeDuration.style.display = showDays ? \"\" : \"none\"\n      if (showDays) {\n        resizeDuration.textContent = settings.value.i18n.labels.durationDays(days)\n      }\n    }\n    if (resizeLine) {\n      resizeLine.style.background = valid ? (occurrence!.event.color ?? \"var(--color-primary)\") : \"var(--color-destructive)\"\n    }\n    setBodyDragging(true, !valid)\n    internals.setDrag({\n      kind: kind === \"move\" ? \"move\" : (kind as \"resize-start\" | \"resize-end\"),\n      occurrence: occurrence!,\n      proposedStart: proposal.start,\n      proposedEnd: proposal.end,\n      proposedAllDay: proposal.allDay,\n      proposedResourceId: proposal.resourceId,\n      valid,\n    })\n  }\n\n  \u002F\u002F ----- edge auto-scroll: pan the timeline while dragging near its edge -----\n  \u002F\u002F The pointer is clamped to the visible track, so without this a bar can\n  \u002F\u002F never travel past the window. Holding the pointer inside the edge zone\n  \u002F\u002F scrolls the viewport (speed eased by proximity), refreshes the track rect\n  \u002F\u002F (the axis moved under the pointer) and re-derives the proposal from the\n  \u002F\u002F same pointer position. Programmatic scrolls never mark user intent, so\n  \u002F\u002F this can never trigger infinite-range growth mid-gesture.\n  const AUTO_SCROLL_EDGE_PX = 24\n  const AUTO_SCROLL_MAX_SPEED = 14\n  let autoScrollRaf = 0\n  \u002F\u002F ponytail: our timeline pane IS the scroll-area-viewport (see\n  \u002F\u002F GanttTreeSplitPanes.vue's section-3 header) - one selector instead of\n  \u002F\u002F upstream's nested \"pane [data-slot=scroll-area-viewport]\".\n  const timelineViewport = viewRoot?.querySelector\u003CHTMLElement>(\"[data-slot=gantt-timeline-pane]\")\n  const autoScrollTick = () => {\n    autoScrollRaf = 0\n    if (finished || !active || !surface || !timelineViewport) return\n    const paneRect = timelineViewport.getBoundingClientRect()\n    const x = lastPointer.clientX\n    let speed = 0\n    if (x \u003C paneRect.left + AUTO_SCROLL_EDGE_PX) {\n      speed = -((paneRect.left + AUTO_SCROLL_EDGE_PX - x) \u002F AUTO_SCROLL_EDGE_PX) * AUTO_SCROLL_MAX_SPEED\n    } else if (x > paneRect.right - AUTO_SCROLL_EDGE_PX) {\n      speed = ((x - (paneRect.right - AUTO_SCROLL_EDGE_PX)) \u002F AUTO_SCROLL_EDGE_PX) * AUTO_SCROLL_MAX_SPEED\n    }\n    if (speed === 0) return\n    const before = timelineViewport.scrollLeft\n    timelineViewport.scrollLeft = before + speed\n    if (timelineViewport.scrollLeft === before) return \u002F\u002F parked on the end\n    const axis = viewRoot?.querySelector\u003CHTMLElement>(\"[data-gantt-axis]\")\n    if (axis) surface.rect = axis.getBoundingClientRect()\n    applyProposal(lastPointer)\n    positionResizeOverlay(lastPointer)\n    scheduleAutoScroll()\n  }\n  const scheduleAutoScroll = () => {\n    if (!autoScrollRaf) autoScrollRaf = requestAnimationFrame(autoScrollTick)\n  }\n\n  \u002F\u002F idempotent: pointerup, pointercancel, Escape, blur and the view-level\n  \u002F\u002F teardown can race; whichever lands first wins and the rest no-op\n  let finished = false\n  const cleanup = () => {\n    if (finished) return\n    finished = true\n    activeGestureCancels.delete(cancel)\n    if (autoScrollRaf) cancelAnimationFrame(autoScrollRaf)\n    try {\n      origin.releasePointerCapture(pointerId)\n    } catch {\n      \u002F\u002F capture already released (pointer gone or origin detached)\n    }\n    window.removeEventListener(\"pointermove\", onPointerMove)\n    window.removeEventListener(\"pointerup\", onPointerUp)\n    window.removeEventListener(\"pointercancel\", onCancel)\n    window.removeEventListener(\"blur\", onWindowBlur)\n    window.removeEventListener(\"keydown\", onKeyDown, true)\n    if (touchTimer) clearTimeout(touchTimer)\n    \u002F\u002F consumer-rendered overlays are owned by the view state; they unmount\n    \u002F\u002F when the drag state clears, so the engine must never removeChild them\n    if (!customMoveOverlay) overlay?.remove()\n    overlay = null\n    if (!customResizeOverlay) resizeOverlay?.remove()\n    resizeOverlay = null\n    setBodyDragging(false)\n  }\n\n  const cancel = () => {\n    cleanup()\n    if (active) {\n      lastGestureEndedAt = performance.now()\n      internals.setDrag(null)\n      internals.setSlotDraft(null)\n    }\n  }\n\n  const onKeyDown = (e: KeyboardEvent) => {\n    if (e.key === \"Escape\") {\n      e.stopPropagation()\n      cancel()\n    }\n  }\n\n  \u002F\u002F focus loss mid-gesture (alt-tab, OS dialogs) means the release may never\n  \u002F\u002F be delivered; treat it as a cancel so the gesture cannot get stuck\n  const onWindowBlur = () => cancel()\n\n  const onPointerMove = (e: PointerEvent) => {\n    if (e.pointerId !== pointerId) return\n    lastPointer = e\n    if (!active) {\n      const distance = Math.hypot(e.clientX - startX, e.clientY - startY)\n      if (isTouch) {\n        \u002F\u002F Long-press pending: moving past tolerance means scroll, not drag\n        if (distance > activation.touchTolerancePx) cancel()\n        return\n      }\n      if (distance \u003C activationDistance) return\n      activate()\n    }\n    applyProposal(e)\n    positionOverlay(e)\n    positionResizeOverlay(e)\n    scheduleAutoScroll()\n  }\n\n  const onPointerUp = (e: PointerEvent) => {\n    if (e.pointerId !== pointerId) return\n    cleanup()\n    if (!active) return\n    lastGestureEndedAt = performance.now()\n\n    const state = instance.state.value\n    if (kind === \"create\") {\n      const draft = state.slotDraft\n      internals.setSlotDraft(null)\n      if (draft) {\n        api.select({ slot: { start: draft.start, end: draft.end, allDay: draft.allDay } })\n        settings.value.onSelectSlot?.(draft)\n      }\n      return\n    }\n    const drag = state.drag\n    internals.setDrag(null)\n    if (!drag || !occurrence) return\n    \u002F\u002F the node refuses concurrency: revert instead of committing an overlap\n    if (overlapRejected) return\n    const unchanged =\n      drag.proposedStart.getTime() === occurrence.start.getTime() &&\n      drag.proposedEnd.getTime() === occurrence.end.getTime() &&\n      (drag.proposedResourceId === undefined || drag.proposedResourceId === occurrence.event.resourceId)\n    if (unchanged) return\n    \u002F\u002F Commit through the one validation funnel; consumer reject = automatic\n    \u002F\u002F revert because the gantt never mutated during the gesture.\n    const accepted = internals.applyProposedUpdate({\n      event: occurrence.event,\n      occurrence,\n      start: drag.proposedStart,\n      end: drag.proposedEnd,\n      allDay: drag.proposedAllDay,\n      resourceId: drag.proposedResourceId,\n      source: kind === \"move\" ? \"drag\" : (kind as \"resize-start\" | \"resize-end\"),\n    })\n    if (accepted && announcer) {\n      announcer.textContent = `${occurrence.event.title}, ${settings.value.i18n.functions.formatEventTime(\n        toZoned(drag.proposedStart, settings.value.timeZone),\n        toZoned(drag.proposedEnd, settings.value.timeZone),\n        drag.proposedAllDay,\n        settings.value.locale\n      )}`\n    }\n  }\n\n  const onCancel = (e: PointerEvent) => {\n    if (e.pointerId !== pointerId) return\n    cancel()\n  }\n\n  window.addEventListener(\"pointermove\", onPointerMove)\n  window.addEventListener(\"pointerup\", onPointerUp)\n  window.addEventListener(\"pointercancel\", onCancel)\n  window.addEventListener(\"blur\", onWindowBlur)\n  window.addEventListener(\"keydown\", onKeyDown, true)\n  activeGestureCancels.add(cancel)\n\n  \u002F\u002F Capture the pointer so a release OUTSIDE the OS window still delivers\n  \u002F\u002F pointerup here instead of leaving the gesture stuck. Captured events keep\n  \u002F\u002F bubbling to the window listeners above, and if the origin node is removed\n  \u002F\u002F mid-gesture the capture auto-releases - behavior then degrades to plain\n  \u002F\u002F window listeners, never worse than before. Guarded: the pointer can\n  \u002F\u002F already be gone by now (fast flicks, synthetic events).\n  try {\n    origin.setPointerCapture(pointerId)\n  } catch {\n    \u002F\u002F capture is an enhancement, never a requirement\n  }\n\n  \u002F\u002F Touch: long-press activation (movement past tolerance cancels above)\n  if (isTouch && !active) {\n    touchTimer = setTimeout(() => {\n      activate()\n      applyProposal(lastPointer)\n    }, activation.touchDelayMs)\n  }\n}\n\ninterface GanttGestures\u003CTData = unknown> {\n  canDrag: (segment: GanttSegment\u003CTData>) => boolean\n  canResize: (segment: GanttSegment\u003CTData>) => boolean\n  beginMove: (e: PointerEvent, segment: GanttSegment\u003CTData>) => void\n  beginResize: (e: PointerEvent, segment: GanttSegment\u003CTData>, edge: \"start\" | \"end\") => void\n  beginCreate: (e: PointerEvent) => void\n}\n\n\u002F** Per-bar \u002F per-row pointer gesture wiring. *\u002F\nfunction useGanttGestures\u003CTData = unknown>(): GanttGestures\u003CTData> {\n  const instance = useGantt\u003CTData>()\n  const viewConfig = useGanttViewConfig\u003CTData>()\n  \u002F\u002F presence flags only: the engine skips its default overlay DOM and\n  \u002F\u002F positions the consumer-rendered node instead\n  const customMoveOverlay = () => !!viewConfig.value.renderDragPreview\n  const customResizeOverlay = () => !!viewConfig.value.renderResizeIndicator\n\n  const canDrag = (segment: GanttSegment\u003CTData>): boolean => {\n    const { interactions } = instance.state.value\n    const event = segment.occurrence.event\n    return interactions.drag && !event.readOnly && event.draggable !== false\n  }\n\n  const beginMove = (e: PointerEvent, segment: GanttSegment\u003CTData>) => {\n    if (e.button !== 0 || !canDrag(segment)) return\n    beginGesture({\n      instance,\n      kind: \"move\",\n      origin: e.currentTarget as HTMLElement,\n      startEvent: e,\n      segment,\n      customMoveOverlay: customMoveOverlay(),\n      scheduleMode: viewConfig.value.scheduleMode,\n    })\n  }\n\n  const canResize = (segment: GanttSegment\u003CTData>): boolean => {\n    const { interactions } = instance.state.value\n    const event = segment.occurrence.event\n    return interactions.resize && !event.readOnly && event.resizable !== false\n  }\n\n  const beginResize = (e: PointerEvent, segment: GanttSegment\u003CTData>, edge: \"start\" | \"end\") => {\n    if (e.button !== 0 || !canResize(segment)) return\n    e.stopPropagation()\n    e.preventDefault()\n    beginGesture({\n      instance,\n      kind: edge === \"start\" ? \"resize-start\" : \"resize-end\",\n      origin: e.currentTarget as HTMLElement,\n      startEvent: e,\n      segment,\n      customResizeOverlay: customResizeOverlay(),\n      scheduleMode: viewConfig.value.scheduleMode,\n    })\n  }\n\n  const beginCreate = (e: PointerEvent) => {\n    if (e.button !== 0) return\n    if (!instance.state.value.interactions.selectSlot) return\n    beginGesture({\n      instance,\n      kind: \"create\",\n      origin: e.currentTarget as HTMLElement,\n      startEvent: e,\n    })\n  }\n\n  return { canDrag, canResize, beginMove, beginResize, beginCreate }\n}\n\nexport { cancelActiveGanttGestures, GANTT_ACTIVATION, markGestureEnd, useGanttGestures, useGanttGestureTeardown, wasRecentDrag }\nexport type { GanttGestures }\n",{"path":4887,"type":480,"target":4888,"content":4889},"src\u002Freui\u002Fgantt\u002Fi18n.ts","components\u002Freui\u002Fgantt\u002Fi18n.ts","\u002F\u002F Title: Gantt I18n\n\u002F\u002F Description: Default UI texts, date-format strings, and formatter functions for the gantt, fully overridable per key.\n\nimport type { GanttDateRange, GanttScale } from \".\u002Ftypes\"\nimport {\n  format,\n  isSameMonth,\n  isSameYear,\n  subMilliseconds,\n  type Locale,\n} from \"date-fns\"\n\ninterface GanttI18nConfig {\n  labels: {\n    today: string\n    previous: string\n    next: string\n    addEvent: string\n    \u002F** \"Add task\" hint at the foot of the tree. *\u002F\n    addTask: string\n    allDay: string\n    loading: string\n    event: string\n    events: (count: number) => string\n    week: (weekNumber: number) => string\n    resources: string\n    goToDate: string\n    \u002F** Hover hint over empty row space, click-only create. *\u002F\n    scheduleHint: string\n    \u002F** Same hint where dragCreate is on and a drag paints a range. *\u002F\n    scheduleHintDrag: string\n    reorder: string\n    \u002F** Scale switcher label (\"Timeline scale\"). *\u002F\n    selectView: string\n    zoomIn: string\n    zoomOut: string\n    \u002F** Aria-label of the tree\u002Ftimeline splitter. *\u002F\n    resizePanel: string\n    \u002F** Aria-label of the off-screen bar chips. *\u002F\n    jumpToBar: (title: string) => string\n    \u002F** Read to screen readers as part of the bar label. *\u002F\n    progress: (percent: number) => string\n    \u002F** Live duration readout on the resize indicator. *\u002F\n    durationDays: (days: number) => string\n    \u002F** Appended to the bar aria-label when its segment is clipped by the range. *\u002F\n    continues: string\n    scales: {\n      day: string\n      week: string\n      month: string\n      quarter: string\n      year: string\n    }\n  }\n  \u002F** date-fns format strings, applied with the gantt `locale`. *\u002F\n  formats: {\n    monthTitle: string\n    dayTitle: string\n    timeGutter: string\n    eventTime: string\n  }\n  functions: {\n    formatTitle: (\n      scale: GanttScale,\n      ctx: {\n        date: Date\n        activeRange: GanttDateRange\n        visibleRange: GanttDateRange\n        locale?: Locale\n      }\n    ) => string\n    formatEventTime: (\n      start: Date,\n      end: Date,\n      allDay: boolean,\n      locale?: Locale\n    ) => string\n    formatDayRange: (range: GanttDateRange, locale?: Locale) => string\n    \u002F** Composes the bar's screen-reader label from its localized parts. *\u002F\n    formatEventAriaLabel: (parts: {\n      title: string\n      timeLabel: string\n      rowTitle?: string\n      progressLabel?: string\n      continues: boolean\n    }) => string\n  }\n}\n\nconst DEFAULT_LABELS: GanttI18nConfig[\"labels\"] = {\n  today: \"Today\",\n  previous: \"Previous\",\n  next: \"Next\",\n  addEvent: \"Add event\",\n  addTask: \"Add task\",\n  allDay: \"All day\",\n  loading: \"Loading events\",\n  event: \"event\",\n  events: (count) => (count === 1 ? \"1 event\" : `${count} events`),\n  week: (weekNumber) => `W${weekNumber}`,\n  resources: \"Resources\",\n  goToDate: \"Go to date\",\n  scheduleHint: \"Click to add a schedule\",\n  scheduleHintDrag: \"Click or drag to add a schedule\",\n  reorder: \"Reorder\",\n  selectView: \"Select view\",\n  zoomIn: \"Zoom in\",\n  zoomOut: \"Zoom out\",\n  resizePanel: \"Resize panel\",\n  jumpToBar: (title) => `Scroll to \"${title}\"`,\n  progress: (percent) => `${percent}% complete`,\n  durationDays: (days) => (days === 1 ? \"1 day\" : `${days} days`),\n  continues: \"continues\",\n  scales: {\n    day: \"Day\",\n    week: \"Week\",\n    month: \"Month\",\n    quarter: \"Quarter\",\n    year: \"Year\",\n  },\n}\n\nconst DEFAULT_FORMATS: GanttI18nConfig[\"formats\"] = {\n  monthTitle: \"MMMM yyyy\",\n  dayTitle: \"EEEE, MMMM d, yyyy\",\n  timeGutter: \"h a\",\n  eventTime: \"h:mm a\",\n}\n\n\u002F**\n * Default formatting functions BOUND to a config's labels\u002Fformats, so that\n * `formats` overrides flow into the default renderers (a consumer overriding\n * formats.eventTime without replacing formatEventTime still sees it applied).\n *\u002F\nfunction makeDefaultGanttFunctions(\n  cfg: Pick\u003CGanttI18nConfig, \"labels\" | \"formats\">\n): GanttI18nConfig[\"functions\"] {\n  return {\n    formatTitle: (scale, { date, activeRange, locale }) => {\n      const opts = { locale }\n      if (scale === \"day\") {\n        return format(date, cfg.formats.dayTitle, opts)\n      }\n      if (scale === \"month\") {\n        return format(date, cfg.formats.monthTitle, opts)\n      }\n      if (scale === \"quarter\") {\n        return format(date, \"QQQ yyyy\", opts)\n      }\n      if (scale === \"year\") {\n        return format(date, \"yyyy\", opts)\n      }\n      \u002F\u002F week: smart range label, last day is activeRange.end - 1ms.\n      \u002F\u002F subMilliseconds keeps the zoned date type (a plain new Date(ms)\n      \u002F\u002F would flip the label to the machine zone near midnight)\n      const rangeEnd = subMilliseconds(activeRange.end, 1)\n      const start = activeRange.start\n      if (isSameMonth(start, rangeEnd)) {\n        return `${format(start, \"MMMM d\", opts)} - ${format(rangeEnd, \"d, yyyy\", opts)}`\n      }\n      if (isSameYear(start, rangeEnd)) {\n        return `${format(start, \"MMM d\", opts)} - ${format(rangeEnd, \"MMM d, yyyy\", opts)}`\n      }\n      return `${format(start, \"MMM d, yyyy\", opts)} - ${format(rangeEnd, \"MMM d, yyyy\", opts)}`\n    },\n    formatEventTime: (start, end, allDay, locale) => {\n      const opts = { locale }\n      if (allDay) {\n        \u002F\u002F a gantt bar is a DATE RANGE: show it, never a bare \"All day\".\n        \u002F\u002F Ends are exclusive midnights, so the last shown day is end - 1ms;\n        \u002F\u002F subMilliseconds keeps the caller's zoned date type intact.\n        const last =\n          end.getTime() - 1 >= start.getTime() ? subMilliseconds(end, 1) : start\n        const sameDay =\n          format(start, \"yyyy-MM-dd\") === format(last, \"yyyy-MM-dd\")\n        if (sameDay) return format(start, \"MMM d, yyyy\", opts)\n        if (isSameYear(start, last)) {\n          return `${format(start, \"MMM d\", opts)} - ${format(last, \"MMM d, yyyy\", opts)}`\n        }\n        return `${format(start, \"MMM d, yyyy\", opts)} - ${format(last, \"MMM d, yyyy\", opts)}`\n      }\n      const fmt = cfg.formats.eventTime\n      \u002F\u002F Multi-day timed events carry the date on both sides. Compare calendar\n      \u002F\u002F days off the last rendered instant (end is exclusive, so a 14:00 to\n      \u002F\u002F midnight bar still ends on the start day). Elapsed ms would miss an\n      \u002F\u002F exactly-24h bar and a DST day that only runs 23 hours.\n      const lastInstant =\n        end.getTime() - 1 >= start.getTime() ? subMilliseconds(end, 1) : start\n      if (format(start, \"yyyy-MM-dd\") !== format(lastInstant, \"yyyy-MM-dd\")) {\n        return `${format(start, `MMM d, ${fmt}`, opts)} - ${format(end, `MMM d, ${fmt}`, opts)}`\n      }\n      return `${format(start, fmt, opts)} - ${format(end, fmt, opts)}`\n    },\n    formatDayRange: (range, locale) => {\n      const opts = { locale }\n      const rangeEnd = subMilliseconds(range.end, 1)\n      return `${format(range.start, \"MMM d\", opts)} - ${format(rangeEnd, \"MMM d\", opts)}`\n    },\n    formatEventAriaLabel: ({\n      title,\n      timeLabel,\n      rowTitle,\n      progressLabel,\n      continues,\n    }) =>\n      [\n        title,\n        timeLabel,\n        rowTitle,\n        progressLabel,\n        continues ? cfg.labels.continues : undefined,\n      ]\n        .filter(Boolean)\n        .join(\", \"),\n  }\n}\n\nconst DEFAULT_GANTT_I18N: GanttI18nConfig = {\n  labels: DEFAULT_LABELS,\n  formats: DEFAULT_FORMATS,\n  functions: makeDefaultGanttFunctions({\n    labels: DEFAULT_LABELS,\n    formats: DEFAULT_FORMATS,\n  }),\n}\n\n\u002F** Deep-partial override shape: replace individual keys, never sections. *\u002F\ninterface GanttI18nOverrides {\n  labels?: Partial\u003COmit\u003CGanttI18nConfig[\"labels\"], \"scales\">> & {\n    scales?: Partial\u003CGanttI18nConfig[\"labels\"][\"scales\"]>\n  }\n  formats?: Partial\u003CGanttI18nConfig[\"formats\"]>\n  functions?: Partial\u003CGanttI18nConfig[\"functions\"]>\n}\n\n\u002F**\n * Shallow merge per nested object, matching the filters.tsx i18n contract:\n * a partial override replaces individual keys, never whole sections. Default\n * functions are re-bound to the MERGED labels\u002Fformats so a `formats` (or\n * `labels.continues`) override reaches the default renderers; explicit\n * `functions` overrides still win.\n *\u002F\nfunction mergeGanttI18n(overrides?: GanttI18nOverrides): GanttI18nConfig {\n  if (!overrides) return DEFAULT_GANTT_I18N\n  const labels = {\n    ...DEFAULT_LABELS,\n    ...overrides.labels,\n    \u002F\u002F nested section: replace individual scale names, never the whole set\n    scales: {\n      ...DEFAULT_LABELS.scales,\n      ...overrides.labels?.scales,\n    },\n  }\n  const formats = { ...DEFAULT_FORMATS, ...overrides.formats }\n  return {\n    labels,\n    formats,\n    functions: {\n      ...makeDefaultGanttFunctions({ labels, formats }),\n      ...overrides.functions,\n    },\n  }\n}\n\nexport { DEFAULT_GANTT_I18N, mergeGanttI18n }\nexport type { GanttI18nConfig, GanttI18nOverrides }\n",{"path":4891,"type":480,"target":4892,"content":4893},"src\u002Freui\u002Fgantt\u002Findex.ts","components\u002Freui\u002Fgantt\u002Findex.ts","\u002F\u002F Примечание: lib.ts и recurrence.ts не реэкспортируются целиком — это\n\u002F\u002F внутренняя календарная математика с непрефиксованными именами\n\u002F\u002F (buildEventIndex, MAX_OCCURRENCES, parseRRuleString и т.п.), которые\n\u002F\u002F коллизируют с одноимённым внутренним слоем reui\u002Fevent-calendar при общем\n\u002F\u002F барреле `export *` в packages\u002Fui\u002Fsrc\u002Findex.ts. Компоненты gantt импортируют\n\u002F\u002F их напрямую по относительному пути (\".\u002Flib\", \".\u002Frecurrence\").\nexport * from \".\u002Ftypes\"\nexport * from \".\u002Fi18n\"\nexport { default as Gantt } from \".\u002FGantt.vue\"\nexport { default as GanttBar } from \".\u002FGanttBar.vue\"\nexport { default as GanttNowLine } from \".\u002FGanttNowLine.vue\"\nexport { default as GanttNowDot } from \".\u002FGanttNowDot.vue\"\nexport { default as GanttCustomDragLayer } from \".\u002FGanttCustomDragLayer.vue\"\nexport { default as GanttOffscreenChips } from \".\u002FGanttOffscreenChips.vue\"\nexport { default as GanttTreeSplitPanes } from \".\u002FGanttTreeSplitPanes.vue\"\nexport { default as GanttTimelineHeader } from \".\u002FGanttTimelineHeader.vue\"\nexport { default as GanttZoomControl } from \".\u002FGanttZoomControl.vue\"\nexport { default as GanttTreeRow } from \".\u002FGanttTreeRow.vue\"\nexport { default as GanttTimelineRow } from \".\u002FGanttTimelineRow.vue\"\nexport { default as GanttView } from \".\u002FGanttView.vue\"\nexport { default as GanttNav } from \".\u002FGanttNav.vue\"\nexport { default as GanttNavToday } from \".\u002FGanttNavToday.vue\"\nexport { default as GanttNavPrev } from \".\u002FGanttNavPrev.vue\"\nexport { default as GanttNavNext } from \".\u002FGanttNavNext.vue\"\nexport { default as GanttTitle } from \".\u002FGanttTitle.vue\"\nexport { default as GanttScaleSwitcher } from \".\u002FGanttScaleSwitcher.vue\"\nexport { default as GanttDatePicker } from \".\u002FGanttDatePicker.vue\"\nexport { default as GanttToolbar } from \".\u002FGanttToolbar.vue\"\nexport * from \".\u002Fnav\"\nexport * from \".\u002Ftimeline-units\"\nexport * from \".\u002Fzoom\"\nexport * from \".\u002Fcontext\"\nexport * from \".\u002Fgestures\"\nexport * from \".\u002Fuse-now\"\n",{"path":4895,"type":480,"target":4896,"content":4897},"src\u002Freui\u002Fgantt\u002Flib.ts","components\u002Freui\u002Fgantt\u002Flib.ts","\u002F\u002F Title: Gantt Lib\n\u002F\u002F Description: Pure, framework-free calendar math: view ranges, zoned day keys, multi-day segmentation, overlap packing, lane packing, and the event index.\n\nimport { expandRecurrence } from \".\u002Frecurrence\"\nimport type {\n  GanttDateRange,\n  GanttEvent,\n  GanttOccurrence,\n  GanttOffDaysConfig,\n  GanttResource,\n  GanttScale,\n  GanttSegment,\n} from \".\u002Ftypes\"\nimport { TZDate } from \"@date-fns\u002Ftz\"\nimport {\n  addDays,\n  addMonths,\n  addWeeks,\n  addYears,\n  differenceInMinutes,\n  format,\n  startOfDay,\n  startOfMonth,\n  startOfQuarter,\n  startOfWeek,\n  startOfYear,\n} from \"date-fns\"\n\ntype WeekStartsOn = 0 | 1 | 2 | 3 | 4 | 5 | 6\n\n\u002F**\n * Packing-effective minimum in minutes so tiny events do not stack invisibly.\n * It is a packing FOOTPRINT, not a render size: two schedules less than this\n * apart are treated as concurrent and split into separate lanes even though\n * their real ranges do not touch.\n *\u002F\nconst MIN_PACK_SLOT = 30\n\n\u002F** The instant re-expressed in the display time zone (TZDate extends Date). *\u002F\nfunction toZoned(date: Date, timeZone: string): TZDate {\n  return new TZDate(date.getTime(), timeZone)\n}\n\n\u002F** Zoned midnight of the day containing the instant. *\u002F\nfunction zonedStartOfDay(date: Date, timeZone: string): TZDate {\n  return startOfDay(toZoned(date, timeZone))\n}\n\n\u002F** Stable per-day key in the display time zone. *\u002F\nfunction getDayKey(date: Date, timeZone: string): string {\n  return format(toZoned(date, timeZone), \"yyyy-MM-dd\")\n}\n\n\u002F** Day length in minutes; 1380\u002F1500 on DST transition days - never assume 1440. *\u002F\nfunction getDayTotalMinutes(dayStart: Date, timeZone: string): number {\n  const next = zonedStartOfDay(\n    addDays(toZoned(dayStart, timeZone), 1),\n    timeZone\n  )\n  return differenceInMinutes(next, dayStart)\n}\n\nfunction snapMinutes(minutes: number, snap: number): number {\n  return Math.round(minutes \u002F snap) * snap\n}\n\ninterface ViewRangeOptions {\n  timeZone: string\n  weekStartsOn: WeekStartsOn\n}\n\ninterface ViewDateRanges {\n  visibleRange: GanttDateRange\n  activeRange: GanttDateRange\n}\n\n\u002F** Axis range for the anchor date at the given scale. *\u002F\nfunction getGanttDateRange(\n  scale: GanttScale,\n  date: Date,\n  opts: ViewRangeOptions\n): ViewDateRanges {\n  const { timeZone, weekStartsOn } = opts\n  const zoned = toZoned(date, timeZone)\n\n  if (scale === \"week\") {\n    const start = startOfWeek(zoned, { weekStartsOn })\n    const range = { start, end: addWeeks(start, 1) }\n    return { activeRange: range, visibleRange: range }\n  }\n  if (scale === \"month\") {\n    \u002F\u002F exact month: no outside days on the horizontal axis\n    const start = startOfMonth(zoned)\n    const range = { start, end: startOfMonth(addMonths(zoned, 1)) }\n    return { activeRange: range, visibleRange: range }\n  }\n  if (scale === \"quarter\") {\n    \u002F\u002F week-aligned so the axis partitions into uniform week units\n    const quarterStart = startOfQuarter(zoned)\n    const quarterEnd = startOfQuarter(addMonths(zoned, 3))\n    const start = startOfWeek(quarterStart, { weekStartsOn })\n    let end = startOfWeek(quarterEnd, { weekStartsOn })\n    if (end \u003C quarterEnd) end = addWeeks(end, 1)\n    return {\n      activeRange: { start: quarterStart, end: quarterEnd },\n      visibleRange: { start, end },\n    }\n  }\n  if (scale === \"year\") {\n    const start = startOfYear(zoned)\n    const range = { start, end: startOfYear(addYears(zoned, 1)) }\n    return { activeRange: range, visibleRange: range }\n  }\n  const start = startOfDay(zoned)\n  const range = { start, end: addDays(start, 1) }\n  return { activeRange: range, visibleRange: range }\n}\n\n\u002F** The anchor date stepped one period forward or backward for the scale. *\u002F\nfunction stepGanttDate(\n  scale: GanttScale,\n  date: Date,\n  direction: 1 | -1,\n  opts: Pick\u003CViewRangeOptions, \"timeZone\">\n): Date {\n  const zoned = toZoned(date, opts.timeZone)\n  if (scale === \"week\") return addWeeks(zoned, direction)\n  if (scale === \"month\") return addMonths(zoned, direction)\n  if (scale === \"quarter\") return addMonths(zoned, direction * 3)\n  if (scale === \"year\") return addYears(zoned, direction)\n  return addDays(zoned, direction)\n}\n\nfunction rangesIntersect(a: GanttDateRange, b: GanttDateRange): boolean {\n  return a.start \u003C b.end && a.end > b.start\n}\n\nfunction eventsOverlap(\n  a: { start: Date; end: Date },\n  b: { start: Date; end: Date }\n): boolean {\n  return a.start \u003C b.end && a.end > b.start\n}\n\nfunction spansMultipleDays(occ: { start: Date; end: Date }): boolean {\n  \u002F\u002F An event ending exactly at the next midnight is still single-day\n  \u002F\u002F (exclusive end), so compare against a strictly-later instant.\n  return occ.end.getTime() - occ.start.getTime() > 24 * 60 * 60 * 1000\n}\n\n\u002F**\n * Identity of a schedule ACROSS time edits. `occurrence.key` embeds the start\n * instant, so it changes the moment a schedule is moved or start-resized -\n * useless as lane memory. This key survives the edit: the event id plus, for a\n * recurring series, the occurrence's position in it.\n *\u002F\nfunction getLaneKey(occurrence: {\n  eventId: string\n  recurrenceIndex?: number\n}): string {\n  return `${occurrence.eventId}::${occurrence.recurrenceIndex ?? 0}`\n}\n\n\u002F**\n * What one schedule held on the previous layout pass. The TIMES are what make\n * this more than a lane number: they are how the packer tells the schedule the\n * user just edited apart from the ones that merely sat still.\n *\u002F\ninterface GanttLaneMemo {\n  lane: number\n  startMs: number\n  endMs: number\n}\n\ninterface PackOptions {\n  \u002F**\n   * Where each schedule sat on the previous pass, by getLaneKey.\n   *\n   * A schedule whose times are UNCHANGED keeps its lane if that lane is still\n   * free, so editing one schedule never re-indexes the ones around it. A\n   * schedule whose times CHANGED - the one the user just dragged or resized -\n   * deliberately forfeits its pin and re-seeks the lowest free lane. That is\n   * what makes the arrangement live rather than frozen: a schedule dragged\n   * onto its neighbours stacks DOWN into the first free lane, and one dragged\n   * clear of them comes back UP inline. Only the edited schedule moves.\n   *\u002F\n  preferredLanes?: Map\u003Cstring, GanttLaneMemo>\n  \u002F** \"single\" collapses the row to one track; see GanttScheduleMode. *\u002F\n  mode?: \"single\" | \"multiple\"\n}\n\n\u002F**\n * Overlap packing for one row's timed segments.\n * Mutates column\u002FcolumnCount\u002FcolumnSpan on the segments, in place.\n * z resolution happens at render: event.zIndex verbatim, else 10 + column.\n *\u002F\nfunction packTimedSegments\u003CTData>(\n  segments: GanttSegment\u003CTData>[],\n  options: PackOptions = {}\n): void {\n  if (segments.length === 0) return\n\n  if (options.mode === \"single\") {\n    \u002F\u002F one track: every schedule shares lane 0 and the row never grows\n    for (const seg of segments) {\n      seg.column = 0\n      seg.columnCount = 1\n      seg.columnSpan = 1\n    }\n    return\n  }\n\n  const preferredLanes = options.preferredLanes\n\n  type Working = {\n    seg: GanttSegment\u003CTData>\n    startMin: number\n    effEnd: number\n    lane: number\n    \u002F** The occupancy entry this item added, so a settle can take it back. *\u002F\n    interval?: { from: number; to: number }\n  }\n\n  const items: Working[] = segments\n    .map((seg) => {\n      const startMin = seg.startMin ?? 0\n      const endMin = seg.endMin ?? startMin\n      return {\n        seg,\n        startMin,\n        effEnd: Math.max(endMin, startMin + MIN_PACK_SLOT),\n        lane: -1,\n      }\n    })\n    .sort(\n      (a, b) =>\n        a.startMin - b.startMin ||\n        b.effEnd - b.startMin - (a.effEnd - a.startMin) ||\n        a.seg.occurrence.key.localeCompare(b.seg.occurrence.key)\n    )\n\n  \u002F\u002F Sweep into connected clusters\n  const clusters: Working[][] = []\n  let current: Working[] = []\n  let clusterEnd = -Infinity\n  for (const item of items) {\n    if (item.startMin >= clusterEnd) {\n      current = []\n      clusters.push(current)\n      clusterEnd = -Infinity\n    }\n    current.push(item)\n    clusterEnd = Math.max(clusterEnd, item.effEnd)\n  }\n\n  for (const cluster of clusters) {\n    \u002F\u002F Per-lane occupancy INTERVALS, not a single running end: pass 1 claims\n    \u002F\u002F remembered lanes out of time order, so a lane can be free before an\n    \u002F\u002F occupant and busy after it.\n    const laneIntervals: Array\u003CArray\u003C{ from: number; to: number }>> = []\n    const isFree = (lane: number, item: Working) =>\n      !(laneIntervals[lane] ?? []).some(\n        (iv) => iv.from \u003C item.effEnd && iv.to > item.startMin\n      )\n    const claim = (lane: number, item: Working) => {\n      while (laneIntervals.length \u003C= lane) laneIntervals.push([])\n      const interval = { from: item.startMin, to: item.effEnd }\n      laneIntervals[lane]!.push(interval)\n      item.lane = lane\n      item.interval = interval\n    }\n    const release = (item: Working) => {\n      const occupants = laneIntervals[item.lane] ?? []\n      const at = occupants.indexOf(item.interval!)\n      if (at >= 0) occupants.splice(at, 1)\n    }\n\n    \u002F\u002F pass 1: schedules that did not move keep the lane they had. The one the\n    \u002F\u002F user just edited is NOT pinned - its times differ from the memo, so it\n    \u002F\u002F falls through to pass 2 and re-seeks a lane against its new span.\n    const pending: Working[] = []\n    for (const item of cluster) {\n      const memo = preferredLanes?.get(getLaneKey(item.seg.occurrence))\n      const untouched =\n        memo !== undefined &&\n        memo.startMs === item.seg.occurrence.start.getTime() &&\n        memo.endMs === item.seg.occurrence.end.getTime()\n      if (untouched && memo.lane >= 0 && isFree(memo.lane, item)) {\n        claim(memo.lane, item)\n      } else {\n        pending.push(item)\n      }\n    }\n    \u002F\u002F pass 2: the rest take the lowest free lane - overlapping goes DOWN into\n    \u002F\u002F the first lane with room, fitting comes back UP to lane 0\n    for (const item of pending) {\n      let lane = 0\n      while (!isFree(lane, item)) lane++\n      claim(lane, item)\n    }\n\n    \u002F\u002F pass 3: nothing floats above an empty lane. A pin only survives while\n    \u002F\u002F something above it still needs the space - once the schedule that was\n    \u002F\u002F there moves away or is deleted, its neighbour settles down into the\n    \u002F\u002F gap. Without this a row keeps a permanently blank top lane and never\n    \u002F\u002F shrinks back. Settling in lane order, and only ever DOWNWARD into space\n    \u002F\u002F that is genuinely free, means two schedules can never trade places -\n    \u002F\u002F so an edit still moves at most the schedule it touched.\n    const byLane = [...cluster].sort(\n      (a, b) => a.lane - b.lane || a.startMin - b.startMin\n    )\n    for (const item of byLane) {\n      if (item.lane === 0) continue\n      let lane = 0\n      while (lane \u003C item.lane && !isFree(lane, item)) lane++\n      if (lane \u003C item.lane) {\n        release(item)\n        claim(lane, item)\n      }\n    }\n  }\n\n  \u002F\u002F Lane memory can leave holes (the schedule that held lane 0 was deleted or\n  \u002F\u002F moved away). Collapse the row's USED lanes onto 0..n-1: relative stacking\n  \u002F\u002F order survives, so nothing reshuffles, but the row cannot creep taller\n  \u002F\u002F than the lanes it actually needs.\n  const used = [...new Set(items.map((item) => item.lane))].sort(\n    (a, b) => a - b\n  )\n  const compacted = new Map(used.map((lane, index) => [lane, index]))\n  const columnCount = used.length\n  for (const item of items) {\n    item.lane = compacted.get(item.lane) ?? 0\n    item.seg.column = item.lane\n    item.seg.columnCount = columnCount\n  }\n\n  \u002F\u002F Partial-overlap expansion: widen rightward into free lanes\n  for (const cluster of clusters) {\n    for (const item of cluster) {\n      let span = 1\n      while (item.lane + span \u003C columnCount) {\n        const blocked = cluster.some(\n          (other) =>\n            other !== item &&\n            other.lane === item.lane + span &&\n            other.startMin \u003C item.effEnd &&\n            other.effEnd > item.startMin\n        )\n        if (blocked) break\n        span++\n      }\n      item.seg.columnSpan = span\n    }\n  }\n}\n\nfunction defaultEventOrder(a: GanttOccurrence, b: GanttOccurrence): number {\n  return (\n    a.start.getTime() - b.start.getTime() ||\n    b.end.getTime() -\n      b.start.getTime() -\n      (a.end.getTime() - a.start.getTime()) ||\n    a.key.localeCompare(b.key)\n  )\n}\n\ninterface BuildIndexOptions\u003CTData = unknown> {\n  timeZone: string\n  \u002F** Escape hatch for exotic recurrence: return the expanded occurrences. *\u002F\n  getOccurrences?: (\n    event: GanttEvent\u003CTData>,\n    range: GanttDateRange,\n    ctx: { timeZone: string }\n  ) => Array\u003C{ start: Date; end: Date }> | null | undefined\n  eventOrder?: (a: GanttOccurrence\u003CTData>, b: GanttOccurrence\u003CTData>) => number\n}\n\ninterface GanttIndex\u003CTData = unknown> {\n  occurrences: GanttOccurrence\u003CTData>[]\n}\n\nfunction buildEventIndex\u003CTData>(\n  events: GanttEvent\u003CTData>[],\n  visibleRange: GanttDateRange,\n  opts: BuildIndexOptions\u003CTData>\n): GanttIndex\u003CTData> {\n  const { timeZone } = opts\n  const order = opts.eventOrder ?? defaultEventOrder\n\n  \u002F\u002F RECURRENCE-ID override replacement: an event carrying recurringEventId +\n  \u002F\u002F originalStart is an edited single occurrence of that series. The parent's\n  \u002F\u002F expansion drops the replaced instant; the override renders as its own\n  \u002F\u002F occurrence through the normal path below.\n  const overrideTimes = new Map\u003Cstring, Set\u003Cnumber>>()\n  for (const event of events) {\n    if (!event.recurringEventId || !event.originalStart) continue\n    let times = overrideTimes.get(event.recurringEventId)\n    if (!times) overrideTimes.set(event.recurringEventId, (times = new Set()))\n    times.add(event.originalStart.getTime())\n  }\n\n  const occurrences: GanttOccurrence\u003CTData>[] = []\n  for (const event of events) {\n    const replaced = overrideTimes.get(event.id)\n    const custom = opts.getOccurrences?.(event, visibleRange, { timeZone })\n    if (custom) {\n      custom.forEach((occ, i) => {\n        if (replaced?.has(occ.start.getTime())) return\n        if (!rangesIntersect({ start: occ.start, end: occ.end }, visibleRange))\n          return\n        occurrences.push({\n          key: `${event.id}::${occ.start.toISOString()}`,\n          eventId: event.id,\n          event,\n          start: occ.start,\n          end: occ.end,\n          allDay: event.allDay ?? false,\n          isRecurring: true,\n          recurrenceIndex: i,\n        })\n      })\n      continue\n    }\n    const expanded = expandRecurrence(event, visibleRange, { timeZone })\n    occurrences.push(\n      ...(replaced\n        ? expanded.filter((occ) => !replaced.has(occ.start.getTime()))\n        : expanded)\n    )\n  }\n  occurrences.sort(order)\n  return { occurrences }\n}\n\n\u002F** Cache key for index memoization; cheap string compare. *\u002F\nfunction getRangeKey(range: GanttDateRange): string {\n  return `${range.start.getTime()}-${range.end.getTime()}`\n}\n\n\u002F** Depth-first flatten of the resource tree (parents included). *\u002F\nfunction flattenResources(\n  resources: GanttResource[],\n  depth = 0\n): Array\u003C{ resource: GanttResource; depth: number }> {\n  const rows: Array\u003C{ resource: GanttResource; depth: number }> = []\n  for (const resource of resources) {\n    rows.push({ resource, depth })\n    if (resource.children?.length) {\n      rows.push(...flattenResources(resource.children, depth + 1))\n    }\n  }\n  return rows\n}\n\n\u002F** Depth-first lookup of one node in the tree. *\u002F\nfunction findResource(\n  resources: GanttResource[],\n  id: string\n): GanttResource | null {\n  for (const resource of resources) {\n    if (resource.id === id) return resource\n    const found = resource.children?.length\n      ? findResource(resource.children, id)\n      : null\n    if (found) return found\n  }\n  return null\n}\n\n\u002F**\n * Pure tree move: removes `resourceId` from wherever it sits and reinserts it\n * under `parentId` (null = root) at `index`. Returns a new tree; the original\n * is untouched. Returns null for impossible moves (unknown ids, or dropping a\n * node into its own subtree).\n *\u002F\nfunction reorderResources(\n  resources: GanttResource[],\n  resourceId: string,\n  parentId: string | null,\n  index: number\n): GanttResource[] | null {\n  let moved: GanttResource | null = null\n\n  const strip = (nodes: GanttResource[]): GanttResource[] =>\n    nodes.flatMap((node) => {\n      if (node.id === resourceId) {\n        moved = node\n        return []\n      }\n      if (!node.children?.length) return [node]\n      return [{ ...node, children: strip(node.children) }]\n    })\n\n  const stripped = strip(resources)\n  if (!moved) return null\n\n  const contains = (node: GanttResource, id: string): boolean =>\n    node.id === id || !!node.children?.some((child) => contains(child, id))\n  if (parentId !== null && contains(moved, parentId)) return null\n\n  const insert = (nodes: GanttResource[]): GanttResource[] => {\n    if (parentId === null) {\n      const next = [...nodes]\n      next.splice(Math.min(Math.max(index, 0), next.length), 0, moved!)\n      return next\n    }\n    return nodes.map((node) => {\n      if (node.id === parentId) {\n        const children = [...(node.children ?? [])]\n        children.splice(\n          Math.min(Math.max(index, 0), children.length),\n          0,\n          moved!\n        )\n        return { ...node, children }\n      }\n      if (!node.children?.length) return node\n      return { ...node, children: insert(node.children) }\n    })\n  }\n\n  const next = insert(stripped)\n  \u002F\u002F unknown parentId: the node vanished - reject\n  if (parentId !== null) {\n    const flat = flattenResources(next)\n    if (!flat.some(({ resource }) => resource.id === resourceId)) return null\n  }\n  return next\n}\n\nconst DEFAULT_WEEKEND_DAYS = [0, 6]\n\n\u002F** Resolves whether a day is an off day (non-working) in the display zone. *\u002F\nfunction resolveOffDay(\n  day: Date,\n  timeZone: string,\n  config: boolean | GanttOffDaysConfig | undefined\n): boolean {\n  if (!config) return false\n  const resolved: GanttOffDaysConfig = config === true ? {} : config\n  const weekendDays = resolved.weekendDays ?? DEFAULT_WEEKEND_DAYS\n  const zoned = toZoned(day, timeZone)\n  if (weekendDays.includes(zoned.getDay())) return true\n  if (resolved.dates?.length) {\n    const key = getDayKey(day, timeZone)\n    if (resolved.dates.some((date) => getDayKey(date, timeZone) === key)) {\n      return true\n    }\n  }\n  return resolved.isOffDay?.(day) ?? false\n}\n\nexport {\n  buildEventIndex,\n  defaultEventOrder,\n  eventsOverlap,\n  findResource,\n  flattenResources,\n  getDayKey,\n  getDayTotalMinutes,\n  getGanttDateRange,\n  getLaneKey,\n  getRangeKey,\n  MIN_PACK_SLOT,\n  packTimedSegments,\n  rangesIntersect,\n  reorderResources,\n  resolveOffDay,\n  snapMinutes,\n  spansMultipleDays,\n  stepGanttDate,\n  toZoned,\n  zonedStartOfDay,\n}\nexport type {\n  BuildIndexOptions,\n  GanttIndex,\n  GanttLaneMemo,\n  PackOptions,\n  ViewDateRanges,\n  ViewRangeOptions,\n  WeekStartsOn,\n}\n",{"path":4899,"type":480,"target":4900,"content":4901},"src\u002Freui\u002Fgantt\u002Fnav.ts","components\u002Freui\u002Fgantt\u002Fnav.ts","\u002F\u002F Title: Gantt Nav shared bits\n\u002F\u002F Description: GANTT_SCALES + the nav button variant\u002Fsize composable, shared by every gantt-nav.tsx subcomponent.\n\nimport { computed, type ComputedRef } from \"vue\"\nimport { useGanttViewConfig } from \".\u002Fcontext\"\nimport type { GanttScale } from \".\u002Ftypes\"\n\nconst GANTT_SCALES: GanttScale[] = [\"day\", \"week\", \"month\", \"quarter\", \"year\"]\n\ninterface GanttNavButtonProps {\n  variant: \"ghost\" | \"outline\" | \"secondary\" | \"default\"\n  size: \"sm\" | \"default\"\n  iconSize: \"icon-sm\" | \"icon\"\n}\n\n\u002F** Configured nav button variant\u002Fsize (viewConfig.navButtonVariant\u002FSize). *\u002F\nfunction useGanttNavButtonProps\u003CTData = unknown>(): ComputedRef\u003CGanttNavButtonProps> {\n  const viewConfig = useGanttViewConfig\u003CTData>()\n  return computed(() => ({\n    variant: viewConfig.value.navButtonVariant,\n    size: viewConfig.value.navButtonSize,\n    iconSize: viewConfig.value.navButtonSize === \"sm\" ? \"icon-sm\" : \"icon\",\n  }))\n}\n\nexport { GANTT_SCALES, useGanttNavButtonProps }\n",{"path":4903,"type":480,"target":4904,"content":4905},"src\u002Freui\u002Fgantt\u002Frecurrence.ts","components\u002Freui\u002Fgantt\u002Frecurrence.ts","\u002F\u002F Title: Gantt Recurrence\n\u002F\u002F Description: RFC 5545 subset recurrence expansion for the event calendar - structured rules or raw RRULE strings, with a hard occurrence cap.\n\nimport type {\n  GanttDateRange,\n  GanttEvent,\n  GanttOccurrence,\n  GanttRecurrenceRule,\n  GanttWeekday,\n} from \".\u002Ftypes\"\nimport { TZDate } from \"@date-fns\u002Ftz\"\nimport { addDays, addMonths, addWeeks, addYears } from \"date-fns\"\n\n\u002F** Guard: max occurrences per event per expansion. *\u002F\nconst MAX_OCCURRENCES = 1000\n\nconst WEEKDAYS: GanttWeekday[] = [\"SU\", \"MO\", \"TU\", \"WE\", \"TH\", \"FR\", \"SA\"]\n\nclass GanttRecurrenceError extends Error {\n  constructor(part: string) {\n    super(\n      `Unsupported recurrence part: ${part}. Use the getOccurrences prop to plug a full RRULE engine for exotic rules.`\n    )\n    this.name = \"GanttRecurrenceError\"\n  }\n}\n\n\u002F**\n * Parses a raw RRULE line (with or without the \"RRULE:\" prefix) into the\n * structured subset. Pass the display time zone so a floating UNTIL\n * (no trailing Z) resolves there instead of in the runtime's local zone.\n *\u002F\nfunction parseRRuleString(\n  input: string,\n  timeZone?: string\n): GanttRecurrenceRule {\n  const body = input.trim().replace(\u002F^RRULE:\u002Fi, \"\")\n  const rule: Partial\u003CGanttRecurrenceRule> = {}\n\n  for (const pair of body.split(\";\")) {\n    if (!pair) continue\n    const [rawKey, rawValue] = pair.split(\"=\")\n    const key = rawKey?.toUpperCase()\n    const value = rawValue ?? \"\"\n\n    switch (key) {\n      case \"FREQ\": {\n        const freq = value.toLowerCase()\n        if (\n          freq !== \"daily\" &&\n          freq !== \"weekly\" &&\n          freq !== \"monthly\" &&\n          freq !== \"yearly\"\n        ) {\n          throw new GanttRecurrenceError(`FREQ=${value}`)\n        }\n        rule.freq = freq\n        break\n      }\n      case \"INTERVAL\":\n        rule.interval = Math.max(1, parseInt(value, 10) || 1)\n        break\n      case \"COUNT\":\n        rule.count = Math.max(1, parseInt(value, 10) || 1)\n        break\n      case \"UNTIL\":\n        rule.until = parseRRuleDate(value, timeZone)\n        break\n      case \"BYDAY\":\n        rule.byWeekday = value.split(\",\").map((token) => {\n          const match = \u002F^(-?\\d+)?(SU|MO|TU|WE|TH|FR|SA)$\u002F.exec(token.trim())\n          if (!match) throw new GanttRecurrenceError(`BYDAY=${token}`)\n          const day = match[2] as GanttWeekday\n          return match[1] ? { day, ordinal: parseInt(match[1], 10) } : day\n        })\n        break\n      case \"BYMONTHDAY\":\n        rule.byMonthDay = value.split(\",\").map((v) => parseInt(v, 10))\n        break\n      case \"BYMONTH\":\n        rule.byMonth = value.split(\",\").map((v) => parseInt(v, 10))\n        break\n      case \"WKST\": {\n        if (!WEEKDAYS.includes(value as GanttWeekday)) {\n          throw new GanttRecurrenceError(`WKST=${value}`)\n        }\n        rule.weekStart = value as GanttWeekday\n        break\n      }\n      default:\n        throw new GanttRecurrenceError(key ?? pair)\n    }\n  }\n\n  if (!rule.freq) throw new GanttRecurrenceError(\"missing FREQ\")\n  return rule as GanttRecurrenceRule\n}\n\nfunction parseRRuleDate(value: string, timeZone?: string): Date {\n  \u002F\u002F RFC 5545 basic formats: YYYYMMDD or YYYYMMDDTHHMMSS(Z)\n  const match = \u002F^(\\d{4})(\\d{2})(\\d{2})(?:T(\\d{2})(\\d{2})(\\d{2})(Z)?)?$\u002F.exec(\n    value\n  )\n  if (!match) throw new GanttRecurrenceError(`UNTIL=${value}`)\n  const [, y, m, d, hh = \"23\", mm = \"59\", ss = \"59\", z] = match\n  \u002F\u002F Floating (non-Z) boundaries resolve in the DISPLAY zone when known -\n  \u002F\u002F local-zone parsing would shift the series end per visitor machine.\n  const date =\n    !z && timeZone\n      ? new TZDate(+y!, +m! - 1, +d!, +hh, +mm, +ss, timeZone)\n      : new Date(`${y}-${m}-${d}T${hh}:${mm}:${ss}${z ? \"Z\" : \"\"}`)\n  if (Number.isNaN(date.getTime())) {\n    throw new GanttRecurrenceError(`UNTIL=${value}`)\n  }\n  return new Date(date.getTime())\n}\n\n\u002F** Serializes the structured subset back to an RRULE line (without prefix). *\u002F\nfunction formatRRuleString(rule: GanttRecurrenceRule): string {\n  const parts: string[] = [`FREQ=${rule.freq.toUpperCase()}`]\n  if (rule.interval && rule.interval > 1)\n    parts.push(`INTERVAL=${rule.interval}`)\n  if (rule.count) parts.push(`COUNT=${rule.count}`)\n  if (rule.until) {\n    const u = rule.until\n    const pad = (n: number) => String(n).padStart(2, \"0\")\n    parts.push(\n      `UNTIL=${u.getUTCFullYear()}${pad(u.getUTCMonth() + 1)}${pad(u.getUTCDate())}T${pad(u.getUTCHours())}${pad(u.getUTCMinutes())}${pad(u.getUTCSeconds())}Z`\n    )\n  }\n  if (rule.byWeekday?.length) {\n    parts.push(\n      `BYDAY=${rule.byWeekday\n        .map((d) => (typeof d === \"string\" ? d : `${d.ordinal}${d.day}`))\n        .join(\",\")}`\n    )\n  }\n  if (rule.byMonthDay?.length)\n    parts.push(`BYMONTHDAY=${rule.byMonthDay.join(\",\")}`)\n  if (rule.byMonth?.length) parts.push(`BYMONTH=${rule.byMonth.join(\",\")}`)\n  if (rule.weekStart) parts.push(`WKST=${rule.weekStart}`)\n  return parts.join(\";\")\n}\n\nfunction resolveRule(\n  recurrence: GanttRecurrenceRule | string,\n  timeZone?: string\n): GanttRecurrenceRule {\n  return typeof recurrence === \"string\"\n    ? parseRRuleString(recurrence, timeZone)\n    : recurrence\n}\n\n\u002F**\n * Expands one event into its occurrences intersecting the range.\n * Non-recurring events yield at most one occurrence. Recurrence iteration is\n * wall-time based in the display zone (DST-safe day\u002Fweek\u002Fmonth steps).\n *\n * Supported subset: FREQ daily\u002Fweekly\u002Fmonthly\u002Fyearly, INTERVAL, COUNT, UNTIL,\n * weekly BYDAY (no ordinals). Parsed-but-unimplemented filters (BYMONTHDAY,\n * BYMONTH, BYDAY outside weekly) throw a GanttRecurrenceError instead of\n * silently mis-expanding; plug the getOccurrences prop for a full engine.\n * WKST parses and round-trips; week emission is Sunday-anchored.\n *\n * exDates remove exactly-matching instants (after COUNT numbering,\n * Google-style: an exception still consumes its COUNT slot); rDates add extra\n * instants with the same duration. RECURRENCE-ID override replacement lives\n * in buildEventIndex, where the override event and its parent series meet.\n *\u002F\nfunction expandRecurrence\u003CTData>(\n  event: GanttEvent\u003CTData>,\n  range: GanttDateRange,\n  ctx: { timeZone: string }\n): GanttOccurrence\u003CTData>[] {\n  const allDay = event.allDay ?? false\n\n  if (!event.recurrence) {\n    if (event.start \u003C range.end && event.end > range.start) {\n      return [\n        {\n          key: `${event.id}::${event.start.toISOString()}`,\n          eventId: event.id,\n          event,\n          start: event.start,\n          end: event.end,\n          allDay,\n          isRecurring: false,\n        },\n      ]\n    }\n    return []\n  }\n\n  const rule = resolveRule(event.recurrence, ctx.timeZone)\n  \u002F\u002F Loud contract: silently ignoring a filter would emit WRONG occurrences.\n  if (rule.byMonthDay?.length) throw new GanttRecurrenceError(\"BYMONTHDAY\")\n  if (rule.byMonth?.length) throw new GanttRecurrenceError(\"BYMONTH\")\n  if (rule.byWeekday?.length && rule.freq !== \"weekly\") {\n    throw new GanttRecurrenceError(\"BYDAY outside FREQ=WEEKLY\")\n  }\n  const interval = Math.max(1, rule.interval ?? 1)\n  const durationMs = event.end.getTime() - event.start.getTime()\n  const zonedStart = new TZDate(event.start.getTime(), ctx.timeZone)\n  \u002F\u002F Excluded instants matched exactly; filtering happens at push time so an\n  \u002F\u002F exception still consumes its COUNT slot (Google-style numbering).\n  const exTimes = new Set((rule.exDates ?? []).map((d) => d.getTime()))\n\n  const weeklyDays: number[] | null =\n    rule.freq === \"weekly\" && rule.byWeekday?.length\n      ? rule.byWeekday.map((d) => {\n          if (typeof d !== \"string\") {\n            throw new GanttRecurrenceError(\n              \"BYDAY ordinal outside monthly\u002Fyearly\"\n            )\n          }\n          return WEEKDAYS.indexOf(d)\n        })\n      : null\n\n  const occurrences: GanttOccurrence\u003CTData>[] = []\n  let produced = 0\n  let index = 0\n  let cursor = zonedStart\n\n  const advance = (from: TZDate, steps: number): TZDate =>\n    rule.freq === \"daily\"\n      ? addDays(from, steps * interval)\n      : rule.freq === \"weekly\"\n        ? addWeeks(from, steps * interval)\n        : rule.freq === \"monthly\"\n          ? addMonths(from, steps * interval)\n          : addYears(from, steps * interval)\n\n  \u002F\u002F Fast-forward past periods entirely before the range: they produce\n  \u002F\u002F nothing and must not consume the occurrence cap (an old-enough daily\n  \u002F\u002F series would otherwise exhaust MAX_OCCURRENCES before reaching the\n  \u002F\u002F window and silently vanish). COUNT rules jump too: the skipped periods\n  \u002F\u002F are credited to `index`, which is what terminates the series, so the\n  \u002F\u002F count still ends it on exactly the right instant. Leaving them on full\n  \u002F\u002F iteration would hide any series whose count exceeds MAX_OCCURRENCES.\n  \u002F\u002F\n  \u002F\u002F Daily and weekly ONLY. Their step is a fixed wall-time length, so one jump\n  \u002F\u002F of N steps lands exactly where N single steps land. addMonths\u002FaddYears\n  \u002F\u002F CLAMP instead: a Jan 31 monthly anchor steps to Feb 28 and never returns to\n  \u002F\u002F the 31st, while a single jump from the anchor clamps at most once. Jumping\n  \u002F\u002F those would make the same occurrence render on a different day depending on\n  \u002F\u002F which window the viewer scrolled in from, so they always iterate.\n  const canFastForward = rule.freq === \"daily\" || rule.freq === \"weekly\"\n  \u002F\u002F weekly BYDAY emits across the cursor's whole Sunday week\n  const weekSlackMs = weeklyDays ? 6 * 86_400_000 : 0\n  \u002F\u002F divide by the LONGEST possible step so the jump can never overshoot\n  const maxStepMs =\n    (rule.freq === \"daily\"\n      ? 24\n      : rule.freq === \"weekly\"\n        ? 7 * 24\n        : rule.freq === \"monthly\"\n          ? 31 * 24\n          : 366 * 24) *\n      3_600_000 *\n      interval +\n    3_600_000\n  for (let pass = 0; canFastForward && pass \u003C 2; pass++) {\n    const gap =\n      range.start.getTime() - durationMs - weekSlackMs - cursor.getTime()\n    const skip = Math.floor(gap \u002F maxStepMs)\n    if (skip \u003C= 0) break\n    cursor = advance(cursor, skip)\n    index += skip * (weeklyDays ? weeklyDays.length : 1)\n  }\n  \u002F\u002F close the remainder step by step (bounded by the jump math)\n  let guard = 0\n  while (\n    guard++ \u003C 10_000 &&\n    !(rule.until && cursor.getTime() > rule.until.getTime()) &&\n    cursor.getTime() + durationMs + weekSlackMs \u003C range.start.getTime()\n  ) {\n    cursor = advance(cursor, 1)\n    index += weeklyDays ? weeklyDays.length : 1\n  }\n  \u002F\u002F The jump credits a whole week of selected days per skipped week, but full\n  \u002F\u002F iteration never counts the selected days that fall BEFORE the anchor\n  \u002F\u002F inside the anchor's own week. Drop them once so both paths number the\n  \u002F\u002F same instant identically (index counts occurrences at or after the\n  \u002F\u002F anchor, and only those).\n  if (weeklyDays && index > 0) {\n    index -= weeklyDays.filter((day) => day \u003C zonedStart.getDay()).length\n  }\n\n  const pushIfVisible = (rawStart: Date) => {\n    \u002F\u002F normalize to a plain instant so consumers never receive zone-carrying\n    \u002F\u002F TZDate instances (mixed-zone formatting bugs)\n    const start = new Date(rawStart.getTime())\n    if (exTimes.has(start.getTime())) return\n    const end = new Date(start.getTime() + durationMs)\n    if (start \u003C range.end && end > range.start) {\n      occurrences.push({\n        key: `${event.id}::${start.toISOString()}`,\n        eventId: event.id,\n        event,\n        start,\n        end,\n        allDay,\n        isRecurring: true,\n        recurrenceIndex: index,\n      })\n    }\n  }\n\n  while (produced \u003C MAX_OCCURRENCES) {\n    if (rule.until && cursor.getTime() > rule.until.getTime()) break\n    \u002F\u002F COUNT is series-absolute, so it reads `index` (the position in the\n    \u002F\u002F series, fast-forward included) rather than `produced` (emissions in\n    \u002F\u002F this loop, which MAX_OCCURRENCES caps).\n    if (rule.count !== undefined && index >= rule.count) break\n    \u002F\u002F Past the visible window with no count to honor - stop iterating. For\n    \u002F\u002F weekly BYDAY the WEEK START decides: selected days earlier in the\n    \u002F\u002F anchor's week can still fall before range.end.\n    const horizonMs = weeklyDays\n      ? addDays(cursor, -cursor.getDay()).getTime()\n      : cursor.getTime()\n    if (horizonMs >= range.end.getTime() && rule.count === undefined) {\n      break\n    }\n\n    if (rule.freq === \"weekly\" && weeklyDays) {\n      \u002F\u002F Emit each selected weekday within the cursor's week\n      for (let d = 0; d \u003C 7; d++) {\n        const candidate = addDays(cursor, d - cursor.getDay())\n        if (!weeklyDays.includes(candidate.getDay())) continue\n        if (candidate.getTime() \u003C zonedStart.getTime()) continue\n        if (rule.until && candidate.getTime() > rule.until.getTime()) continue\n        \u002F\u002F the cap is checked here too, or a week that crosses it mid-loop\n        \u002F\u002F still emits its remaining selected days\n        if (produced >= MAX_OCCURRENCES) break\n        if (rule.count !== undefined && index >= rule.count) break\n        pushIfVisible(candidate)\n        produced++\n        index++\n      }\n    } else {\n      pushIfVisible(cursor)\n      produced++\n      index++\n    }\n\n    cursor = advance(cursor, 1)\n  }\n\n  \u002F\u002F RDATE: extra instants join the set (deduped against generated starts and\n  \u002F\u002F exclusions) with the same wall-time duration. Sorted so direct consumers\n  \u002F\u002F still receive chronological order (buildEventIndex re-sorts regardless).\n  if (rule.rDates?.length) {\n    const seen = new Set(occurrences.map((o) => o.start.getTime()))\n    for (const rDate of rule.rDates) {\n      const start = new Date(rDate.getTime())\n      if (seen.has(start.getTime()) || exTimes.has(start.getTime())) continue\n      const end = new Date(start.getTime() + durationMs)\n      if (start >= range.end || end \u003C= range.start) continue\n      seen.add(start.getTime())\n      occurrences.push({\n        key: `${event.id}::${start.toISOString()}`,\n        eventId: event.id,\n        event,\n        start,\n        end,\n        allDay,\n        isRecurring: true,\n        \u002F\u002F keep counting past the generated instants: an RDATE with no index\n        \u002F\u002F would fall back to 0 and collide with the series' first occurrence\n        \u002F\u002F in any consumer that identifies an instance by its position\n        recurrenceIndex: index++,\n      })\n    }\n    occurrences.sort((a, b) => a.start.getTime() - b.start.getTime())\n  }\n\n  return occurrences\n}\n\nexport {\n  GanttRecurrenceError,\n  expandRecurrence,\n  formatRRuleString,\n  MAX_OCCURRENCES,\n  parseRRuleString,\n}\n",{"path":4907,"type":480,"target":4908,"content":4909},"src\u002Freui\u002Fgantt\u002Ftimeline-units.ts","components\u002Freui\u002Fgantt\u002Ftimeline-units.ts","\u002F\u002F Title: Gantt Timeline Units\n\u002F\u002F Description: Port of the header\u002Fgrid unit model from gantt-view.tsx (~lines 388-565): per-scale unit list (day\u002Fquarter\u002Fyear\u002Fweek\u002Fmonth) with weights, grouping sectors, and the default unit width.\n\nimport { computed, type ComputedRef } from \"vue\"\nimport { addDays, addMinutes, addMonths, format, getWeek, startOfMonth, startOfQuarter, startOfWeek } from \"date-fns\"\nimport { useGanttSettings, useGanttViewConfig } from \".\u002Fcontext\"\nimport { getDayKey, resolveOffDay, toZoned, zonedStartOfDay } from \".\u002Flib\"\nimport { useTodayKey } from \".\u002Fuse-now\"\nimport type { GanttDateRange, GanttScale } from \".\u002Ftypes\"\n\ninterface TimelineUnit {\n  key: string\n  label: string\n  ms: number\n  \u002F** Relative width share; uniform scales use 1 (year: days per month). *\u002F\n  weight: number\n  isToday?: boolean\n  isOff?: boolean\n}\n\ninterface TimelineGroup {\n  key: string\n  label: string\n  span: number\n}\n\ninterface GanttTimelineUnits {\n  units: TimelineUnit[]\n  groups: TimelineGroup[]\n  unitWidthRem: number\n}\n\n\u002F**\n * Header model: bottom row = units, top row = grouping sectors. Weights are\n * proportional to REAL duration (a 23h\u002F25h DST day differs from its\n * siblings), so weight-driven gridlines, ms-fraction bar geometry, and the\n * dnd pointer math all share one coordinate system.\n *\u002F\nfunction useGanttTimelineUnits(\n  scale: ComputedRef\u003CGanttScale>,\n  range: ComputedRef\u003CGanttDateRange>,\n  interval: ComputedRef\u003Cnumber>\n): ComputedRef\u003CGanttTimelineUnits> {\n  const settings = useGanttSettings()\n  const viewConfig = useGanttViewConfig()\n  const todayDayKey = useTodayKey(computed(() => settings.value.timeZone))\n\n  return computed\u003CGanttTimelineUnits>(() => {\n    const timeZone = settings.value.timeZone\n    const rangeEndMs = range.value.end.getTime()\n    const metrics = viewConfig.value.metrics\n    const units: TimelineUnit[] = []\n    const groups: TimelineGroup[] = []\n    const todayStartMs = zonedStartOfDay(new Date(), timeZone).getTime()\n\n    if (scale.value === \"day\") {\n      const labelFormat = interval.value % 60 === 0 ? settings.value.i18n.formats.timeGutter : \"h:mm\"\n      \u002F\u002F walk whole days: infinite scroll can extend the range past one day\n      let dayCursor = zonedStartOfDay(range.value.start, timeZone)\n      while (dayCursor.getTime() \u003C rangeEndMs) {\n        const zonedDay = toZoned(dayCursor, timeZone)\n        const nextDay = zonedStartOfDay(addDays(zonedDay, 1), timeZone)\n        const dayMinutes = (nextDay.getTime() - dayCursor.getTime()) \u002F 60000\n        const dayOff = resolveOffDay(dayCursor, timeZone, viewConfig.value.offDays ?? true)\n        let span = 0\n        for (let m = 0; m \u003C dayMinutes; m += interval.value) {\n          const time = addMinutes(zonedDay, m)\n          \u002F\u002F a DST day whose minutes don't divide evenly leaves a short final\n          \u002F\u002F unit; its weight must be its REAL share or bars drift\n          const weight = Math.min(interval.value, dayMinutes - m) \u002F interval.value\n          units.push({\n            key: `${getDayKey(dayCursor, timeZone)}-m${m}`,\n            label: format(time, labelFormat, { locale: settings.value.locale }),\n            ms: time.getTime(),\n            weight,\n            isOff: dayOff,\n          })\n          span += weight\n        }\n        groups.push({\n          key: getDayKey(dayCursor, timeZone),\n          label: format(zonedDay, settings.value.i18n.formats.dayTitle, { locale: settings.value.locale }),\n          span,\n        })\n        dayCursor = nextDay\n      }\n      return {\n        units,\n        groups,\n        unitWidthRem: metrics?.unitWidths?.day ?? Math.max(2.5, 5 * (interval.value \u002F 60)),\n      }\n    }\n\n    if (scale.value === \"quarter\") {\n      \u002F\u002F units are week-aligned weeks (lib aligns the range), groups are months\n      let cursor = zonedStartOfDay(range.value.start, timeZone)\n      while (cursor.getTime() \u003C rangeEndMs) {\n        const zoned = toZoned(cursor, timeZone)\n        const next = zonedStartOfDay(addDays(zoned, 7), timeZone)\n        \u002F\u002F real week duration \u002F nominal week: 1 except across DST changes\n        const weight = (next.getTime() - cursor.getTime()) \u002F (7 * 24 * 60 * 60000)\n        units.push({\n          key: getDayKey(cursor, timeZone),\n          label: format(zoned, \"MMM d\", { locale: settings.value.locale }),\n          ms: cursor.getTime(),\n          weight,\n          isToday: todayStartMs >= cursor.getTime() && todayStartMs \u003C next.getTime(),\n        })\n        const monthKey = format(zoned, \"yyyy-MM\")\n        const lastGroup = groups[groups.length - 1]\n        if (lastGroup && lastGroup.key === monthKey) {\n          lastGroup.span += weight\n        } else {\n          groups.push({ key: monthKey, label: format(zoned, \"MMMM\", { locale: settings.value.locale }), span: weight })\n        }\n        cursor = next\n      }\n      return { units, groups, unitWidthRem: metrics?.unitWidths?.quarter ?? 8 }\n    }\n\n    if (scale.value === \"year\") {\n      \u002F\u002F units are calendar months (weight = real duration), groups are quarters\n      let cursor: Date = startOfMonth(toZoned(range.value.start, timeZone))\n      while (cursor.getTime() \u003C rangeEndMs) {\n        const next = startOfMonth(addMonths(cursor, 1))\n        \u002F\u002F nominal-day units so a month reads ~30 wide; real ms keeps DST months true\n        const weight = (next.getTime() - cursor.getTime()) \u002F (24 * 60 * 60000)\n        units.push({\n          key: format(cursor, \"yyyy-MM\"),\n          label: format(cursor, \"MMM\", { locale: settings.value.locale }),\n          ms: cursor.getTime(),\n          weight,\n          isToday: todayStartMs >= cursor.getTime() && todayStartMs \u003C next.getTime(),\n        })\n        const quarterStart = startOfQuarter(cursor)\n        const quarterKey = format(quarterStart, \"yyyy-QQQ\")\n        const lastGroup = groups[groups.length - 1]\n        if (lastGroup && lastGroup.key === quarterKey) {\n          lastGroup.span += weight\n        } else {\n          groups.push({\n            key: quarterKey,\n            label: format(quarterStart, \"QQQ yyyy\", { locale: settings.value.locale }),\n            span: weight,\n          })\n        }\n        cursor = next\n      }\n      return { units, groups, unitWidthRem: metrics?.unitWidths?.year ?? 10 }\n    }\n\n    \u002F\u002F week\u002Fmonth: units are days, groups are ISO-ish weeks\n    let cursor = zonedStartOfDay(range.value.start, timeZone)\n    while (cursor.getTime() \u003C rangeEndMs) {\n      const zoned = toZoned(cursor, timeZone)\n      const nextDay = zonedStartOfDay(addDays(zoned, 1), timeZone)\n      \u002F\u002F real day duration \u002F 24h: 1 except the 23h\u002F25h DST days\n      const weight = (nextDay.getTime() - cursor.getTime()) \u002F (24 * 60 * 60000)\n      units.push({\n        key: getDayKey(cursor, timeZone),\n        label: format(zoned, \"EEE d\", { locale: settings.value.locale }),\n        ms: cursor.getTime(),\n        weight,\n        isToday: getDayKey(cursor, timeZone) === todayDayKey.value,\n        isOff: resolveOffDay(cursor, timeZone, viewConfig.value.offDays ?? true),\n      })\n      \u002F\u002F locale supplies firstWeekContainsDate so W-numbers match the locale's\n      \u002F\u002F week numbering (ISO in de\u002Ffr, US-style otherwise); the explicit\n      \u002F\u002F weekStartsOn keeps the number aligned with the rendered grid\n      const weekNumber = getWeek(zoned, { locale: settings.value.locale, weekStartsOn: settings.value.weekStartsOn })\n      \u002F\u002F key + label from the true week start: a range that begins midweek\n      \u002F\u002F must not split or mislabel its first group (incl. the Jan 1 week)\n      const weekStart = startOfWeek(zoned, { weekStartsOn: settings.value.weekStartsOn })\n      const weekKey = `w-${format(weekStart, \"yyyy-MM-dd\")}`\n      const lastGroup = groups[groups.length - 1]\n      if (lastGroup && lastGroup.key === weekKey) {\n        lastGroup.span += weight\n      } else {\n        groups.push({\n          key: weekKey,\n          label: `${settings.value.i18n.labels.week(weekNumber)} ${format(weekStart, \"MMM d\", { locale: settings.value.locale })} - ${format(addDays(weekStart, 6), \"d\", { locale: settings.value.locale })}`,\n          span: weight,\n        })\n      }\n      cursor = nextDay\n    }\n    return {\n      units,\n      groups,\n      unitWidthRem: metrics?.unitWidths?.[scale.value] ?? (scale.value === \"week\" ? 10 : 4),\n    }\n  })\n}\n\nexport { useGanttTimelineUnits }\nexport type { GanttTimelineUnits, TimelineGroup, TimelineUnit }\n",{"path":4911,"type":480,"target":4912,"content":4913},"src\u002Freui\u002Fgantt\u002Ftypes.ts","components\u002Freui\u002Fgantt\u002Ftypes.ts","\u002F\u002F Title: Gantt Types\n\u002F\u002F Description: Public TypeScript contract for the headless gantt: events, occurrences, segments, state, and callbacks.\n\ntype GanttBarId = string\n\n\u002F** Horizontal time scale of the gantt axis. *\u002F\ntype GanttScale = \"day\" | \"week\" | \"month\" | \"quarter\" | \"year\"\n\n\u002F** Proposal emitted when a timeline resource row is drag-reordered. *\u002F\ninterface GanttResourceReorder {\n  \u002F** The dragged resource id. *\u002F\n  resourceId: string\n  \u002F** New parent id, or null for the root level. *\u002F\n  parentId: string | null\n  \u002F** Insertion index among the new parent's children. *\u002F\n  index: number\n  \u002F** The full resource tree with the move applied (convenience). *\u002F\n  resources: GanttResource[]\n}\n\n\u002F**\n * How many schedules one tree node may hold at once.\n * - \"single\": one track. The node never grows a second lane and a gesture that\n *   would create a concurrent schedule is refused.\n * - \"multiple\": concurrent schedules stack into stable lanes and the row grows.\n *\u002F\ntype GanttScheduleMode = \"single\" | \"multiple\"\n\n\u002F**\n * Drop policy for a gesture that would overlap another schedule in the SAME\n * node. Policy only - overlapping data always renders.\n * - \"allow\" (default): the gesture commits as proposed.\n * - \"clamp\": the gesture stops at the neighbour's edge.\n * - \"reject\": the gesture is marked invalid and never commits.\n *\u002F\ntype GanttOverlapPolicy = \"allow\" | \"reject\" | \"clamp\"\n\n\u002F** Vertical placement of a row's content when the node holds several lanes. *\u002F\ntype GanttRowAlign = \"start\" | \"center\"\n\n\u002F**\n * One node of the gantt tree: a generic item that carries a title, consumer\n * columns, and zero or more schedules. It is not domain-bound - the same node\n * expresses a task (one schedule) or a resource lane (many). Nesting via\n * children renders as collapsible groups.\n *\u002F\ninterface GanttResource {\n  id: string\n  title: string\n  \u002F** Token or css color used for subtle row\u002Fcolumn accents. *\u002F\n  color?: string\n  \u002F** Per-node cardinality override; falls back to the view-level default. *\u002F\n  scheduleMode?: GanttScheduleMode\n  children?: GanttResource[]\n}\n\n\u002F** Preferred name for a tree node; `GanttResource` is the legacy alias. *\u002F\ntype GanttNode = GanttResource\n\ninterface GanttDateRange {\n  \u002F** Inclusive instant. *\u002F\n  start: Date\n  \u002F** Exclusive instant. *\u002F\n  end: Date\n}\n\ntype GanttWeekday = \"MO\" | \"TU\" | \"WE\" | \"TH\" | \"FR\" | \"SA\" | \"SU\"\n\ninterface GanttRecurrenceRule {\n  freq: \"daily\" | \"weekly\" | \"monthly\" | \"yearly\"\n  interval?: number\n  count?: number\n  \u002F** Inclusive instant. *\u002F\n  until?: Date\n  byWeekday?: Array\u003CGanttWeekday | { day: GanttWeekday; ordinal: number }>\n  byMonthDay?: number[]\n  byMonth?: number[]\n  weekStart?: GanttWeekday\n  exDates?: Date[]\n  rDates?: Date[]\n}\n\ninterface GanttEvent\u003CTData = unknown> {\n  id: GanttBarId\n  title: string\n  \u002F** Plain instant; consumers parse ISO strings themselves. *\u002F\n  start: Date\n  \u002F** Exclusive; must be >= start. *\u002F\n  end: Date\n  allDay?: boolean\n  \u002F** Structured rule or a raw \"RRULE:...\" line. *\u002F\n  recurrence?: GanttRecurrenceRule | string\n  \u002F** This event is an edited single occurrence of that series. *\u002F\n  recurringEventId?: GanttBarId\n  \u002F** Which occurrence it replaces (RECURRENCE-ID semantics). *\u002F\n  originalStart?: Date\n  \u002F** Token or css color; flows to the --gantt-event-color css var. *\u002F\n  color?: string\n  \u002F** Excluded from drag and resize regardless of interactions state. *\u002F\n  readOnly?: boolean\n  \u002F** Per-event override; default comes from interactions.drag. *\u002F\n  draggable?: boolean\n  \u002F** Per-event override; default comes from interactions.resize. *\u002F\n  resizable?: boolean\n  \u002F** Packing prominence; feeds getEventPriority ordering. *\u002F\n  priority?: number\n  \u002F** Completion 0-100; renders as a subtle fill inside the bar. *\u002F\n  progress?: number\n  \u002F** Explicit stacking override; wins over the computed z. *\u002F\n  zIndex?: number\n  \u002F** Resource row this bar belongs to. *\u002F\n  resourceId?: string\n  \u002F** Consumer payload, fully generic. *\u002F\n  data?: TData\n}\n\ninterface GanttOccurrence\u003CTData = unknown> {\n  \u002F** Stable per instance: `${event.id}::${startISO}`. *\u002F\n  key: string\n  eventId: GanttBarId\n  event: GanttEvent\u003CTData>\n  start: Date\n  end: Date\n  allDay: boolean\n  isRecurring: boolean\n  recurrenceIndex?: number\n}\n\ninterface GanttSegment\u003CTData = unknown> {\n  occurrence: GanttOccurrence\u003CTData>\n  \u002F** Range-start reference instant of the segment's timeline slice. *\u002F\n  day: Date\n  isStart: boolean\n  isEnd: boolean\n  continuesBefore: boolean\n  continuesAfter: boolean\n  \u002F** Minutes from the visible range start, clamped to the range. *\u002F\n  startMin?: number\n  endMin?: number\n  \u002F** Row lane packing: 0-based lane index within the node's row. *\u002F\n  column?: number\n  \u002F** Lanes the node's row resolved to. *\u002F\n  columnCount?: number\n  columnSpan?: number\n}\n\ninterface GanttSelection {\n  eventKeys: string[]\n  \u002F** Committed slot selection; see GanttSlotDraft for the in-gesture value. *\u002F\n  slot: { start: Date; end: Date; allDay: boolean } | null\n}\n\ninterface GanttInteractions {\n  \u002F** Horizontal move within the bar's own row; never across rows. *\u002F\n  drag: boolean\n  resize: boolean\n  selectSlot: boolean\n}\n\ninterface GanttDragState\u003CTData = unknown> {\n  kind: \"move\" | \"resize-start\" | \"resize-end\"\n  occurrence: GanttOccurrence\u003CTData>\n  proposedStart: Date\n  proposedEnd: Date\n  proposedAllDay: boolean\n  \u002F** The bar's own resource; moves are x-axis only and never cross rows. *\u002F\n  proposedResourceId?: string\n  \u002F** Last canDropEvent verdict; drives data-drop-invalid styling. *\u002F\n  valid: boolean\n}\n\n\u002F**\n * The in-progress drag-create rectangle ONLY, cleared on commit or cancel.\n * The committed slot selection lives in GanttSelection.slot.\n *\u002F\ninterface GanttSlotDraft {\n  start: Date\n  end: Date\n  allDay: boolean\n  \u002F** Present when the slot was selected inside a resource row. *\u002F\n  resourceId?: string\n}\n\ninterface GanttState\u003CTData = unknown> {\n  \u002F** Horizontal axis scale. *\u002F\n  scale: GanttScale\n  \u002F** Anchor date. *\u002F\n  date: Date\n  \u002F** Full rendered axis range - fetch remote data for THIS. *\u002F\n  visibleRange: GanttDateRange\n  \u002F** The logical period (the month\u002Fweek itself). *\u002F\n  activeRange: GanttDateRange\n  events: GanttEvent\u003CTData>[]\n  selection: GanttSelection\n  interactions: GanttInteractions\n  loading: boolean\n  drag: GanttDragState\u003CTData> | null\n  slotDraft: GanttSlotDraft | null\n  \u002F**\n   * Instant at the center of the scrolled viewport; the nav title follows it\n   * so the header always names what you are looking at. null before the view\n   * reports a position (falls back to the anchor date).\n   *\u002F\n  viewportCenter: Date | null\n}\n\ninterface GanttRangeInfo {\n  range: GanttDateRange\n  activeRange: GanttDateRange\n  scale: GanttScale\n  date: Date\n  timeZone: string\n}\n\ninterface GanttProposedUpdate\u003CTData = unknown> {\n  event: GanttEvent\u003CTData>\n  \u002F** null when source === \"api\". *\u002F\n  occurrence: GanttOccurrence\u003CTData> | null\n  start: Date\n  end: Date\n  allDay: boolean\n  \u002F** The bar's own resource (moves stay in-row); set on create\u002Fapi. *\u002F\n  resourceId?: string\n  source: \"drag\" | \"resize-start\" | \"resize-end\" | \"keyboard\" | \"api\"\n}\n\n\u002F** false = reject\u002Frevert; void or true = accept; object = accept with adjustment. *\u002F\ntype GanttUpdateResult =\n  | boolean\n  | void\n  | { start?: Date; end?: Date; allDay?: boolean }\n\n\u002F** A click is a point, not a range; `end` is reserved for future gestures. *\u002F\ninterface GanttSlotInfo {\n  date: Date\n  end?: Date\n  allDay: boolean\n  \u002F** Present when the click happened inside a resource row. *\u002F\n  resourceId?: string\n}\n\n\u002F**\n * Off-day marking (non-working days). `true` uses the defaults: weekends\n * with a muted background. Custom weekday sets, explicit dates, a predicate,\n * and a custom class are all supported; marked cells carry `data-off` for\n * CSS-selector customization.\n *\u002F\ninterface GanttOffDaysConfig {\n  \u002F** Weekday numbers treated as off (0 = Sunday). Default [0, 6]. *\u002F\n  weekendDays?: number[]\n  \u002F** Additional explicit off dates (compared by day in the display zone). *\u002F\n  dates?: Date[]\n  \u002F** Full custom predicate; runs in addition to weekendDays\u002Fdates. *\u002F\n  isOffDay?: (day: Date) => boolean\n  \u002F** Marker classes; default \"bg-muted\u002F40\". *\u002F\n  className?: string\n}\n\n\u002F**\n * External-data contract. v1 ships the type plus docs recipes (Google\n * events.list \u002F MS Graph calendarView map to GanttEvent in ~15 lines);\n * OAuth, tokens, and sync loops are application backend territory.\n *\u002F\ninterface GanttDataAdapter\u003CTData = unknown> {\n  getEvents(\n    range: GanttDateRange,\n    signal?: AbortSignal\n  ): Promise\u003CGanttEvent\u003CTData>[]>\n}\n\nexport type {\n  GanttEvent,\n  GanttDataAdapter,\n  GanttDateRange,\n  GanttDragState,\n  GanttBarId,\n  GanttInteractions,\n  GanttNode,\n  GanttOccurrence,\n  GanttOffDaysConfig,\n  GanttOverlapPolicy,\n  GanttProposedUpdate,\n  GanttRangeInfo,\n  GanttRecurrenceRule,\n  GanttResource,\n  GanttRowAlign,\n  GanttScheduleMode,\n  GanttSegment,\n  GanttSelection,\n  GanttSlotDraft,\n  GanttSlotInfo,\n  GanttState,\n  GanttResourceReorder,\n  GanttScale,\n  GanttUpdateResult,\n  GanttWeekday,\n}\n",{"path":4915,"type":480,"target":4916,"content":4917},"src\u002Freui\u002Fgantt\u002Fuse-now.ts","components\u002Freui\u002Fgantt\u002Fuse-now.ts","\u002F**\n * Часть порта gantt-view.tsx (MIT) — `useNow`\u002F`useTodayKey`, вынесены в\n * отдельный файл, т.к. нужны нескольким компонентам\u002Fcomposable'ам\n * (GanttNowLine, GanttNowDot, useGanttTimelineUnits).\n *\u002F\nimport { onBeforeUnmount, onMounted, ref, watch, type ComputedRef, type Ref } from \"vue\"\nimport { getDayKey } from \".\u002Flib\"\n\n\u002F** Current time, refreshed on an interval and on tab focus. *\u002F\nfunction useNow(intervalMs = 30_000): Ref\u003CDate> {\n  const now = ref(new Date()) as Ref\u003CDate>\n  let id: ReturnType\u003Ctypeof setInterval> | undefined\n  const tick = () => {\n    now.value = new Date()\n  }\n  onMounted(() => {\n    id = setInterval(tick, intervalMs)\n    document.addEventListener(\"visibilitychange\", tick)\n    window.addEventListener(\"focus\", tick)\n  })\n  onBeforeUnmount(() => {\n    clearInterval(id)\n    document.removeEventListener(\"visibilitychange\", tick)\n    window.removeEventListener(\"focus\", tick)\n  })\n  return now\n}\n\n\u002F**\n * Today's zoned day key, re-rendering only at the midnight rollover (and on\n * focus\u002Fvisibility) - the grid needs day granularity, not the 30s now tick.\n *\u002F\nfunction useTodayKey(timeZone: ComputedRef\u003Cstring> | Ref\u003Cstring>): Ref\u003Cstring> {\n  const key = ref(getDayKey(new Date(), timeZone.value)) as Ref\u003Cstring>\n  let id: ReturnType\u003Ctypeof setInterval> | undefined\n  const tick = () => {\n    const next = getDayKey(new Date(), timeZone.value)\n    if (next !== key.value) key.value = next\n  }\n  onMounted(() => {\n    tick()\n    id = setInterval(tick, 60_000)\n    document.addEventListener(\"visibilitychange\", tick)\n    window.addEventListener(\"focus\", tick)\n  })\n  onBeforeUnmount(() => {\n    clearInterval(id)\n    document.removeEventListener(\"visibilitychange\", tick)\n    window.removeEventListener(\"focus\", tick)\n  })\n  watch(timeZone, tick)\n  return key\n}\n\nexport { useNow, useTodayKey }\n",{"path":4919,"type":480,"target":4920,"content":4921},"src\u002Freui\u002Fgantt\u002Fview-lib.ts","components\u002Freui\u002Fgantt\u002Fview-lib.ts","\u002F\u002F Title: Gantt View Lib\n\u002F\u002F Description: Pure, framework-free helpers from gantt-view.tsx (lane packing preview, pointer-to-track math, pane\u002Fviewport DOM helpers, row geometry constants). Not re-exported from index.ts for the same reason as lib.ts (see index.ts header).\n\nimport type { GanttResource, GanttSegment } from \".\u002Ftypes\"\nimport { MIN_PACK_SLOT } from \".\u002Flib\"\n\n\u002F** One flattened tree row (a resource at its position in the expanded tree). *\u002F\ninterface TimelineRow {\n  resource: GanttResource\n  parentId: string | null\n  depth: number\n  isGroup: boolean\n  collapsed: boolean\n}\n\n\u002F** Per-row packed bars plus the extents the off-screen chips need. *\u002F\ninterface TimelineRowBars {\n  segments: GanttSegment[]\n  laneCount: number\n  \u002F** Lane a drag-create in flight would land on, or null when none is aimed at this row. *\u002F\n  draftLane: number | null\n  \u002F** Mode this row was packed under; the draft overlay must honour it. *\u002F\n  scheduleMode: \"single\" | \"multiple\"\n  heightRem: number\n  \u002F** Gap above the first bar; equal to every other gap in the row. *\u002F\n  laneOffsetRem: number\n  \u002F** The band the FIRST schedule occupies, its gaps included. *\u002F\n  bandRem: number\n  \u002F** Envelope of all bars, as track fractions; null when the row is empty. *\u002F\n  extent: {\n    from: number\n    to: number\n    color?: string\n    label: string\n    \u002F** First bar start, for the jump-chip tooltip. *\u002F\n    startMs: number\n  } | null\n  \u002F** Parent rollup: descendant-bar envelope + duration-weighted progress, present only on group rows without bars of their own. *\u002F\n  summary: { from: number; to: number; progress: number | null } | null\n}\n\n\u002F** One unit's cumulative start\u002Fwidth, as fractions of the whole track. *\u002F\ninterface UnitFraction {\n  unit: { ms: number }\n  start: number\n  width: number\n}\n\n\u002F** Cumulative start\u002Fwidth fractions per unit, for backdrop stripes\u002Flines\u002Fhint snapping. *\u002F\nfunction computeUnitFractions\u003CTUnit extends { weight: number; ms: number }>(\n  units: TUnit[]\n): Array\u003C{ unit: TUnit; start: number; width: number }> {\n  const totalWeight = units.reduce((sum, unit) => sum + unit.weight, 0)\n  let acc = 0\n  return units.map((unit) => {\n    const start = acc \u002F totalWeight\n    acc += unit.weight\n    return { unit, start, width: unit.weight \u002F totalWeight }\n  })\n}\n\n\u002F** Snap a track fraction to its unit (hint preview target). *\u002F\nfunction createHintStopResolver(unitFractions: UnitFraction[]) {\n  return (fraction: number): { index: number; center: number; ms: number; endMs: number } | null => {\n    for (let i = 0; i \u003C unitFractions.length; i++) {\n      const { unit, start, width } = unitFractions[i]!\n      if (fraction \u003C start + width || i === unitFractions.length - 1) {\n        return {\n          index: i,\n          center: start + width \u002F 2,\n          ms: unit.ms,\n          endMs: unitFractions[i + 1]?.unit.ms ?? unit.ms,\n        }\n      }\n    }\n    return null\n  }\n}\n\n\u002F**\n * Row geometry is three numbers: a bar is LANE_HEIGHT_REM tall, stacked bars\n * are separated by LANE_GAP_REM, and the block as a whole is inset from the\n * row's edges by ROW_PADDING_REM. Padding and gap are deliberately NOT the\n * same value - schedules in one node belong together, so they sit tight, while\n * the row still needs real breathing room above and below. Every inter-lane\n * gap is identical, which is what keeps a stacked row reading evenly.\n *\u002F\nconst LANE_HEIGHT_REM = 1.25\nconst LANE_GAP_REM = 0.1875\nconst ROW_PADDING_REM = 0.5\n\u002F** Drop-indicator height (h-5); it is centered inside its lane band. *\u002F\nconst GHOST_HEIGHT_REM = 1.25\n\u002F** Bars narrower than this flip their title outside in barLabel \"auto\". *\u002F\nconst AUTO_LABEL_MIN_REM = 7\n\n\u002F**\n * Lowest lane free for [startMs, endMs) among a row's segments, padded by\n * MIN_PACK_SLOT exactly as packTimedSegments pads its own occupancy test.\n * Comparing raw instants instead lets a sub-slot bar read as clear, so an\n * affordance would promise a lane the packer then refuses.\n *\n * Shared by the hover hint and the drag placeholder on purpose: two copies of\n * this is how the ring and the range it paints end up on different tracks.\n *\u002F\nfunction lowestFreeLane(segments: GanttSegment[], startMs: number, endMs: number): number {\n  const padMs = MIN_PACK_SLOT * 60000\n  const to = Math.max(endMs, startMs + padMs)\n  const busy = new Set\u003Cnumber>()\n  for (const segment of segments) {\n    const segStart = segment.occurrence.start.getTime()\n    const segEnd = Math.max(segment.occurrence.end.getTime(), segStart + padMs)\n    if (segStart \u003C to && segEnd > startMs) busy.add(segment.column ?? 0)\n  }\n  let lane = 0\n  while (busy.has(lane)) lane += 1\n  return lane\n}\n\n\u002F**\n * Pointer x resolved against the element's TIME axis: the 0..1 fraction and\n * the same measurement in CSS pixels from the axis start. Mirrored in RTL,\n * where the range start renders at the element's right edge. One rect read\n * and one style read, because this runs on every pointer move.\n *\u002F\nfunction trackPoint(el: HTMLElement, clientX: number): { fraction: number; offset: number } {\n  const rect = el.getBoundingClientRect()\n  const rtl = getComputedStyle(el).direction === \"rtl\"\n  const offset = rtl ? rect.right - clientX : clientX - rect.left\n  \u002F\u002F `offset` is exact and drives the time maths. `snapped` is the same value\n  \u002F\u002F biased so that rect start + snapped lands on a WHOLE viewport pixel: the\n  \u002F\u002F row's own edge routinely sits on a half pixel, so rounding the offset\n  \u002F\u002F alone still puts anything placed at it between two pixels.\n  const snapped = rtl ? rect.right - Math.round(clientX) : Math.round(clientX) - rect.left\n  return {\n    fraction: rect.width > 0 ? offset \u002F rect.width : 0,\n    offset: snapped,\n  }\n}\n\n\u002F** Fraction only, for the call sites that do not place anything. *\u002F\nfunction trackFraction(el: HTMLElement, clientX: number): number {\n  return trackPoint(el, clientX).fraction\n}\n\n\u002F** The pane's scrollable viewport (custom ScrollArea or native host). *\u002F\nfunction getPaneViewport(pane: HTMLElement | null): HTMLElement | null {\n  return pane?.querySelector\u003CHTMLElement>(\"[data-slot=scroll-area-viewport]\") ?? null\n}\n\n\u002F** Distance scrolled from the inline-start edge (RTL reports negative). *\u002F\nfunction getScrollStart(viewport: HTMLElement): number {\n  return Math.abs(viewport.scrollLeft)\n}\n\n\u002F** Write a distance-from-inline-start back as a signed scrollLeft. *\u002F\nfunction setScrollStart(viewport: HTMLElement, value: number) {\n  viewport.scrollLeft = getComputedStyle(viewport).direction === \"rtl\" ? -value : value\n}\n\nexport {\n  AUTO_LABEL_MIN_REM,\n  GHOST_HEIGHT_REM,\n  LANE_GAP_REM,\n  LANE_HEIGHT_REM,\n  ROW_PADDING_REM,\n  computeUnitFractions,\n  createHintStopResolver,\n  getPaneViewport,\n  getScrollStart,\n  lowestFreeLane,\n  setScrollStart,\n  trackFraction,\n  trackPoint,\n}\nexport type { TimelineRow, TimelineRowBars, UnitFraction }\n",{"path":4923,"type":480,"target":4924,"content":4925},"src\u002Freui\u002Fgantt\u002Fzoom.ts","components\u002Freui\u002Fgantt\u002Fzoom.ts","\u002F\u002F Title: Gantt Zoom\n\u002F\u002F Description: Port of the zoom state slice from gantt-view.tsx (~lines 567-584, 2409-2449): controlled\u002Funcontrolled zoom multiplier, clamped by zoomRange, plus the in\u002Fout step used by the floating zoom control.\n\nimport { computed, ref, type ComputedRef, type Ref } from \"vue\"\nimport { useGanttViewConfig } from \".\u002Fcontext\"\n\nconst DEFAULT_ZOOM_RANGE = { min: 0.5, max: 3, step: 0.25 }\n\ninterface GanttZoom {\n  zoom: ComputedRef\u003Cnumber>\n  canZoomIn: ComputedRef\u003Cboolean>\n  canZoomOut: ComputedRef\u003Cboolean>\n  zoomIn: () => void\n  zoomOut: () => void\n  \u002F** Set an arbitrary (already-computed) target, clamped to zoomRange. Used by wheel\u002Fpinch zoom, which steps continuously rather than by `zoomRange.step`. *\u002F\n  setZoom: (next: number) => void\n}\n\n\u002F**\n * Re-anchoring the viewport across a zoom step (`anchorZoomCenter`\u002F\n * `anchorZoomPointer`\u002Fwheel-zoom, gantt-view.tsx ~1490-1622) lives in\n * `GanttTreeSplitPanes.vue` now (it owns the real scroll axis element,\n * `[data-gantt-axis]`, and the `pendingRestore`\u002F`seat()` contract that\n * consumes it) - see that file's header. This composable only owns the\n * multiplier's value\u002Fclamp\u002Fstep math, same as before.\n *\u002F\nfunction useGanttZoom\u003CTData = unknown>(): GanttZoom {\n  const viewConfig = useGanttViewConfig\u003CTData>()\n  const zoomRange = computed(() => ({ ...DEFAULT_ZOOM_RANGE, ...viewConfig.value.zoomRange }))\n  const clampZoom = (value: number) => Math.min(Math.max(value, zoomRange.value.min), zoomRange.value.max)\n\n  const internalZoom = ref(viewConfig.value.defaultZoom ?? 1) as Ref\u003Cnumber>\n  const zoom = computed(() => clampZoom(viewConfig.value.zoom ?? internalZoom.value))\n\n  const setZoomValue = (next: number) => {\n    const clamped = clampZoom(next)\n    if (viewConfig.value.zoom === undefined) internalZoom.value = clamped\n    viewConfig.value.onZoomChange?.(clamped)\n  }\n\n  return {\n    zoom,\n    canZoomIn: computed(() => zoom.value \u003C zoomRange.value.max - 1e-9),\n    canZoomOut: computed(() => zoom.value > zoomRange.value.min + 1e-9),\n    zoomIn: () => setZoomValue(+(zoom.value + (zoomRange.value.step ?? 0.25)).toFixed(2)),\n    zoomOut: () => setZoomValue(+(zoom.value - (zoomRange.value.step ?? 0.25)).toFixed(2)),\n    setZoom: setZoomValue,\n  }\n}\n\nexport { useGanttZoom, DEFAULT_ZOOM_RANGE }\nexport type { GanttZoom }\n",{"source":483},1785737589312]