@elcrm/form 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +162 -204
- package/dist/Form.d.ts +21 -3
- package/dist/assets/styles/Check.module.css +1 -1
- package/dist/assets/styles/Code.module.css +1 -1
- package/dist/assets/styles/Color.module.css +1 -1
- package/dist/assets/styles/Drag.module.css +1 -1
- package/dist/assets/styles/Field.module.css +1 -1
- package/dist/assets/styles/FieldGroup.module.css +1 -0
- package/dist/assets/styles/File.module.css +1 -1
- package/dist/assets/styles/Form.module.css +1 -1
- package/dist/assets/styles/Radio.module.css +1 -1
- package/dist/assets/styles/Rating.module.css +1 -1
- package/dist/assets/styles/Rich.module.css +1 -1
- package/dist/assets/styles/Select.module.css +1 -1
- package/dist/assets/styles/Tabs.module.css +1 -0
- package/dist/assets/styles/Tags.module.css +1 -1
- package/dist/assets/styles/Time.module.css +1 -1
- package/dist/core/Field.d.ts +38 -10
- package/dist/core/Field.js +2 -2
- package/dist/dark.css +34 -11
- package/dist/fields/Card.d.ts +26 -0
- package/dist/fields/Card.js +1 -0
- package/dist/fields/Check.d.ts +20 -2
- package/dist/fields/Check.js +1 -1
- package/dist/fields/Code.d.ts +26 -4
- package/dist/fields/Code.js +1 -1
- package/dist/fields/Color.d.ts +21 -13
- package/dist/fields/Color.js +1 -1
- package/dist/fields/ColorPanel.d.ts +20 -0
- package/dist/fields/ColorPanel.js +1 -0
- package/dist/fields/Date.d.ts +16 -2
- package/dist/fields/Date.js +1 -1
- package/dist/fields/DateCalendar.d.ts +26 -0
- package/dist/fields/DateCalendar.js +1 -0
- package/dist/fields/Display.d.ts +15 -5
- package/dist/fields/Display.js +2 -1
- package/dist/fields/DragDrop.d.ts +27 -4
- package/dist/fields/DragDrop.js +1 -1
- package/dist/fields/Email.d.ts +18 -3
- package/dist/fields/Email.js +1 -1
- package/dist/fields/FieldGroup.d.ts +127 -0
- package/dist/fields/FieldGroup.js +1 -0
- package/dist/fields/File.d.ts +14 -1
- package/dist/fields/File.js +1 -1
- package/dist/fields/Hidden.d.ts +7 -2
- package/dist/fields/Hidden.js +1 -1
- package/dist/fields/Input.d.ts +10 -5
- package/dist/fields/Input.js +1 -1
- package/dist/fields/Mask.d.ts +14 -1
- package/dist/fields/Mask.js +1 -1
- package/dist/fields/Modal.d.ts +20 -3
- package/dist/fields/Modal.js +1 -1
- package/dist/fields/Money.d.ts +12 -1
- package/dist/fields/Money.js +1 -1
- package/dist/fields/NativeTextField.d.ts +9 -5
- package/dist/fields/NativeTextField.js +1 -1
- package/dist/fields/Numeric.d.ts +10 -8
- package/dist/fields/Numeric.js +1 -1
- package/dist/fields/Options.d.ts +24 -5
- package/dist/fields/Options.js +1 -1
- package/dist/fields/Password.d.ts +11 -0
- package/dist/fields/Password.js +1 -1
- package/dist/fields/Percent.d.ts +10 -6
- package/dist/fields/Percent.js +1 -1
- package/dist/fields/Phone.d.ts +13 -1
- package/dist/fields/Phone.js +1 -1
- package/dist/fields/Radio.d.ts +17 -1
- package/dist/fields/Radio.js +1 -1
- package/dist/fields/Range.d.ts +19 -6
- package/dist/fields/Range.js +1 -1
- package/dist/fields/Rating.d.ts +13 -1
- package/dist/fields/Rating.js +1 -1
- package/dist/fields/RichText.d.ts +10 -2
- package/dist/fields/RichText.js +1 -1
- package/dist/fields/Select.d.ts +21 -0
- package/dist/fields/Select.js +1 -1
- package/dist/fields/Tabs.d.ts +45 -0
- package/dist/fields/Tabs.js +1 -0
- package/dist/fields/Tags.d.ts +13 -1
- package/dist/fields/Tags.js +1 -1
- package/dist/fields/Textarea.d.ts +10 -1
- package/dist/fields/Textarea.js +1 -1
- package/dist/fields/Time.d.ts +13 -4
- package/dist/fields/Time.js +1 -1
- package/dist/fields/TimePicker.d.ts +24 -0
- package/dist/fields/TimePicker.js +1 -0
- package/dist/fields/Url.d.ts +11 -1
- package/dist/fields/cardUtils.d.ts +37 -0
- package/dist/fields/cardUtils.js +1 -0
- package/dist/fields/colorUtils.d.ts +29 -0
- package/dist/fields/colorUtils.js +1 -0
- package/dist/fields/dateUtils.d.ts +32 -0
- package/dist/fields/dateUtils.js +1 -0
- package/dist/fields/emailUtils.d.ts +4 -0
- package/dist/fields/emailUtils.js +1 -0
- package/dist/fields/timeUtils.d.ts +23 -0
- package/dist/fields/timeUtils.js +1 -0
- package/dist/fields/type.d.ts +97 -26
- package/dist/hooks/use.d.ts +32 -13
- package/dist/index.d.ts +21 -9
- package/dist/index.js +2 -2
- package/dist/index.umd.js +3 -2
- package/dist/light.css +35 -12
- package/dist/package.js +1 -1
- package/dist/style.css +17 -13
- package/dist/styles/Code.module.js +1 -1
- package/dist/styles/Color.module.js +1 -1
- package/dist/styles/Field.module.js +1 -1
- package/dist/styles/FieldGroup.module.js +1 -0
- package/dist/styles/Tabs.module.js +1 -0
- package/package.json +1 -1
package/dist/fields/Rating.d.ts
CHANGED
|
@@ -1,10 +1,22 @@
|
|
|
1
1
|
import { TInput } from './type';
|
|
2
|
-
/**
|
|
2
|
+
/**
|
|
3
|
+
* Оценка звёздами.
|
|
4
|
+
*
|
|
5
|
+
* **В форме:** `number` `0…max` (`0` = пусто).
|
|
6
|
+
*
|
|
7
|
+
* **Элементы:** капсула → `role="slider"` + кнопки звёзд. Стрелки меняют значение.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* <RatingField name="rate" form={form} label="Оценка" max={5} allowClear />
|
|
11
|
+
*/
|
|
3
12
|
export type TRating = TInput & {
|
|
4
13
|
max?: number;
|
|
5
14
|
/** Разрешить сброс повторным кликом по текущей оценке */
|
|
6
15
|
allowClear?: boolean;
|
|
7
16
|
};
|
|
17
|
+
/**
|
|
18
|
+
* Звёздный рейтинг.
|
|
19
|
+
*/
|
|
8
20
|
declare function RatingField({ value, form, onValue, onBlur: onBlurField, name, placeholder, label, error, hidden, disabled, className, after, before, max, allowClear, }: TRating): import("react/jsx-runtime").JSX.Element | null;
|
|
9
21
|
declare namespace RatingField {
|
|
10
22
|
var displayName: string;
|
package/dist/fields/Rating.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import e from"../styles/Field.module.js";import t from"../core/Field.js";import{useFormFieldState as n}from"../hooks/use.js";import r from"../styles/Rating.module.js";import
|
|
1
|
+
import e from"../styles/Field.module.js";import t from"../core/Field.js";import{useFormFieldState as n}from"../hooks/use.js";import{createFieldEmit as r}from"./emit.js";import i from"../styles/Rating.module.js";import a from"react";import{jsx as o,jsxs as s}from"react/jsx-runtime";function c(e,t){return Number.isFinite(e)?Math.min(t,Math.max(0,Math.round(e))):0}function l({value:l=0,form:u,onValue:d,onBlur:f,name:p=``,placeholder:m=``,label:h=``,error:g=``,hidden:_=!1,disabled:v=!1,className:y=``,after:b=``,before:x=``,max:S=5,allowClear:C=!0}){let{value:w}=n(u,p,l),T=c(Number(w),S),E=!v,[D,O]=a.useState(0),k=e=>{r(u,p,d)(e,!0)};if(_&&!T)return null;let A=D||T;return s(t,{type:`rating`,label:h,error:g,element_blok:{className:[y||``]},element_input:{className:[i.r],props:{"data-disabled":v||!E?`true`:void 0}},children:[x?o(`span`,{className:e.a,children:x}):null,o(`div`,{role:`slider`,"aria-label":h||m||`Оценка`,"aria-valuemin":0,"aria-valuemax":S,"aria-valuenow":T,tabIndex:E?0:-1,onMouseLeave:()=>O(0),onBlur:()=>{O(0),f?.({value:T,name:p})},onKeyDown:e=>{if(!E)return;let t=T;if(e.key===`ArrowRight`||e.key===`ArrowUp`)t=Math.min(S,T+1);else if(e.key===`ArrowLeft`||e.key===`ArrowDown`)t=Math.max(0,T-1);else if(e.key===`Home`)t=0;else if(e.key===`End`)t=S;else return;e.preventDefault(),k(t)},children:Array.from({length:S},(e,t)=>{let n=t+1;return o(`button`,{type:`button`,className:n<=A?`${i.s} ${i.o}`:i.s,"aria-label":`${n} из ${S}`,disabled:!E,onMouseEnter:()=>E&&O(n),onClick:()=>{E&&k(C&&T===n?0:n)},children:`★`},n)})}),b?o(`span`,{className:e.a,children:b}):null]})}l.displayName=`RatingField`;export{l as default};
|
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
import { TInput } from './type';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
3
|
+
* Простой WYSIWYG (`document.execCommand`). **В форме:** HTML-строка.
|
|
4
|
+
*
|
|
5
|
+
* **Элементы:** капсула как textarea → опциональный `toolbar` (B/I/U/списки) + редактор.
|
|
6
|
+
* Санитизацию HTML делайте в приложении.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* <RichTextField name="bio" form={form} label="Описание" toolbar />
|
|
5
10
|
*/
|
|
6
11
|
export type TRichText = TInput & {
|
|
7
12
|
/** Показать панель форматирования */
|
|
8
13
|
toolbar?: boolean;
|
|
9
14
|
};
|
|
15
|
+
/**
|
|
16
|
+
* Rich-текст. Значение — HTML, не plain text.
|
|
17
|
+
*/
|
|
10
18
|
declare function RichTextField({ value, form, onValue, onBlur: onBlurField, name, placeholder, label, error, hidden, disabled, className, after, before, maxLength, spellCheck, toolbar, }: TRichText): import("react/jsx-runtime").JSX.Element | null;
|
|
11
19
|
declare namespace RichTextField {
|
|
12
20
|
var displayName: string;
|
package/dist/fields/RichText.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import e from"../styles/Field.module.js";import t from"../core/Field.js";import{useFormFieldState as n}from"../hooks/use.js";import{
|
|
1
|
+
import e from"../styles/Field.module.js";import t from"../core/Field.js";import{useFormFieldState as n}from"../hooks/use.js";import{createFieldEmit as r}from"./emit.js";import{isEmptyTextLike as i}from"./visibility.js";import a from"../styles/Rich.module.js";import o from"react";import{jsx as s,jsxs as c}from"react/jsx-runtime";function l({value:l=``,form:u,onValue:d,onBlur:f,name:p=``,placeholder:m=``,label:h=``,error:g=``,hidden:_=!1,disabled:v=!1,className:y=``,after:b=``,before:x=``,maxLength:S,spellCheck:C=!0,toolbar:w=!0}){let{value:T,version:E}=n(u,p,l),D=!v,O=typeof T==`string`?T:``,k=o.useRef(null),A=o.useRef(!1);o.useLayoutEffect(()=>{if(A.current)return;let e=k.current;e&&e.innerHTML!==O&&(e.innerHTML=O||``)},[O,E]);let j=r(u,p,d),M=(e,t=!1)=>{S&&e.replace(/<[^>]+>/g,``).length>S||j(e,t)},N=e=>{if(D){k.current?.focus();try{document.execCommand(e,!1)}catch{}M(k.current?.innerHTML||``)}};if(_&&i(O.replace(/<[^>]+>/g,``)))return null;let P={};return m&&!O&&(P.placeholder=m),D&&(P.edit=``),c(t,{type:`richtext`,label:h,error:g,element_blok:{className:[y||``]},element_input:{className:[a.r],props:{"data-disabled":v||!D?`true`:void 0}},children:[x?s(`span`,{className:e.a,children:x}):null,c(`div`,{className:a.w,children:[w?c(`div`,{className:a.t,role:`toolbar`,children:[s(`button`,{type:`button`,className:a.b,disabled:!D,"aria-label":`Жирный`,onMouseDown:e=>e.preventDefault(),onClick:()=>N(`bold`),children:`B`}),s(`button`,{type:`button`,className:a.b,disabled:!D,"aria-label":`Курсив`,onMouseDown:e=>e.preventDefault(),onClick:()=>N(`italic`),children:s(`em`,{children:`I`})}),s(`button`,{type:`button`,className:a.b,disabled:!D,"aria-label":`Подчёркивание`,onMouseDown:e=>e.preventDefault(),onClick:()=>N(`underline`),children:s(`u`,{children:`U`})}),s(`button`,{type:`button`,className:a.b,disabled:!D,"aria-label":`Маркированный список`,onMouseDown:e=>e.preventDefault(),onClick:()=>N(`insertUnorderedList`),children:`•`}),s(`button`,{type:`button`,className:a.b,disabled:!D,"aria-label":`Нумерованный список`,onMouseDown:e=>e.preventDefault(),onClick:()=>N(`insertOrderedList`),children:`1.`})]}):null,s(`div`,{ref:k,className:a.e,contentEditable:D,suppressContentEditableWarning:!0,spellCheck:C,role:`textbox`,"aria-multiline":!0,"aria-label":h||m||`Текст`,tabIndex:D?0:-1,onFocus:()=>{A.current=!0},onInput:()=>M(k.current?.innerHTML||``),onBlur:()=>{A.current=!1;let e=k.current?.innerHTML||``;M(e,!0),f?.({value:e,name:p})},...P})]}),b?s(`span`,{className:e.a,children:b}):null]})}l.displayName=`RichTextField`;export{l as default};
|
package/dist/fields/Select.d.ts
CHANGED
|
@@ -11,10 +11,31 @@ type SelectOptionArrayItem = {
|
|
|
11
11
|
s?: number;
|
|
12
12
|
};
|
|
13
13
|
type SelectOptions = SelectOptionObject | SelectOptionArrayItem[];
|
|
14
|
+
/**
|
|
15
|
+
* Выпадающий список (портал в `document.body`).
|
|
16
|
+
*
|
|
17
|
+
* **В форме:** `number` (id опции; `0` часто = «не выбрано»).
|
|
18
|
+
*
|
|
19
|
+
* **Элементы:** капсула-`combobox` + список `listbox` (li = option).
|
|
20
|
+
* Формат опций: `{ [id]: { n: "Подпись", s: 1 } }` или `[{ i, n, s }]`.
|
|
21
|
+
* `s === 0` — скрыть пункт. `order` — порядок id.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* const ROLES = { 1: { n: "Админ", s: 1 }, 2: { n: "Юзер", s: 1 } };
|
|
25
|
+
* <SelectField name="role" form={form} label="Роль" options={ROLES} placeholder="Выберите" />
|
|
26
|
+
*/
|
|
14
27
|
export type TSelect = TInput & {
|
|
28
|
+
/**
|
|
29
|
+
* Пункты: объект `{ id: { n, s } }` или массив `{ id|i, n, s? }`.
|
|
30
|
+
* `n` — текст, `s` — видимость (`0` = скрыт).
|
|
31
|
+
*/
|
|
15
32
|
options?: SelectOptions;
|
|
33
|
+
/** Порядок ключей, если `options` — объект. */
|
|
16
34
|
order?: number[];
|
|
17
35
|
};
|
|
36
|
+
/**
|
|
37
|
+
* Селект: клик по капсуле открывает список, привязанный к полю при скролле.
|
|
38
|
+
*/
|
|
18
39
|
declare function SelectField({ value, form, onValue, name, placeholder, label, error, hidden, disabled, className, after, before, options, order, size, }: TSelect): import("react/jsx-runtime").JSX.Element | "";
|
|
19
40
|
declare namespace SelectField {
|
|
20
41
|
var displayName: string;
|
package/dist/fields/Select.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import e from"../styles/Field.module.js";import t from"../core/Field.js";import{useFormFieldState as n}from"../hooks/use.js";import{acquireOverlayZIndex as
|
|
1
|
+
import e from"../styles/Field.module.js";import t from"../core/Field.js";import{useFormFieldState as n}from"../hooks/use.js";import{createFieldEmit as r}from"./emit.js";import{acquireOverlayZIndex as i,releaseOverlayZIndex as a}from"../overlayZ.js";import o from"../styles/Select.module.js";import s,{useEffect as c,useState as l}from"react";import{Fragment as u,jsx as d,jsxs as f}from"react/jsx-runtime";import{createPortal as p}from"react-dom";function m({value:i=0,form:a,onValue:l,name:m=``,placeholder:h=``,label:_,error:v,hidden:y=!1,disabled:b=!1,className:x,after:S=``,before:C=``,options:w,order:T,size:E=`m`}){let[D,O]=s.useState(!1),[k,A]=s.useState(0),[j,M]=s.useState(null),N=s.useRef(null),P=s.useId(),F=s.useCallback(()=>{let e=N.current;if(!e)return;let t=e.getBoundingClientRect();M(e=>e&&e.top===t.top&&e.left===t.left&&e.width===t.width&&e.height===t.height&&e.bottom===t.bottom?e:t)},[]);c(()=>{if(!D)return;F();let e=()=>F();return window.addEventListener(`scroll`,e,!0),window.addEventListener(`resize`,e),()=>{window.removeEventListener(`scroll`,e,!0),window.removeEventListener(`resize`,e)}},[D,F]);let{value:I}=n(a,m,i??0),L=Number(I),R=[];w&&(R=Array.isArray(w)?w.map(e=>({id:e.id??e.i??0,label:e.n||``,hidden:e.s===0})).filter(e=>e.id!==0&&e.label.length>0):Object.keys(w).map(e=>{let t=Number(e),n=w[t];return{id:t,label:n?.n||``,hidden:n?.s===0}}).filter(e=>e.label.length>0));let z=(()=>{if(!T||T.length===0)return R;let e=new Map(R.map(e=>[e.id,e]));return T.map(t=>e.get(t)).filter(e=>!!e)})().filter(e=>!e.hidden),B=e=>z.find(t=>t.id===e)?.label,V=D&&z.length>0?`${P}-opt-${k}`:void 0,H=e=>{r(a,m,l)(e,!0)},U=e=>{if(z.length===0||b)return;let t=z.findIndex(e=>e.id===L);N.current=e;let n=e.getBoundingClientRect(),r=t>=0?t:0;M(n),A(r),O(!0)},W=e=>{U(e.currentTarget)},G=e=>{if(!b&&z.length!==0){if(!D&&(e.key===`ArrowDown`||e.key===`Enter`||e.key===` `)){e.preventDefault(),U(e.currentTarget);return}if(D){if(e.key===`Escape`){e.preventDefault(),O(!1);return}if(e.key===`ArrowDown`){e.preventDefault(),A(e=>(e+1)%z.length);return}if(e.key===`ArrowUp`){e.preventDefault(),A(e=>e-1<0?z.length-1:e-1);return}if(e.key===`Enter`){e.preventDefault();let t=z[k];t&&H(t.id),O(!1)}}}},K={};h&&(K.placeholder=h);let q=B(L),J=(q===void 0||q===``)&&!!h,Y=L===0||B(L)===void 0;return y&&Y?``:f(u,{children:[f(t,{type:`select`,label:_,element_blok:{className:[x||``]},error:v,size:E,element_input:{className:[e.w,o.f],props:{onClick:W,onKeyDown:G,tabIndex:b?-1:0,role:`combobox`,"aria-expanded":D,"aria-haspopup":`listbox`,"aria-controls":P,...V?{"aria-activedescendant":V}:{},"aria-disabled":b?!0:void 0,"data-disabled":b?`true`:void 0}},children:[C?d(`span`,{className:e.a,children:C}):null,d(`div`,{...K,className:[e.x,J?o.p:``].filter(Boolean).join(` `),children:q??h??``}),S?d(`span`,{className:e.a,children:S}):null]}),D&&p(d(g,{parent:j||void 0,onValue:H,className:x,items:z,setOpen:O,listId:P,activeIndex:k,currentValue:L}),document.body)]})}m.displayName=`SelectField`;var h=s.memo(m),g=({onValue:e,parent:t,className:n,items:r,setOpen:s,listId:p,activeIndex:m,currentValue:h})=>{let[g]=l(()=>i());if(c(()=>()=>a(g),[g]),!t)return null;let v=()=>{s(!1)},y=t=>{s(!1),e(t)},b=Math.min(300,Math.max(52,r.length*52+8)),x=window.innerHeight-t.bottom-6-8,S=t.top-6-8,C=x<Math.min(b,160)&&S>x,w=Math.max(t.width,260),T=Math.min(Math.max(8,t.left),window.innerWidth-w-8),E=C?t.top-b-6:t.bottom+6;return E=Math.min(E,window.innerHeight-b-8),E=Math.max(8,E),f(u,{children:[d(`div`,{className:o.o,style:{zIndex:g},onClick:v,"aria-hidden":!0}),d(`div`,{className:[o.l,n].filter(e=>e!==void 0).join(` `),style:{top:`${E}px`,left:T+`px`,width:w,zIndex:g+1},children:d(_,{listId:p,items:r,link:y,activeIndex:m,currentValue:h})})]})},_=({listId:e,items:t,link:n,activeIndex:r,currentValue:i})=>d(`ul`,{id:e,role:`listbox`,children:t.map((t,a)=>d(`li`,{id:`${e}-opt-${a}`,role:`option`,"aria-selected":i===t.id,"data-focus":r===a?`true`:`false`,onClick:()=>n(t.id),children:t.label},t.id))});export{h as default};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import { TInput } from './type';
|
|
3
|
+
type TabsOptionObject = Record<string | number, {
|
|
4
|
+
n: string;
|
|
5
|
+
s?: number;
|
|
6
|
+
icon?: ReactNode;
|
|
7
|
+
}>;
|
|
8
|
+
type TabsOptionArrayItem = {
|
|
9
|
+
id?: string | number;
|
|
10
|
+
i?: string | number;
|
|
11
|
+
n: string;
|
|
12
|
+
/** `0` — сегмент виден, но недоступен (приглушён) */
|
|
13
|
+
s?: number;
|
|
14
|
+
/** Иконка / точка слева от подписи */
|
|
15
|
+
icon?: ReactNode;
|
|
16
|
+
};
|
|
17
|
+
type TabsOptions = TabsOptionObject | TabsOptionArrayItem[];
|
|
18
|
+
/**
|
|
19
|
+
* Сегменты (вкладки-переключатель).
|
|
20
|
+
*
|
|
21
|
+
* **В форме:** `string | number` (id активного сегмента).
|
|
22
|
+
*
|
|
23
|
+
* **Элементы:** капсула без внутреннего padding → кнопки-сегменты.
|
|
24
|
+
* `options[].icon` — слот слева в сегменте. `s === 0` — сегмент виден, но disabled.
|
|
25
|
+
* `equal` — равная ширина (по умолчанию true).
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* <TabsField name="tab" form={form} options={[{ i: "a", n: "А" }, { i: "b", n: "Б" }]} />
|
|
29
|
+
*/
|
|
30
|
+
export type TTabs = TInput & {
|
|
31
|
+
options?: TabsOptions;
|
|
32
|
+
/**
|
|
33
|
+
* Равная ширина сегментов (по умолчанию `true`).
|
|
34
|
+
* `false` — ширина по содержимому.
|
|
35
|
+
*/
|
|
36
|
+
equal?: boolean;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Сегмент-контрол. Фокус на сегменте, не на всей капсуле.
|
|
40
|
+
*/
|
|
41
|
+
declare function TabsField({ value, form, onValue, onBlur: onBlurField, name, placeholder, label, error, hidden, disabled, className, after, before, options, equal, size, }: TTabs): import("react/jsx-runtime").JSX.Element | null;
|
|
42
|
+
declare namespace TabsField {
|
|
43
|
+
var displayName: string;
|
|
44
|
+
}
|
|
45
|
+
export default TabsField;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import e from"../styles/Field.module.js";import t from"../core/Field.js";import{useFormFieldState as n}from"../hooks/use.js";import{createFieldEmit as r}from"./emit.js";import i from"../styles/Tabs.module.js";import"react";import{jsx as a,jsxs as o}from"react/jsx-runtime";function s(e){return e?Array.isArray(e)?e.map(e=>({id:e.id??e.i??``,label:e.n||``,disabled:e.s===0,icon:e.icon})).filter(e=>e.label.length>0&&e.id!==``):Object.keys(e).map(t=>{let n=e[t],r=Number(t);return{id:Number.isFinite(r)&&String(r)===t?r:t,label:n?.n||``,disabled:n?.s===0,icon:n?.icon}}).filter(e=>e.label.length>0):[]}function c(e,t){return e===t?!0:String(e)===String(t)}function l({value:l=``,form:u,onValue:d,onBlur:f,name:p=``,placeholder:m=``,label:h=``,error:g=``,hidden:_=!1,disabled:v=!1,className:y=``,after:b=``,before:x=``,options:S,equal:C=!0,size:w}){let{value:T}=n(u,p,l),E=!v,D=s(S),O=e=>{r(u,p,d)(e,!0)};return _&&(T==null||T===``)?null:o(t,{type:`tabs`,label:h,error:g,size:w,element_blok:{className:[y||``]},element_input:{className:[i.r],props:{"data-disabled":v||!E?`true`:void 0}},children:[x?a(`span`,{className:e.a,children:x}):null,a(`div`,{className:i.i,role:`tablist`,"aria-label":h||m||`Вкладки`,style:C?void 0:{justifyContent:`flex-start`},children:D.map(e=>{let t=c(T,e.id),n=v||!E||e.disabled;return o(`button`,{type:`button`,role:`tab`,"aria-selected":t,"aria-disabled":n||void 0,disabled:n,className:[i.t,t?i.a:``].filter(Boolean).join(` `),style:C?void 0:{flex:`0 0 auto`},onClick:()=>{n||O(e.id)},onBlur:()=>{!f||!p||f({value:u?u.getValue(p,T):T,name:p})},children:[e.icon==null?null:a(`span`,{className:i.o,"aria-hidden":!0,children:e.icon}),a(`span`,{className:i.l,children:e.label})]},String(e.id))})}),b?a(`span`,{className:e.a,children:b}):null]})}l.displayName=`TabsField`;export{l as default};
|
package/dist/fields/Tags.d.ts
CHANGED
|
@@ -1,10 +1,22 @@
|
|
|
1
1
|
import { TInput } from './type';
|
|
2
|
-
/**
|
|
2
|
+
/**
|
|
3
|
+
* Чипы / теги.
|
|
4
|
+
*
|
|
5
|
+
* **В форме:** `string[]`.
|
|
6
|
+
*
|
|
7
|
+
* **Элементы:** капсула → чипы + однострочный ввод. Enter / `separators` создают чип.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* <TagsField name="tags" form={form} label="Метки" maxTags={8} />
|
|
11
|
+
*/
|
|
3
12
|
export type TTags = TInput & {
|
|
4
13
|
maxTags?: number;
|
|
5
14
|
/** Разделители при вводе (по умолчанию Enter / запятая) */
|
|
6
15
|
separators?: string[];
|
|
7
16
|
};
|
|
17
|
+
/**
|
|
18
|
+
* Поле тегов. Backspace на пустом вводе удаляет последний чип.
|
|
19
|
+
*/
|
|
8
20
|
declare function TagsField({ value, form, onValue, onBlur: onBlurField, name, placeholder, label, error, hidden, disabled, className, after, before, maxTags, maxLength, separators, }: TTags): import("react/jsx-runtime").JSX.Element | null;
|
|
9
21
|
declare namespace TagsField {
|
|
10
22
|
var displayName: string;
|
package/dist/fields/Tags.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import e from"../styles/Field.module.js";import t from"../core/Field.js";import{useFormFieldState as n}from"../hooks/use.js";import r from"../styles/Tags.module.js";import
|
|
1
|
+
import e from"../styles/Field.module.js";import t from"../core/Field.js";import{useFormFieldState as n}from"../hooks/use.js";import{createFieldEmit as r}from"./emit.js";import i from"../styles/Tags.module.js";import a from"react";import{jsx as o,jsxs as s}from"react/jsx-runtime";function c(e){return Array.isArray(e)?e.map(String).map(e=>e.trim()).filter(Boolean):typeof e==`string`&&e.trim()?e.split(`,`).map(e=>e.trim()).filter(Boolean):[]}function l({value:l=[],form:u,onValue:d,onBlur:f,name:p=``,placeholder:m=`Добавить…`,label:h=``,error:g=``,hidden:_=!1,disabled:v=!1,className:y=``,after:b=``,before:x=``,maxTags:S,maxLength:C,separators:w=[`,`,`Enter`]}){let{value:T}=n(u,p,l),E=c(T),D=!v,[O,k]=a.useState(``),A=a.useRef(null),j=e=>{r(u,p,d)(e,!0)},M=e=>{let t=e.trim();if(t&&!(C&&t.length>C)){if(E.some(e=>e.toLowerCase()===t.toLowerCase())){k(``);return}S!==void 0&&E.length>=S||(j([...E,t]),k(``))}},N=e=>{D&&j(E.filter((t,n)=>n!==e))},P=e=>{if(D){if(w.includes(e.key)||e.key===`,`&&w.includes(`,`)){e.preventDefault(),M(O);return}e.key===`Backspace`&&!O&&E.length>0&&(e.preventDefault(),N(E.length-1))}},F=E.length===0;return _&&F?null:s(t,{type:`tags`,label:h,error:g,element_blok:{className:[y||``]},element_input:{className:[i.r],props:{"data-disabled":v||!D?`true`:void 0,onClick:()=>A.current?.focus()}},children:[x?o(`span`,{className:e.a,children:x}):null,s(`div`,{className:i.i,children:[E.map((e,t)=>s(`span`,{className:i.c,children:[o(`span`,{className:i.l,children:e}),D?o(`button`,{type:`button`,className:i.m,"aria-label":`Удалить ${e}`,onClick:e=>{e.stopPropagation(),N(t)},children:`×`}):null]},`${e}-${t}`)),D&&(S===void 0||E.length<S)?o(`input`,{ref:A,className:i.n,value:O,placeholder:E.length===0?m:``,disabled:v,maxLength:C,"aria-label":h||m||`Теги`,onChange:e=>k(e.target.value),onKeyDown:P,onBlur:()=>{O.trim()&&M(O),f?.({value:E,name:p})}}):null]}),b?o(`span`,{className:e.a,children:b}):null]})}l.displayName=`TagsField`;export{l as default};
|
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
import { TInput } from './type';
|
|
2
|
-
/**
|
|
2
|
+
/**
|
|
3
|
+
* Многострочный текст (`contentEditable`).
|
|
4
|
+
*
|
|
5
|
+
* **В форме:** `string` (могут быть `\n`).
|
|
6
|
+
*
|
|
7
|
+
* **Элементы:** `label` + высокая капсула (`--field-note-height`) + `before` / `after`.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* <TextareaField name="comment" form={form} label="Комментарий" placeholder="Текст…" />
|
|
11
|
+
*/
|
|
3
12
|
declare function TextareaField({ value, form, onValue, name, placeholder, label, hidden, disabled, onBlur: onBlurField, className, after, before, error, size, inputmode, maxLength, spellCheck, }: TInput): import("react/jsx-runtime").JSX.Element | null;
|
|
4
13
|
declare namespace TextareaField {
|
|
5
14
|
var displayName: string;
|
package/dist/fields/Textarea.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import e from"../styles/Field.module.js";import t,{useFieldA11y as n}from"../core/Field.js";import{useFormFieldState as r}from"../hooks/use.js";import i,{asEditableClipboard as
|
|
1
|
+
import e from"../styles/Field.module.js";import t,{useFieldA11y as n}from"../core/Field.js";import{useFormFieldState as r}from"../hooks/use.js";import{createFieldEmit as i}from"./emit.js";import a,{asEditableClipboard as o}from"../events/onEvent.js";import{isEmptyTextLike as s}from"./visibility.js";import c from"react";import{jsx as l,jsxs as u}from"react/jsx-runtime";function d({value:n,form:d,onValue:p,name:m=``,placeholder:h=``,label:g=``,hidden:_=!1,disabled:v=!1,onBlur:y,className:b=``,after:x=``,before:S=``,error:C=``,size:w=`m`,inputmode:T=`text`,maxLength:E,spellCheck:D=!0}){let{value:O,version:k}=r(d,m||``,n),A=!v,j=String(O??``),M=c.useRef(null),N=c.useRef(!1);c.useLayoutEffect(()=>{if(N.current)return;let e=M.current;e&&e.innerText!==j&&(e.innerText=j)},[j,k]);let P=i(d,m,p),F=e=>{P(e.currentTarget.innerText)},I=e=>{let t=(e.currentTarget.textContent??``).trim();E&&t.length>=E&&e.key!==`Backspace`&&e.preventDefault(),e.key===`Enter`&&!e.shiftKey&&e.preventDefault()},L=e=>a.Paste(E||0,o(e));if(_&&s(O))return null;let R={};h&&(R.placeholder=h),A&&(R.edit=``),R.inputMode=T||`text`;let z=e=>{a.Blur(e),y?.({value:(e.currentTarget.textContent??``).trim(),name:m||``}),N.current=!1},B=()=>{N.current=!0};return u(t,{type:`note`,label:g,error:C,size:w,element_blok:{className:[b||``]},element_input:{className:[e.n],props:{"data-disabled":v||!A?`true`:void 0}},children:[S?l(`span`,{className:e.a,children:S}):null,l(f,{divRef:M,editable:A,label:g,placeholder:h,spellCheck:D,dd:R,onPaste:L,onInput:F,onFocus:B,onBlur:z,onKeyDown:I}),x?l(`span`,{className:e.a,children:x}):null]})}function f({divRef:e,editable:t,label:r,placeholder:i,spellCheck:a,dd:o,onPaste:s,onInput:c,onFocus:u,onBlur:d,onKeyDown:f}){let p=n();return l(`div`,{ref:e,id:p?.controlId,role:`textbox`,"aria-multiline":`true`,"aria-label":r?void 0:i||`Текст`,"aria-describedby":p?.describedBy,"aria-invalid":p?.errorId?!0:void 0,tabIndex:t?0:-1,"aria-disabled":t?void 0:!0,onPaste:s,onInput:c,onFocus:u,onBlur:d,onKeyDown:f,spellCheck:a,contentEditable:t?`plaintext-only`:!1,suppressContentEditableWarning:!0,...o})}d.displayName=`TextareaField`;export{d as default};
|
package/dist/fields/Time.d.ts
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
import { TInput } from './type';
|
|
2
|
-
/**
|
|
2
|
+
/**
|
|
3
|
+
* Время `HH:mm` (два contentEditable: часы и минуты).
|
|
4
|
+
*
|
|
5
|
+
* **В форме:** `string` `"14:30"` или `""`.
|
|
6
|
+
*
|
|
7
|
+
* **Элементы:** капсула → [часы] `:` [минуты] + опционально кнопка picker (`after` по умолчанию — часы).
|
|
8
|
+
* `step` — шаг минут в **секундах** (`300` = 5 мин). `align`: `start` | `center`.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* <TimeField name="at" form={form} label="Время" step={300} picker />
|
|
12
|
+
*/
|
|
3
13
|
export type TTime = TInput & {
|
|
4
14
|
/** Шаг для минут в секундах (`60` — любая минута; `300` — кратно 5 мин) */
|
|
5
15
|
step?: number;
|
|
@@ -22,9 +32,8 @@ export type TTime = TInput & {
|
|
|
22
32
|
presets?: string[];
|
|
23
33
|
};
|
|
24
34
|
/**
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* уходит при уходе фокуса с группы (snap минут, clamp, формат `HH:mm`).
|
|
35
|
+
* Поле времени. Пока фокус в группе — в DOM сырые цифры; в форму `HH:mm` уходит на blur.
|
|
36
|
+
* Popup слотов — при `picker` / `presets`.
|
|
28
37
|
*/
|
|
29
38
|
declare function TimeField({ value, form, onValue, onBlur: onBlurField, name, placeholder, label, hidden, disabled, error, className, after, before, step, min, max, id, spellCheck, align, picker, presets, }: TTime): import("react/jsx-runtime").JSX.Element | null;
|
|
30
39
|
declare namespace TimeField {
|
package/dist/fields/Time.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import e from"../styles/Field.module.js";import t from"../core/Field.js";import{useFormFieldState as n}from"../hooks/use.js";import r
|
|
1
|
+
import e from"../styles/Field.module.js";import t from"../core/Field.js";import{useFormFieldState as n}from"../hooks/use.js";import{createFieldEmit as r}from"./emit.js";import i from"../events/onEvent.js";import a from"../styles/Time.module.js";import o from"./TimePicker.js";import{buildTimeSlots as s,clampToRange as c,combine as l,digitsOnly as u,getRangeStartOffsetInElement as ee,normalizeTime as d,pad2 as f,parsePartDigits as p,setCursorPosition as m,snapMinute as h,splitTime as g}from"./timeUtils.js";import _ from"react";import{Fragment as te,jsx as v,jsxs as y}from"react/jsx-runtime";import{createPortal as ne}from"react-dom";function re(){return y(`svg`,{viewBox:`0 0 24 24`,width:`20`,height:`20`,fill:`none`,"aria-hidden":!0,children:[v(`circle`,{cx:`12`,cy:`12`,r:`8.25`,stroke:`currentColor`,strokeWidth:`1.75`}),v(`path`,{d:`M12 7.5V12l3 2`,stroke:`currentColor`,strokeWidth:`1.75`,strokeLinecap:`round`,strokeLinejoin:`round`})]})}function b({value:b=``,form:x,onValue:S,onBlur:C,name:w=``,placeholder:T=``,label:E=``,hidden:ie=!1,disabled:D=!1,error:ae=``,className:oe=``,after:O=``,before:k=``,step:A=60,min:j,max:M,id:se,spellCheck:N=!1,align:ce=`start`,picker:P=!1,presets:F}){let{value:le,version:ue}=n(x,w,b),I=d(le),L=!D,R=_.useRef(null),z=_.useRef(null),B=_.useRef(null),V=_.useRef(null),[H,U]=_.useState(!1),[W,G]=_.useState(!1),[K,q]=_.useState(null),[J,Y]=_.useState(0),de=_.useId(),X=_.useMemo(()=>s(A,j,M,F),[A,j,M,F]);_.useLayoutEffect(()=>{if(!H||!L)return;let{h:e,m:t}=g(I),n=(e,t)=>{e&&(t===null?e.innerHTML=`<br>`:e.textContent=String(t))};n(R.current,e),n(z.current,t)},[H,I,ue,L]);let fe=_.useCallback(()=>{let e=p(R.current?.textContent||``,23),t=p(z.current?.textContent||``,59);if(e===null&&t===null)return``;let n=l(e,t);if(!n)return``;let{h:r,m:i}=g(n);return i!==null&&(n=l(r,h(i,A))),n&&=c(n,j,M),n||``},[j,M,A]),Z=_.useCallback(e=>{let t=d(e);if(t){let{h:e,m:n}=g(t);n!==null&&(t=l(e,h(n,A))),t=c(t,j,M)}return r(x,w,S)(t,!0),t},[x,w,S,j,M,A]),pe=_.useCallback(()=>{if(!P||!L||X.length===0)return;let e=V.current;if(!e)return;let t=Math.max(0,X.findIndex(e=>e===I));Y(t>=0?t:0),q(e.getBoundingClientRect()),G(!0)},[P,L,X,I]);_.useEffect(()=>{if(!W)return;let e=()=>{let e=V.current;e&&q(e.getBoundingClientRect())};return window.addEventListener(`scroll`,e,!0),window.addEventListener(`resize`,e),()=>{window.removeEventListener(`scroll`,e,!0),window.removeEventListener(`resize`,e)}},[W]),_.useEffect(()=>{if(!W)return;let e=e=>{if(e.key===`Escape`){e.preventDefault(),G(!1);return}if(X.length!==0){if(e.key===`ArrowDown`)e.preventDefault(),Y(e=>(e+1)%X.length);else if(e.key===`ArrowUp`)e.preventDefault(),Y(e=>e-1<0?X.length-1:e-1);else if(e.key===`Enter`){e.preventDefault();let t=X[J];t&&(Z(t),G(!1))}}};return window.addEventListener(`keydown`,e),()=>window.removeEventListener(`keydown`,e)},[W,X,J,Z]);let me=e=>t=>{if(t.preventDefault(),!L)return;let n=u(t.clipboardData?.getData(`text/plain`)??``),r=e===`hour`?23:59;if(n.length>=4){let e=Math.min(23,Math.max(0,parseInt(n.slice(0,2),10))),t=Math.min(59,Math.max(0,parseInt(n.slice(2,4),10))),r=R.current,i=z.current;r&&(r.textContent=String(e),queueMicrotask(()=>m(String(e).length,r))),i&&(i.textContent=String(t),queueMicrotask(()=>m(String(t).length,i)));return}let i=n.slice(0,2),a=t.currentTarget;if(i===``){a.innerHTML=`<br>`,queueMicrotask(()=>m(0,a));return}let o=parseInt(i,10);if(Number.isNaN(o))return;let s=String(Math.min(r,Math.max(0,o)));a.textContent=s,queueMicrotask(()=>m(s.length,a))},he=e=>{let t=e.target,n=t.textContent||``,r=ee(t),i=n.slice(0,r).replace(/\D/g,``).length,a=u(n).slice(0,2);if(a===``){t.innerHTML=`<br>`,queueMicrotask(()=>m(0,t));return}let o=parseInt(a,10);if(Number.isNaN(o))return;let s=String(Math.min(23,Math.max(0,o)));t.textContent=s;let c=Math.min(i,s.length);queueMicrotask(()=>m(c,t))},ge=e=>{let t=e.target,n=t.textContent||``,r=ee(t),i=n.slice(0,r).replace(/\D/g,``).length,a=u(n).slice(0,2);if(a===``){t.innerHTML=`<br>`,queueMicrotask(()=>m(0,t));return}let o=parseInt(a,10);if(Number.isNaN(o))return;let s=String(Math.min(59,Math.max(0,o)));t.textContent=s;let c=Math.min(i,s.length);queueMicrotask(()=>m(c,t))},_e=e=>{(e.key===`Enter`||e.key===`:`)&&(e.preventDefault(),z.current?.focus())},ve=e=>{e.key===`Enter`&&e.preventDefault()},ye=e=>{let t=e.currentTarget;(t.textContent||``).trim().length||(t.innerHTML=`<br>`),L&&U(!0)},be=e=>{i.Blur(e)};_.useEffect(()=>{let e=B.current;if(!e)return;let t=t=>{let n=t.relatedTarget;if(n&&e.contains(n))return;let i=fe();r(x,w,S)(i,!0),C&&w&&C({value:i,name:w}),U(!1)};return e.addEventListener(`focusout`,t),()=>e.removeEventListener(`focusout`,t)},[x,w,S,C,j,M,A,fe]);let{h:xe,m:Se}=g(I),Q={};L&&(Q.edit=``);let $={...Q},Ce={...Q};T&&($.placeholder=T);let we=I!==``||H;return ie&&I===``?null:y(te,{children:[v(`div`,{ref:V,children:y(t,{type:`time`,label:E,error:ae,element_blok:{className:[oe||``]},element_input:{className:[e.w,a.r,ce===`center`?a.c:``].filter(Boolean),props:{"data-disabled":D||!L?`true`:void 0}},children:[k?v(`span`,{className:e.a,children:k}):null,y(`div`,{ref:B,className:a.i,role:`group`,"aria-label":E||`Время`,children:[v(`div`,{id:se,ref:R,className:a.p,contentEditable:L?`plaintext-only`:!1,suppressContentEditableWarning:!0,spellCheck:N,inputMode:`numeric`,onPaste:me(`hour`),onInput:he,onKeyDown:_e,onFocus:ye,onBlur:be,"aria-label":`Часы, 0–23`,...$,children:!L||!H?xe===null?``:f(xe):null}),we&&v(`span`,{className:a.s,"aria-hidden":`true`,children:`:`}),v(`div`,{ref:z,className:a.p,contentEditable:L?`plaintext-only`:!1,suppressContentEditableWarning:!0,spellCheck:N,inputMode:`numeric`,onPaste:me(`minute`),onInput:ge,onKeyDown:ve,onFocus:ye,onBlur:be,"aria-label":`Минуты, 0–59`,...Ce,children:!L||!H?Se===null?``:f(Se):null})]}),P?v(`button`,{type:`button`,className:a.b,disabled:!L||X.length===0,"aria-label":`Быстрый выбор времени`,"aria-haspopup":`listbox`,"aria-expanded":W,"aria-controls":de,onMouseDown:e=>e.preventDefault(),onClick:pe,children:v(re,{})}):null,O?v(`span`,{className:e.a,children:O}):null]})}),W&&K&&ne(v(o,{parent:K,slots:X,current:I,activeIndex:J,listId:de,className:oe,setOpen:G,setActiveIndex:Y,onPick:e=>{Z(e),G(!1)}}),document.body)]})}b.displayName=`TimeField`;export{b as default};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { default as React } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Попап слотов {@link TimeField} (портал, якорь к капсуле).
|
|
4
|
+
* Не публичный API пакета.
|
|
5
|
+
*/
|
|
6
|
+
export type TTimePickerProps = {
|
|
7
|
+
/** getBoundingClientRect капсулы. */
|
|
8
|
+
parent: DOMRect;
|
|
9
|
+
/** Список `HH:mm`. */
|
|
10
|
+
slots: string[];
|
|
11
|
+
/** Текущее значение поля. */
|
|
12
|
+
current: string;
|
|
13
|
+
/** Подсветка пункта (клавиатура). */
|
|
14
|
+
activeIndex: number;
|
|
15
|
+
listId: string;
|
|
16
|
+
className?: string;
|
|
17
|
+
setOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
|
18
|
+
setActiveIndex: React.Dispatch<React.SetStateAction<number>>;
|
|
19
|
+
/** Выбор слота → коммит в форму. */
|
|
20
|
+
onPick: (slot: string) => void;
|
|
21
|
+
};
|
|
22
|
+
/** Список времени `role="listbox"`. */
|
|
23
|
+
declare function TimePicker({ parent, slots, current, activeIndex, listId, className, setOpen, setActiveIndex, onPick, }: TTimePickerProps): import("react/jsx-runtime").JSX.Element;
|
|
24
|
+
export default TimePicker;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{acquireOverlayZIndex as e,releaseOverlayZIndex as t}from"../overlayZ.js";import n from"../styles/Select.module.js";import r from"react";import{Fragment as i,jsx as a,jsxs as o}from"react/jsx-runtime";function s({parent:s,slots:c,current:l,activeIndex:u,listId:d,className:f,setOpen:p,setActiveIndex:m,onPick:h}){let g=r.useRef(null),[_]=r.useState(()=>e());r.useEffect(()=>()=>t(_),[_]),r.useEffect(()=>{(g.current?.querySelector(`[data-focus="true"]`))?.scrollIntoView({block:`nearest`})},[u]);let v=window.innerHeight-s.bottom<306?s.top-300-6:s.bottom+6;v=Math.min(v,window.innerHeight-300-6),v=Math.max(6,v);let y=Math.max(s.width,160),b=Math.min(Math.max(6,s.left),window.innerWidth-y-6);return o(i,{children:[a(`div`,{className:n.o,style:{zIndex:_},onClick:()=>p(!1),"aria-hidden":!0}),a(`div`,{className:[n.l,f].filter(e=>e!==void 0).join(` `),style:{top:v+`px`,left:b+`px`,width:y,zIndex:_+1},children:a(`ul`,{id:d,role:`listbox`,ref:g,children:c.map((e,t)=>a(`li`,{id:`${d}-opt-${t}`,role:`option`,"aria-selected":l===e,"data-focus":u===t?`true`:`false`,onMouseEnter:()=>m(t),onClick:()=>h(e),children:e},e))})})]})}export{s as default};
|
package/dist/fields/Url.d.ts
CHANGED
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
import { default as React } from 'react';
|
|
2
2
|
import { TInput } from './type';
|
|
3
|
-
/**
|
|
3
|
+
/**
|
|
4
|
+
* Сайт / ссылка. Нативный `<input type="url">`.
|
|
5
|
+
*
|
|
6
|
+
* **В форме:** `string`. Пропы — {@link TInput}.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* <UrlField name="site" form={form} label="Сайт" placeholder="https://" />
|
|
10
|
+
*/
|
|
4
11
|
export type TUrl = TInput;
|
|
12
|
+
/**
|
|
13
|
+
* Поле URL (автозаполнение `url`, клавиатура `inputMode="url"`).
|
|
14
|
+
*/
|
|
5
15
|
declare function UrlField(props: TUrl): import("react/jsx-runtime").JSX.Element;
|
|
6
16
|
declare namespace UrlField {
|
|
7
17
|
var displayName: string;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Алгоритм Луна для номеров банковских карт.
|
|
3
|
+
* @returns true, если контрольная сумма верна
|
|
4
|
+
*/
|
|
5
|
+
export declare function luhnCheck(digits: string): boolean;
|
|
6
|
+
/** Только цифры, максимум 19 (ISO/IEC 7812) */
|
|
7
|
+
export declare function cardDigits(value: unknown): string;
|
|
8
|
+
/** Группы по 4: `4111 1111 1111 1111` */
|
|
9
|
+
export declare function formatCardNumber(digits: string): string;
|
|
10
|
+
export type CardBrand = "visa" | "mastercard" | "amex" | "mir" | "unionpay" | "unknown";
|
|
11
|
+
/**
|
|
12
|
+
* Определение платёжной системы по BIN.
|
|
13
|
+
* Visa / Мир / Amex — с 4 цифр. Mastercard 2xxx часто только с 6
|
|
14
|
+
* (диапазон 2221–2720); 2220 на четырёх — unknown.
|
|
15
|
+
*/
|
|
16
|
+
export declare function detectCardBrand(digits: string): CardBrand;
|
|
17
|
+
/** Состояние бейджа: не пересчитываем на 5+ если уже узнали бренд. */
|
|
18
|
+
export type CardBrandLock = {
|
|
19
|
+
prefix4: string;
|
|
20
|
+
locked: boolean;
|
|
21
|
+
brand: CardBrand;
|
|
22
|
+
};
|
|
23
|
+
export declare function emptyCardBrandLock(): CardBrandLock;
|
|
24
|
+
/**
|
|
25
|
+
* Иконка по BIN:
|
|
26
|
+
* — меньше 4 цифр → «••••»;
|
|
27
|
+
* — на 4-й: Visa / Мир / Amex / MC (если хватает) — сразу и freeze;
|
|
28
|
+
* — если на 4 ещё unknown (часто MC 2xxx) — ждём до 6-й, потом freeze;
|
|
29
|
+
* — 7+ не трогаем, пока не сменятся первые 4.
|
|
30
|
+
*/
|
|
31
|
+
export declare function stepCardBrand(digits: string, prev: CardBrandLock): CardBrandLock;
|
|
32
|
+
/**
|
|
33
|
+
* Проверка номера карты.
|
|
34
|
+
* Пустая строка — ок (обязательность снаружи).
|
|
35
|
+
* Неполная — ошибка; полная без Луна — ошибка.
|
|
36
|
+
*/
|
|
37
|
+
export declare function validateCardNumber(value: unknown): string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function e(e){let t=e.replace(/\D/g,``);if(t.length<13||t.length>19)return!1;let n=0,r=!1;for(let e=t.length-1;e>=0;e--){let i=t.charCodeAt(e)-48;if(i<0||i>9)return!1;r&&(i*=2,i>9&&(i-=9)),n+=i,r=!r}return n%10==0}function t(e){return String(e??``).replace(/\D/g,``).slice(0,19)}function n(e){return t(e).replace(/(\d{4})(?=\d)/g,`$1 `).trimEnd()}function r(e){let n=t(e);if(n.length<4)return`unknown`;let r=n.slice(0,6);return/^4/.test(r)?`visa`:/^3[47]/.test(r)?`amex`:/^220[0-4]/.test(r)?`mir`:/^(5[1-5]|222[1-9]|22[3-9]\d|2[3-6]\d{2}|27[01]\d|2720)/.test(r)?`mastercard`:/^62/.test(r)?`unionpay`:`unknown`}function i(){return{prefix4:``,locked:!1,brand:`unknown`}}function a(e,n){let o=t(e);if(o.length<4)return i();let s=o.slice(0,4);if(n.prefix4&&n.prefix4!==s)return a(o,i());if(n.locked&&n.prefix4===s)return n;let c=r(o);return c===`unknown`?o.length>=6?{prefix4:s,locked:!0,brand:`unknown`}:{prefix4:s,locked:!1,brand:`unknown`}:{prefix4:s,locked:!0,brand:c}}function o(n){let r=t(n);return r?r.length<13?`Неполный номер карты`:e(r)?``:`Некорректный номер карты`:``}export{t as cardDigits,r as detectCardBrand,i as emptyCardBrandLock,n as formatCardNumber,e as luhnCheck,a as stepCardBrand,o as validateCardNumber};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/** Встроенная палитра (6 колонок), близкая к пресетам UI */
|
|
2
|
+
export declare const DEFAULT_COLOR_PALETTE: string[];
|
|
3
|
+
export type Hsva = {
|
|
4
|
+
h: number;
|
|
5
|
+
s: number;
|
|
6
|
+
v: number;
|
|
7
|
+
a: number;
|
|
8
|
+
};
|
|
9
|
+
export declare function clamp(n: number, min: number, max: number): number;
|
|
10
|
+
export declare function toHexByte(n: number): string;
|
|
11
|
+
/** HSV → RGB 0..255 */
|
|
12
|
+
export declare function hsvToRgb(h: number, s: number, v: number): {
|
|
13
|
+
r: number;
|
|
14
|
+
g: number;
|
|
15
|
+
b: number;
|
|
16
|
+
};
|
|
17
|
+
export declare function rgbToHsv(r: number, g: number, b: number): Omit<Hsva, "a">;
|
|
18
|
+
/** #RGB / #RRGGBB / #RRGGBBAA → hsva */
|
|
19
|
+
export declare function parseHexColor(input: unknown): Hsva | null;
|
|
20
|
+
/** hsva → #RRGGBB или #RRGGBBAA (alpha < 1) */
|
|
21
|
+
export declare function formatHexColor(hsva: Hsva, withAlpha: boolean): string;
|
|
22
|
+
export declare function cssColor(hsva: Hsva): string;
|
|
23
|
+
export declare function hueCss(h: number): string;
|
|
24
|
+
export declare function normalizeHexCompare(hex: string): string;
|
|
25
|
+
export declare function isEmptyColor(value: unknown): boolean;
|
|
26
|
+
export declare function isEditablePasteTarget(target: EventTarget | null): boolean;
|
|
27
|
+
/** Hex / rgb из буфера обмена */
|
|
28
|
+
export declare function parseClipboardColor(text: string): Hsva | null;
|
|
29
|
+
export declare function hasEyeDropper(): boolean;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var e=`#FF2D55.#FF3B30.#FF453A.#FF6961.#FF8A80.#C11F3E.#AF52DE.#BF5AF2.#DA8FFF.#5856D6.#5E5CE6.#7D7AFF.#007AFF.#0A84FF.#409CFF.#64D2FF.#70D7FF.#5AC8FA.#32ADE6.#64D2FF.#40C8E0.#00C7BE.#63E6E2.#66D4CF.#34C759.#30D158.#30DB5B.#4CD964.#6DE6A0.#8EEDB3.#A8E10C.#B5F200.#D0FF00.#E4FF66.#F2FF99.#C6FF00.#FFCC00.#FFD60A.#FFEA00.#FFE066.#FFF3A3.#FFEB3B.#FF9500.#FF9F0A.#FFB340.#FFB340.#FFD580.#FF8C00.#A2845E.#AC8E68.#C4A484.#8E8E93.#636366.#48484A.#3A3A3C.#2C2C2E.#1C1C1E`.split(`.`);function t(e,t,n){return Math.min(n,Math.max(t,e))}function n(e){return Math.round(t(e,0,255)).toString(16).padStart(2,`0`).toUpperCase()}function r(e,t,n){let r=(e%360+360)%360,i=n*t,a=i*(1-Math.abs(r/60%2-1)),o=n-i,s=0,c=0,l=0;return r<60?[s,c,l]=[i,a,0]:r<120?[s,c,l]=[a,i,0]:r<180?[s,c,l]=[0,i,a]:r<240?[s,c,l]=[0,a,i]:r<300?[s,c,l]=[a,0,i]:[s,c,l]=[i,0,a],{r:(s+o)*255,g:(c+o)*255,b:(l+o)*255}}function i(e,t,n){let r=e/255,i=t/255,a=n/255,o=Math.max(r,i,a),s=o-Math.min(r,i,a),c=0;s!==0&&(c=o===r?(i-a)/s%6:o===i?(a-r)/s+2:(r-i)/s+4,c*=60,c<0&&(c+=360));let l=o===0?0:s/o;return{h:c,s:l,v:o}}function a(e){if(typeof e!=`string`)return null;let t=e.trim();if(!t||(t[0]===`#`&&(t=t.slice(1)),![3,4,6,8].includes(t.length))||!/^[0-9a-fA-F]+$/.test(t))return null;let n=0,r=0,a=0,o=1;return t.length===3||t.length===4?(n=parseInt(t[0]+t[0],16),r=parseInt(t[1]+t[1],16),a=parseInt(t[2]+t[2],16),t.length===4&&(o=parseInt(t[3]+t[3],16)/255)):(n=parseInt(t.slice(0,2),16),r=parseInt(t.slice(2,4),16),a=parseInt(t.slice(4,6),16),t.length===8&&(o=parseInt(t.slice(6,8),16)/255)),{...i(n,r,a),a:o}}function o(e,t){let{r:i,g:a,b:o}=r(e.h,e.s,e.v),s=`#${n(i)}${n(a)}${n(o)}`;return!t||e.a>=.999?s:`${s}${n(e.a*255)}`}function s(e){let{r:t,g:n,b:i}=r(e.h,e.s,e.v),a=Math.round(t),o=Math.round(n),s=Math.round(i);return e.a>=.999?`rgb(${a}, ${o}, ${s})`:`rgba(${a}, ${o}, ${s}, ${Number(e.a.toFixed(3))})`}function c(e){let{r:t,g:n,b:i}=r(e,1,1);return`rgb(${Math.round(t)},${Math.round(n)},${Math.round(i)})`}function l(e){let t=a(e);return t?o(t,t.a<.999):e.toUpperCase()}function u(e){return e==null?!0:typeof e==`string`?e.trim()===``:!1}function d(e){if(!(e instanceof HTMLElement))return!1;let t=e.tagName;return t===`INPUT`||t===`TEXTAREA`||t===`SELECT`?!0:!!e.isContentEditable}function f(e){let n=(e||``).trim();if(!n)return null;let r=a(n)??a(n[0]===`#`?n:`#${n.replace(/^#/,``)}`);if(r)return r;let o=/^rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})(?:\s*,\s*([\d.]+))?\s*\)$/i.exec(n);if(o){let e=t(Number(o[1]),0,255),n=t(Number(o[2]),0,255),r=t(Number(o[3]),0,255),a=o[4]===void 0?1:t(Number(o[4]),0,1);return{...i(e,n,r),a}}return null}function p(){return typeof window<`u`&&`EyeDropper`in window}export{e as DEFAULT_COLOR_PALETTE,t as clamp,s as cssColor,o as formatHexColor,p as hasEyeDropper,c as hueCss,d as isEditablePasteTarget,u as isEmptyColor,l as normalizeHexCompare,f as parseClipboardColor,a as parseHexColor};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export type TDateTemplate = "dd.mm.yyyy" | "yyyy-mm-dd" | "dd.mm.yy" | "dd.mm.yyyy HH:MM:SS" | string;
|
|
2
|
+
export type TDatePosition = {
|
|
3
|
+
x: number;
|
|
4
|
+
y: number;
|
|
5
|
+
b: number;
|
|
6
|
+
};
|
|
7
|
+
export type TMonthInfo = {
|
|
8
|
+
val: string;
|
|
9
|
+
name: string;
|
|
10
|
+
names: string;
|
|
11
|
+
abc: string;
|
|
12
|
+
};
|
|
13
|
+
/** Для отображения в триггере: пусто → placeholder/data-placeholder, не «сегодня» */
|
|
14
|
+
export declare function isDateDisplayEmpty(value: unknown): boolean;
|
|
15
|
+
/** Месяц (1–12) в году y попадает в диапазон min/max */
|
|
16
|
+
export declare function isMonthInRange(y: number, m: number, min?: Date, max?: Date): boolean;
|
|
17
|
+
/** Год целиком пересекается с min/max */
|
|
18
|
+
export declare function isYearInRange(y: number, min?: Date, max?: Date): boolean;
|
|
19
|
+
/** Начало 12-летнего блока для сетки лет, с учётом min/max */
|
|
20
|
+
export declare function clampYearPageStart(start: number, min?: Date, max?: Date): number;
|
|
21
|
+
export declare function isEditablePasteTarget(target: EventTarget | null): boolean;
|
|
22
|
+
/** Разбор даты из буфера: dd.mm.yyyy, yyyy-mm-dd и т.п. */
|
|
23
|
+
export declare function parseClipboardDate(text: string): Date | null;
|
|
24
|
+
export declare function clampDateValue(d: Date, min?: Date, max?: Date): Date;
|
|
25
|
+
/** Дополнение числа ведущими нулями до длины l */
|
|
26
|
+
export declare function FormatNumber(l: number, n: number): string;
|
|
27
|
+
/** Дата в формате хранения формы: YYYY-MM-DD */
|
|
28
|
+
export declare function toStoreDateString(d: Date): string;
|
|
29
|
+
/** Имя месяца (1–12) или из Date */
|
|
30
|
+
export declare function FormatMonth(m: number | Date): TMonthInfo;
|
|
31
|
+
/** Форматирование даты по шаблону (dd.mm.yyyy и т.п.) */
|
|
32
|
+
export declare function FormatDate(x: Date | string | number, y?: TDateTemplate): string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{isEmptyTextLike as e}from"./visibility.js";function t(t){return!!(t==null||typeof t==`string`&&e(t)||t instanceof Date&&Number.isNaN(t.getTime()))}function n(e,t,n,r){let i=new Date(e,t-1,1);return!(n&&new Date(e,t,0)<n||r&&i>r)}function r(e,t,n){return!(t&&e<t.getFullYear()||n&&e>n.getFullYear())}function i(e,t,n){let r=e;if(t){let e=t.getFullYear();r+11<e&&(r=e-11),r<e&&(r=e)}if(n){let e=n.getFullYear();r>e&&(r=e-11),r+11>e&&(r=e-11)}return r}function a(e){if(!(e instanceof HTMLElement))return!1;let t=e.tagName;return t===`INPUT`||t===`TEXTAREA`||t===`SELECT`?!0:!!e.isContentEditable}function o(e){let t=(e||``).trim();if(!t)return null;let n=t.split(/\s+/)[0].replace(/,/g,`.`),r=(e,t,n)=>{if(t<1||t>12||n<1||n>31)return null;let r=e<100?2e3+e:e,i=new Date(r,t-1,n);return Number.isNaN(i.getTime())||i.getFullYear()!==r||i.getMonth()!==t-1||i.getDate()!==n?null:i},i=/^(\d{1,2})[./](\d{1,2})[./](\d{2,4})$/.exec(n);if(i)return r(parseInt(i[3],10),parseInt(i[2],10),parseInt(i[1],10));if(i=/^(\d{4})-(\d{1,2})-(\d{1,2})$/.exec(n),i)return r(parseInt(i[1],10),parseInt(i[2],10),parseInt(i[3],10));if(i=/^(\d{1,2})\/(\d{1,2})\/(\d{2,4})$/.exec(n),i)return r(parseInt(i[3],10),parseInt(i[2],10),parseInt(i[1],10));let a=n.replace(/\D/g,``);return a.length===8?r(parseInt(a.slice(4,8),10),parseInt(a.slice(2,4),10),parseInt(a.slice(0,2),10))||r(parseInt(a.slice(0,4),10),parseInt(a.slice(4,6),10),parseInt(a.slice(6,8),10)):null}function s(e,t,n){let r=e.getTime();return t&&r<t.getTime()&&(r=t.getTime()),n&&r>n.getTime()&&(r=n.getTime()),new Date(r)}function c(e,t){let n=t+``;for(;n.length<e;)n=`0`+n;return n}function l(e){return e.getFullYear()+`-`+c(2,e.getMonth()+1)+`-`+c(2,e.getDate())}var u={val:`01`,name:`—`,names:`—`,abc:`—`};function d(e){let t=typeof e==`number`?e:new Date(e).getMonth()+1;return(!Number.isFinite(t)||t<1||t>12)&&(t=new Date().getMonth()+1),{1:{val:`01`,name:`Январь`,names:`Января`,abc:`Янв`},2:{val:`02`,name:`Февраль`,names:`Февраля`,abc:`Февр`},3:{val:`03`,name:`Март`,names:`Марта`,abc:`Март`},4:{val:`04`,name:`Апрель`,names:`Апреля`,abc:`Апр`},5:{val:`05`,name:`Май`,names:`Мая`,abc:`Май`},6:{val:`06`,name:`Июнь`,names:`Июня`,abc:`Июнь`},7:{val:`07`,name:`Июль`,names:`Июля`,abc:`Июль`},8:{val:`08`,name:`Август`,names:`Августа`,abc:`Авг`},9:{val:`09`,name:`Сентябрь`,names:`Сентября`,abc:`Сент`},10:{val:`10`,name:`Октябрь`,names:`Октября`,abc:`Окт`},11:{val:`11`,name:`Ноябрь`,names:`Ноября`,abc:`Нояб`},12:{val:`12`,name:`Декабрь`,names:`Декабря`,abc:`Дек`}}[t]??u}function f(e,t){let n,r;t===void 0?(r=String(e),n=new Date):(r=t,n=new Date(e)),Number.isNaN(n.getTime())&&(n=new Date);let i=r,a={yyyy:n.getFullYear(),yy:(``+n.getFullYear()).slice(-2),mm:(`0`+(n.getMonth()+1)).slice(-2),dd:(`0`+n.getDate()).slice(-2),HH:(`0`+n.getHours()).slice(-2),MM:(`0`+n.getMinutes()).slice(-2),SS:(`0`+n.getSeconds()).slice(-2),__:` \xA0\xA0\xA0\xA0 `,_:` \xA0\xA0 `};for(let e in a)i=i.replace(e,String(a[e]));return i}export{f as FormatDate,d as FormatMonth,c as FormatNumber,s as clampDateValue,i as clampYearPageStart,t as isDateDisplayEmpty,a as isEditablePasteTarget,n as isMonthInRange,r as isYearInRange,o as parseClipboardDate,l as toStoreDateString};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function e(e){let t=String(e??``).trim();return t?/^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/i.test(t):!0}function t(t){let n=String(t??``).trim();return n?e(n)?``:`Некорректный email`:``}export{e as isValidEmail,t as validateEmail};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** Смещение начала выделения в символах от начала contentEditable (как в Mask.tsx) */
|
|
2
|
+
export declare function getRangeStartOffsetInElement(el: HTMLElement): number;
|
|
3
|
+
/** Установка каретки в позицию `pos` (символы), как в Mask.tsx */
|
|
4
|
+
export declare function setCursorPosition(pos: number, elem: HTMLElement): void;
|
|
5
|
+
export declare function pad2(n: number): string;
|
|
6
|
+
export declare function digitsOnly(s: string): string;
|
|
7
|
+
export declare function normalizeTime(v: unknown): string;
|
|
8
|
+
export declare function splitTime(s: string): {
|
|
9
|
+
h: number | null;
|
|
10
|
+
m: number | null;
|
|
11
|
+
};
|
|
12
|
+
/** Сборка `HH:mm` с ведущими нулями — только при коммите (blur), не во время ввода */
|
|
13
|
+
export declare function combine(h: number | null, m: number | null): string;
|
|
14
|
+
/** Цифры из текста части (часы/минуты) в DOM */
|
|
15
|
+
export declare function parsePartDigits(s: string, max: number): number | null;
|
|
16
|
+
export declare function toMinutes(hhmm: string): number | null;
|
|
17
|
+
export declare function snapMinute(minute: number, stepSec: number): number;
|
|
18
|
+
export declare function clampToRange(hhmm: string, min?: string, max?: string): string;
|
|
19
|
+
/**
|
|
20
|
+
* Слоты для popup. При step меньше 5 мин список слишком длинный —
|
|
21
|
+
* для picker берём минимум 15 мин, если нет явных `presets`.
|
|
22
|
+
*/
|
|
23
|
+
export declare function buildTimeSlots(stepSec: number, min?: string, max?: string, presets?: string[]): string[];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function e(e){let t=window.getSelection();if(!t||t.rangeCount===0)return 0;let n=t.getRangeAt(0);if(!e.contains(n.startContainer))return 0;let r=document.createRange();return r.selectNodeContents(e),r.setEnd(n.startContainer,n.startOffset),r.toString().length}function t(e,t){let n=t.firstChild;if(!n||n.nodeType!==Node.TEXT_NODE)return;let r=n.textContent?.length??0,i=Math.max(0,Math.min(e,r)),a=document.createRange(),o=window.getSelection();o&&(a.setStart(n,i),a.collapse(!0),o.removeAllRanges(),o.addRange(a),t.focus())}function n(e){return String(e).padStart(2,`0`)}function r(e){return(e||``).replace(/\D/g,``)}function i(e){if(e==null||e===``||typeof e!=`string`)return``;let t=e.trim(),r=/^(\d{1,2}):(\d{2})(?::(\d{2}))?$/.exec(t);if(!r)return``;let i=Math.min(23,Math.max(0,parseInt(r[1],10))),a=Math.min(59,Math.max(0,parseInt(r[2],10)));return`${n(i)}:${n(a)}`}function a(e){let t=i(e);if(!t)return{h:null,m:null};let[n,r]=t.split(`:`).map(e=>parseInt(e,10));return{h:n,m:r}}function o(e,t){if(e===null&&t===null)return``;let r=e??0,i=t??0;return`${n(r)}:${n(i)}`}function s(e,t){let n=r(e).slice(0,2);if(n===``)return null;let i=parseInt(n,10);return Number.isNaN(i)?null:Math.min(t,Math.max(0,i))}function c(e){let t=i(e);if(!t)return null;let[n,r]=t.split(`:`).map(e=>parseInt(e,10));return n*60+r}function l(e,t){let n=Math.max(1,Math.round(t/60));if(n<=1)return e;let r=Math.round(e/n)*n;return Math.min(59,Math.max(0,r))}function u(e,t,n){let r=i(e);if(!r)return``;let a=c(r);if(a===null)return``;if(t){let e=c(t);e!==null&&(a=Math.max(a,e))}if(n){let e=c(n);e!==null&&(a=Math.min(a,e))}return o(Math.floor(a/60),a%60)}function d(e,t,n,r){if(r&&r.length>0){let e=new Set,a=[];for(let o of r){let r=i(o);!r||e.has(r)||(e.add(r),a.push(u(r,t,n)||r))}return a}let a=Math.max(1,Math.round(e/60));a<5&&(a=15);let s=t?c(t)??0:0,l=n?c(n)??1439:1439,d=Math.min(1439,l),f=[],p=Math.ceil(s/a)*a;for(let e=p;e<=d;e+=a){let t=Math.floor(e/60);if(t>23)break;let n=e%60;f.push(o(t,n))}return f}export{d as buildTimeSlots,u as clampToRange,o as combine,r as digitsOnly,e as getRangeStartOffsetInElement,i as normalizeTime,n as pad2,s as parsePartDigits,t as setCursorPosition,l as snapMinute,a as splitTime};
|