react-bootstrap-plugins 2.3.0 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -5,4 +5,79 @@ export { TableLoading, TableLoadingProps } from './TableLoading.cjs';
5
5
  export { AutoTextarea, AutoTextareaProps } from './AutoTextarea.cjs';
6
6
  export { NavPill, NavPills, NavPillsProps } from './NavPills.cjs';
7
7
  export { AutoDisplay, AutoDisplayProps } from './AutoDisplay.cjs';
8
- import 'react';
8
+ import * as React from 'react';
9
+
10
+ interface InputCurrencyProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'value' | 'onChange' | 'type'> {
11
+ /**
12
+ * Numeric value to display. Accepts `number` or `string` (the component
13
+ * coerces to `Number` internally). Pass `undefined` / `null` / `''` for
14
+ * an empty field.
15
+ */
16
+ value?: number | string | null;
17
+ /**
18
+ * Called with a synthetic input change event. `e.target.value` is
19
+ * replaced by the parsed numeric value — a `number` when the input
20
+ * holds a valid figure, or `''` when the field is cleared.
21
+ * `e.target.name` is preserved so the parent can identify the field.
22
+ */
23
+ onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;
24
+ /** ISO 4217 currency code (default `'UGX'`) */
25
+ currency?: string;
26
+ /** BCP 47 locale tag for formatting (default `'en-UG'`) */
27
+ locale?: string;
28
+ /**
29
+ * Number of decimal places.
30
+ * UGX = 0, USD = 2, JPY = 0, etc. (default `0`)
31
+ */
32
+ decimals?: number;
33
+ /**
34
+ * When `true`, formatting omits the currency symbol and outputs a plain
35
+ * locale-formatted number (e.g. `'150,000'` instead of `'UGX 150,000'`).
36
+ * Useful for non-monetary numeric inputs that still benefit from
37
+ * thousand-separator formatting.
38
+ */
39
+ plain?: boolean;
40
+ }
41
+ /**
42
+ * Controlled currency input for financial forms.
43
+ *
44
+ * **Data contract**: numbers in, numbers out. The component manages a
45
+ * locale-formatted display internally — the consumer never sees commas
46
+ * or currency symbols in its state.
47
+ *
48
+ * **Editing UX**: while the field is focused, the raw number is shown so
49
+ * the user can type without commas / symbols fighting their keystrokes.
50
+ * On blur (or when the external `value` prop changes from above), the
51
+ * display snaps back to the locale-formatted string.
52
+ *
53
+ * @example
54
+ * // UGX — zero decimal places, symbol shown
55
+ * <InputCurrency
56
+ * value={150000}
57
+ * name="fee"
58
+ * onChange={(e) => setFee(e.target.value)}
59
+ * />
60
+ *
61
+ * @example
62
+ * // USD with cents
63
+ * <InputCurrency
64
+ * value={99.99}
65
+ * currency="USD"
66
+ * locale="en-US"
67
+ * decimals={2}
68
+ * name="price"
69
+ * onChange={(e) => setPrice(e.target.value)}
70
+ * />
71
+ *
72
+ * @example
73
+ * // Plain locale number — no currency symbol
74
+ * <InputCurrency
75
+ * value={50000}
76
+ * plain
77
+ * name="count"
78
+ * onChange={(e) => setCount(e.target.value)}
79
+ * />
80
+ */
81
+ declare const InputCurrency: React.ForwardRefExoticComponent<InputCurrencyProps & React.RefAttributes<HTMLInputElement>>;
82
+
83
+ export { InputCurrency, type InputCurrencyProps };
package/dist/index.d.ts CHANGED
@@ -5,4 +5,79 @@ export { TableLoading, TableLoadingProps } from './TableLoading.js';
5
5
  export { AutoTextarea, AutoTextareaProps } from './AutoTextarea.js';
6
6
  export { NavPill, NavPills, NavPillsProps } from './NavPills.js';
7
7
  export { AutoDisplay, AutoDisplayProps } from './AutoDisplay.js';
8
- import 'react';
8
+ import * as React from 'react';
9
+
10
+ interface InputCurrencyProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'value' | 'onChange' | 'type'> {
11
+ /**
12
+ * Numeric value to display. Accepts `number` or `string` (the component
13
+ * coerces to `Number` internally). Pass `undefined` / `null` / `''` for
14
+ * an empty field.
15
+ */
16
+ value?: number | string | null;
17
+ /**
18
+ * Called with a synthetic input change event. `e.target.value` is
19
+ * replaced by the parsed numeric value — a `number` when the input
20
+ * holds a valid figure, or `''` when the field is cleared.
21
+ * `e.target.name` is preserved so the parent can identify the field.
22
+ */
23
+ onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;
24
+ /** ISO 4217 currency code (default `'UGX'`) */
25
+ currency?: string;
26
+ /** BCP 47 locale tag for formatting (default `'en-UG'`) */
27
+ locale?: string;
28
+ /**
29
+ * Number of decimal places.
30
+ * UGX = 0, USD = 2, JPY = 0, etc. (default `0`)
31
+ */
32
+ decimals?: number;
33
+ /**
34
+ * When `true`, formatting omits the currency symbol and outputs a plain
35
+ * locale-formatted number (e.g. `'150,000'` instead of `'UGX 150,000'`).
36
+ * Useful for non-monetary numeric inputs that still benefit from
37
+ * thousand-separator formatting.
38
+ */
39
+ plain?: boolean;
40
+ }
41
+ /**
42
+ * Controlled currency input for financial forms.
43
+ *
44
+ * **Data contract**: numbers in, numbers out. The component manages a
45
+ * locale-formatted display internally — the consumer never sees commas
46
+ * or currency symbols in its state.
47
+ *
48
+ * **Editing UX**: while the field is focused, the raw number is shown so
49
+ * the user can type without commas / symbols fighting their keystrokes.
50
+ * On blur (or when the external `value` prop changes from above), the
51
+ * display snaps back to the locale-formatted string.
52
+ *
53
+ * @example
54
+ * // UGX — zero decimal places, symbol shown
55
+ * <InputCurrency
56
+ * value={150000}
57
+ * name="fee"
58
+ * onChange={(e) => setFee(e.target.value)}
59
+ * />
60
+ *
61
+ * @example
62
+ * // USD with cents
63
+ * <InputCurrency
64
+ * value={99.99}
65
+ * currency="USD"
66
+ * locale="en-US"
67
+ * decimals={2}
68
+ * name="price"
69
+ * onChange={(e) => setPrice(e.target.value)}
70
+ * />
71
+ *
72
+ * @example
73
+ * // Plain locale number — no currency symbol
74
+ * <InputCurrency
75
+ * value={50000}
76
+ * plain
77
+ * name="count"
78
+ * onChange={(e) => setCount(e.target.value)}
79
+ * />
80
+ */
81
+ declare const InputCurrency: React.ForwardRefExoticComponent<InputCurrencyProps & React.RefAttributes<HTMLInputElement>>;
82
+
83
+ export { InputCurrency, type InputCurrencyProps };
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- export{a as DatePicker}from'./chunk-GCPYN7WB.js';export{a as SearchSelect}from'./chunk-ZCAQ7S2A.js';export{a as Label}from'./chunk-HYB5IGJK.js';export{a as TableLoading}from'./chunk-7LXLWNUR.js';export{a as AutoTextarea}from'./chunk-KKWFIJKN.js';export{a as NavPills}from'./chunk-KVUHNUBG.js';import'./chunk-RR7TBRGI.js';export{a as AutoDisplay}from'./chunk-2UX7JYM3.js';//# sourceMappingURL=index.js.map
2
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IiIsImZpbGUiOiJpbmRleC5qcyJ9
1
+ export{a as DatePicker}from'./chunk-LWCY2GIT.js';export{a as SearchSelect}from'./chunk-ZCAQ7S2A.js';export{a as Label}from'./chunk-HYB5IGJK.js';export{a as TableLoading}from'./chunk-7LXLWNUR.js';export{a as AutoTextarea}from'./chunk-KKWFIJKN.js';export{a as NavPills}from'./chunk-KVUHNUBG.js';import {a}from'./chunk-RR7TBRGI.js';export{a as AutoDisplay}from'./chunk-2UX7JYM3.js';import*as n from'react';import {jsx}from'react/jsx-runtime';var y=n.forwardRef(({value:r,onChange:b,className:h,name:p,id:C,disabled:P=false,readOnly:I=false,placeholder:N,currency:i="UGX",locale:c="en-UG",decimals:a$1=0,plain:m=false,onBlur:R,onFocus:L,...D},T)=>{let s=n.useCallback(e=>{if(e==null||e==="")return "";let t=Number(e);if(Number.isNaN(t))return "";let o=a$1>0?{minimumFractionDigits:a$1,maximumFractionDigits:a$1}:{minimumFractionDigits:0,maximumFractionDigits:0};return m?t.toLocaleString(c,o):t.toLocaleString(c,{style:"currency",currency:i,...o})},[i,c,a$1,m]),l=n.useCallback(e=>{if(!e)return "";let t=e.replace(/[^0-9.\-]/g,"");if(t==="-"||t===""||t===".")return "";let o=Number(t);return Number.isNaN(o)?"":a$1===0?Math.round(o):o},[a$1]),[f,g]=n.useState(false),[E,u]=n.useState(()=>s(r)),d=n.useRef(r);return n.useEffect(()=>{!f&&r!==d.current&&u(s(r)),d.current=r;},[r,f,s]),jsx("input",{ref:T,type:"text",id:C??p,name:p,value:E,onChange:e=>{let t=e.target.value;u(t);let o=l(t);e.target.value=o,b?.(e);},onFocus:e=>{g(true),r!=null&&r!==""&&u(String(r)),L?.(e);},onBlur:e=>{g(false);let t=l(e.target.value);u(s(t)),R?.(e);},disabled:P,readOnly:I,placeholder:N,inputMode:"decimal",autoComplete:"off",className:a("form-control",h),...D})});y.displayName="InputCurrency";export{y as InputCurrency};//# sourceMappingURL=index.js.map
2
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uL3NyYy9jb21wb25lbnRzL0lucHV0Q3VycmVuY3kudHN4Il0sIm5hbWVzIjpbIklucHV0Q3VycmVuY3kiLCJ2YWx1ZSIsIm9uQ2hhbmdlIiwiY2xhc3NOYW1lIiwibmFtZSIsImlkIiwiZGlzYWJsZWQiLCJyZWFkT25seSIsInBsYWNlaG9sZGVyIiwiY3VycmVuY3kiLCJsb2NhbGUiLCJkZWNpbWFscyIsInBsYWluIiwib25CbHVyIiwib25Gb2N1cyIsInJlc3QiLCJyZWYiLCJmb3JtYXREaXNwbGF5IiwidmFsIiwibnVtIiwiZnJhY3Rpb25EaWdpdHMiLCJwYXJzZU51bWVyaWMiLCJyYXciLCJjbGVhbmVkIiwiZm9jdXNlZCIsInNldEZvY3VzZWQiLCJpbm5lciIsInNldElubmVyIiwicHJldlZhbHVlUmVmIiwianN4IiwibnVtZXJpYyIsImNuIl0sIm1hcHBpbmdzIjoidWJBb0ZBLElBQU1BLENBQUFBLENBQXNCLENBQUEsQ0FBQSxVQUFBLENBQzFCLENBQ0UsQ0FDRSxLQUFBLENBQUFDLENBQUFBLENBQ0EsUUFBQSxDQUFBQyxDQUFBQSxDQUNBLFVBQUFDLENBQUFBLENBQ0EsSUFBQSxDQUFBQyxDQUFBQSxDQUNBLEVBQUEsQ0FBQUMsRUFDQSxRQUFBLENBQUFDLENBQUFBLENBQVcsS0FBQSxDQUNYLFFBQUEsQ0FBQUMsRUFBVyxLQUFBLENBQ1gsV0FBQSxDQUFBQyxDQUFBQSxDQUNBLFFBQUEsQ0FBQUMsRUFBVyxLQUFBLENBQ1gsTUFBQSxDQUFBQyxDQUFBQSxDQUFTLE9BQUEsQ0FDVCxTQUFBQyxHQUFBQSxDQUFXLENBQUEsQ0FDWCxLQUFBLENBQUFDLENBQUFBLENBQVEsS0FBQSxDQUNSLE1BQUEsQ0FBQUMsQ0FBQUEsQ0FDQSxPQUFBLENBQUFDLEVBQ0EsR0FBR0MsQ0FDTCxDQUFBLENBQ0FDLENBQUFBLEdBQ0csQ0FPSCxJQUFNQyxDQUFBQSxDQUFzQixDQUFBLENBQUEsV0FBQSxDQUN6QkMsQ0FBQUEsRUFBb0QsQ0FDbkQsR0FBSUEsQ0FB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QU8xQixDQUFLLENBQUMsQ0FBQSxDQUV4QmEsQ0FBQUEsR0FBVSxDQUFDLEVBQ2IsQ0FBQSxDQXFCSSxNQUFBLENBbkJnQixDQUFBLEVBQTBDLENBQzVEVyxFQUFXLEtBQUssQ0FBQSxDQUVoQixJQUFNSyxDQUFBQSxDQUFVVCxDQUFBQSxDQUFhLENBQUEsQ0FBRSxNQUFBLENBQU8sS0FBSyxFQUMzQ00sQ0FBQUEsQ0FBU1YsQ0FBQUEsQ0FBY2EsQ0FBTyxDQUFDLEVBQy9CakIsQ0FBQUEsR0FBUyxDQUFDLEVBQ1osQ0FBQSxDQWNJLFNBQVVQLENBQUFBLENBQ1YsUUFBQSxDQUFVQyxDQUFBQSxDQUNWLFdBQUEsQ0FBYUMsRUFDYixTQUFBLENBQVUsU0FBQSxDQUNWLFlBQUEsQ0FBYSxLQUFBLENBQ2IsVUFBV3VCLENBQUFBLENBQUcsY0FBQSxDQUFnQjVCLENBQVMsQ0FBQSxDQUN0QyxHQUFHWSxDQUFBQSxDQUNOLENBRUosQ0FDRixFQUVBZixFQUFjLFdBQUEsQ0FBYyxlQUFBIiwiZmlsZSI6ImluZGV4LmpzIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0ICogYXMgUmVhY3QgZnJvbSAncmVhY3QnXG5pbXBvcnQgeyBjbiB9IGZyb20gJy4uL2xpYi9jbi5qcydcblxuLyogLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tICovXG4vKiAgVHlwZXMgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICovXG4vKiAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0gKi9cblxuZXhwb3J0IGludGVyZmFjZSBJbnB1dEN1cnJlbmN5UHJvcHNcbiAgZXh0ZW5kcyBPbWl0PFJlYWN0LklucHV0SFRNTEF0dHJpYnV0ZXM8SFRNTElucHV0RWxlbWVudD4sICd2YWx1ZScgfCAnb25DaGFuZ2UnIHwgJ3R5cGUnPiB7XG4gIC8qKlxuICAgKiBOdW1lcmljIHZhbHVlIHRvIGRpc3BsYXkuIEFjY2VwdHMgYG51bWJlcmAgb3IgYHN0cmluZ2AgKHRoZSBjb21wb25lbnRcbiAgICogY29lcmNlcyB0byBgTnVtYmVyYCBpbnRlcm5hbGx5KS4gUGFzcyBgdW5kZWZpbmVkYCAvIGBudWxsYCAvIGAnJ2AgZm9yXG4gICAqIGFuIGVtcHR5IGZpZWxkLlxuICAgKi9cbiAgdmFsdWU/OiBudW1iZXIgfCBzdHJpbmcgfCBudWxsXG4gIC8qKlxuICAgKiBDYWxsZWQgd2l0aCBhIHN5bnRoZXRpYyBpbnB1dCBjaGFuZ2UgZXZlbnQuIGBlLnRhcmdldC52YWx1ZWAgaXNcbiAgICogcmVwbGFjZWQgYnkgdGhlIHBhcnNlZCBudW1lcmljIHZhbHVlIOKAlCBhIGBudW1iZXJgIHdoZW4gdGhlIGlucHV0XG4gICAqIGhvbGRzIGEgdmFsaWQgZmlndXJlLCBvciBgJydgIHdoZW4gdGhlIGZpZWxkIGlzIGNsZWFyZWQuXG4gICAqIGBlLnRhcmdldC5uYW1lYCBpcyBwcmVzZXJ2ZWQgc28gdGhlIHBhcmVudCBjYW4gaWRlbnRpZnkgdGhlIGZpZWxkLlxuICAgKi9cbiAgb25DaGFuZ2U/OiAoZTogUmVhY3QuQ2hhbmdlRXZlbnQ8SFRNTElucHV0RWxlbWVudD4pID0+IHZvaWRcbiAgLyoqIElTTyA0MjE3IGN1cnJlbmN5IGNvZGUgKGRlZmF1bHQgYCdVR1gnYCkgKi9cbiAgY3VycmVuY3k/OiBzdHJpbmdcbiAgLyoqIEJDUCA0NyBsb2NhbGUgdGFnIGZvciBmb3JtYXR0aW5nIChkZWZhdWx0IGAnZW4tVUcnYCkgKi9cbiAgbG9jYWxlPzogc3RyaW5nXG4gIC8qKlxuICAgKiBOdW1iZXIgb2YgZGVjaW1hbCBwbGFjZXMuXG4gICAqIFVHWCA9IDAsIFVTRCA9IDIsIEpQWSA9IDAsIGV0Yy4gKGRlZmF1bHQgYDBgKVxuICAgKi9cbiAgZGVjaW1hbHM/OiBudW1iZXJcbiAgLyoqXG4gICAqIFdoZW4gYHRydWVgLCBmb3JtYXR0aW5nIG9taXRzIHRoZSBjdXJyZW5jeSBzeW1ib2wgYW5kIG91dHB1dHMgYSBwbGFpblxuICAgKiBsb2NhbGUtZm9ybWF0dGVkIG51bWJlciAoZS5nLiBgJzE1MCwwMDAnYCBpbnN0ZWFkIG9mIGAnVUdYIDE1MCwwMDAnYCkuXG4gICAqIFVzZWZ1bCBmb3Igbm9uLW1vbmV0YXJ5IG51bWVyaWMgaW5wdXRzIHRoYXQgc3RpbGwgYmVuZWZpdCBmcm9tXG4gICAqIHRob3VzYW5kLXNlcGFyYXRvciBmb3JtYXR0aW5nLlxuICAgKi9cbiAgcGxhaW4/OiBib29sZWFuXG59XG5cbi8qIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSAqL1xuLyogIElucHV0Q3VycmVuY3kgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAqL1xuLyogLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tICovXG5cbi8qKlxuICogQ29udHJvbGxlZCBjdXJyZW5jeSBpbnB1dCBmb3IgZmluYW5jaWFsIGZvcm1zLlxuICpcbiAqICoqRGF0YSBjb250cmFjdCoqOiBudW1iZXJzIGluLCBudW1iZXJzIG91dC4gVGhlIGNvbXBvbmVudCBtYW5hZ2VzIGFcbiAqIGxvY2FsZS1mb3JtYXR0ZWQgZGlzcGxheSBpbnRlcm5hbGx5IOKAlCB0aGUgY29uc3VtZXIgbmV2ZXIgc2VlcyBjb21tYXNcbiAqIG9yIGN1cnJlbmN5IHN5bWJvbHMgaW4gaXRzIHN0YXRlLlxuICpcbiAqICoqRWRpdGluZyBVWCoqOiB3aGlsZSB0aGUgZmllbGQgaXMgZm9jdXNlZCwgdGhlIHJhdyBudW1iZXIgaXMgc2hvd24gc29cbiAqIHRoZSB1c2VyIGNhbiB0eXBlIHdpdGhvdXQgY29tbWFzIC8gc3ltYm9scyBmaWdodGluZyB0aGVpciBrZXlzdHJva2VzLlxuICogT24gYmx1ciAob3Igd2hlbiB0aGUgZXh0ZXJuYWwgYHZhbHVlYCBwcm9wIGNoYW5nZXMgZnJvbSBhYm92ZSksIHRoZVxuICogZGlzcGxheSBzbmFwcyBiYWNrIHRvIHRoZSBsb2NhbGUtZm9ybWF0dGVkIHN0cmluZy5cbiAqXG4gKiBAZXhhbXBsZVxuICogLy8gVUdYIOKAlCB6ZXJvIGRlY2ltYWwgcGxhY2VzLCBzeW1ib2wgc2hvd25cbiAqIDxJbnB1dEN1cnJlbmN5XG4gKiAgIHZhbHVlPXsxNTAwMDB9XG4gKiAgIG5hbWU9XCJmZWVcIlxuICogICBvbkNoYW5nZT17KGUpID0+IHNldEZlZShlLnRhcmdldC52YWx1ZSl9XG4gKiAvPlxuICpcbiAqIEBleGFtcGxlXG4gKiAvLyBVU0Qgd2l0aCBjZW50c1xuICogPElucHV0Q3VycmVuY3lcbiAqICAgdmFsdWU9ezk5Ljk5fVxuICogICBjdXJyZW5jeT1cIlVTRFwiXG4gKiAgIGxvY2FsZT1cImVuLVVTXCJcbiAqICAgZGVjaW1hbHM9ezJ9XG4gKiAgIG5hbWU9XCJwcmljZVwiXG4gKiAgIG9uQ2hhbmdlPXsoZSkgPT4gc2V0UHJpY2UoZS50YXJnZXQudmFsdWUpfVxuICogLz5cbiAqXG4gKiBAZXhhbXBsZVxuICogLy8gUGxhaW4gbG9jYWxlIG51bWJlciDigJQgbm8gY3VycmVuY3kgc3ltYm9sXG4gKiA8SW5wdXRDdXJyZW5jeVxuICogICB2YWx1ZT17NTAwMDB9XG4gKiAgIHBsYWluXG4gKiAgIG5hbWU9XCJjb3VudFwiXG4gKiAgIG9uQ2hhbmdlPXsoZSkgPT4gc2V0Q291bnQoZS50YXJnZXQudmFsdWUpfVxuICogLz5cbiAqL1xuY29uc3QgSW5wdXRDdXJyZW5jeSA9IFJlYWN0LmZvcndhcmRSZWY8SFRNTElucHV0RWxlbWVudCwgSW5wdXRDdXJyZW5jeVByb3BzPihcbiAgKFxuICAgIHtcbiAgICAgIHZhbHVlLFxuICAgICAgb25DaGFuZ2UsXG4gICAgICBjbGFzc05hbWUsXG4gICAgICBuYW1lLFxuICAgICAgaWQsXG4gICAgICBkaXNhYmxlZCA9IGZhbHNlLFxuICAgICAgcmVhZE9ubHkgPSBmYWxzZSxcbiAgICAgIHBsYWNlaG9sZGVyLFxuICAgICAgY3VycmVuY3kgPSAnVUdYJyxcbiAgICAgIGxvY2FsZSA9ICdlbi1VRycsXG4gICAgICBkZWNpbWFscyA9IDAsXG4gICAgICBwbGFpbiA9IGZhbHNlLFxuICAgICAgb25CbHVyLFxuICAgICAgb25Gb2N1cyxcbiAgICAgIC4uLnJlc3RcbiAgICB9LFxuICAgIHJlZixcbiAgKSA9PiB7XG4gICAgLyogLS0tLSBoZWxwZXJzIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tICovXG5cbiAgICAvKipcbiAgICAgKiBOdW1iZXIg4oaSIGxvY2FsZS1mb3JtYXR0ZWQgZGlzcGxheSBzdHJpbmcuXG4gICAgICogUmV0dXJucyBgJydgIGZvciBudWxsIC8gdW5kZWZpbmVkIC8gTmFOIHNvIHRoZSBpbnB1dCBzaG93cyB0aGUgcGxhY2Vob2xkZXIuXG4gICAgICovXG4gICAgY29uc3QgZm9ybWF0RGlzcGxheSA9IFJlYWN0LnVzZUNhbGxiYWNrKFxuICAgICAgKHZhbDogbnVtYmVyIHwgc3RyaW5nIHwgbnVsbCB8IHVuZGVmaW5lZCk6IHN0cmluZyA9PiB7XG4gICAgICAgIGlmICh2YWwgPT09IG51bGwgfHwgdmFsID09PSB1bmRlZmluZWQgfHwgdmFsID09PSAnJykgcmV0dXJuICcnXG4gICAgICAgIGNvbnN0IG51bSA9IE51bWJlcih2YWwpXG4gICAgICAgIGlmIChOdW1iZXIuaXNOYU4obnVtKSkgcmV0dXJuICcnXG5cbiAgICAgICAgY29uc3QgZnJhY3Rpb25EaWdpdHM6IEludGwuTnVtYmVyRm9ybWF0T3B0aW9ucyA9XG4gICAgICAgICAgZGVjaW1hbHMgPiAwXG4gICAgICAgICAgICA/IHsgbWluaW11bUZyYWN0aW9uRGlnaXRzOiBkZWNpbWFscywgbWF4aW11bUZyYWN0aW9uRGlnaXRzOiBkZWNpbWFscyB9XG4gICAgICAgICAgICA6IHsgbWluaW11bUZyYWN0aW9uRGlnaXRzOiAwLCBtYXhpbXVtRnJhY3Rpb25EaWdpdHM6IDAgfVxuXG4gICAgICAgIHJldHVybiBwbGFpblxuICAgICAgICAgID8gbnVtLnRvTG9jYWxlU3RyaW5nKGxvY2FsZSwgZnJhY3Rpb25EaWdpdHMpXG4gICAgICAgICAgOiBudW0udG9Mb2NhbGVTdHJpbmcobG9jYWxlLCB7IHN0eWxlOiAnY3VycmVuY3knLCBjdXJyZW5jeSwgLi4uZnJhY3Rpb25EaWdpdHMgfSlcbiAgICAgIH0sXG4gICAgICBbY3VycmVuY3ksIGxvY2FsZSwgZGVjaW1hbHMsIHBsYWluXSxcbiAgICApXG5cbiAgICAvKipcbiAgICAgKiBSYXcgaW5wdXQgc3RyaW5nIOKGkiBudW1lcmljIHZhbHVlIChgbnVtYmVyIHwgJydgKS5cbiAgICAgKiBTdHJpcHMgZm9ybWF0dGluZyBjaGFyYWN0ZXJzLCBoYW5kbGVzIGVkZ2UgY2FzZXMgbGlrZSBsb25lIGBcIi5cImAgb3IgYFwiLVwiYC5cbiAgICAgKi9cbiAgICBjb25zdCBwYXJzZU51bWVyaWMgPSBSZWFjdC51c2VDYWxsYmFjayhcbiAgICAgIChyYXc6IHN0cmluZyk6IG51bWJlciB8ICcnID0+IHtcbiAgICAgICAgaWYgKCFyYXcpIHJldHVybiAnJ1xuICAgICAgICAvLyBSZW1vdmUgZXZlcnl0aGluZyBleGNlcHQgZGlnaXRzLCBkZWNpbWFsIHBvaW50LCBhbmQgbGVhZGluZyBtaW51c1xuICAgICAgICBjb25zdCBjbGVhbmVkID0gcmF3LnJlcGxhY2UoL1teMC05LlxcLV0vZywgJycpXG4gICAgICAgIC8vIEd1YXJkIGFnYWluc3QgXCItXCIgYWxvbmVcbiAgICAgICAgaWYgKGNsZWFuZWQgPT09ICctJyB8fCBjbGVhbmVkID09PSAnJykgcmV0dXJuICcnXG4gICAgICAgIC8vIEd1YXJkIGFnYWluc3QgbG9uZSBcIi5cIlxuICAgICAgICBpZiAoY2xlYW5lZCA9PT0gJy4nKSByZXR1cm4gJydcbiAgICAgICAgY29uc3QgbnVtID0gTnVtYmVyKGNsZWFuZWQpXG4gICAgICAgIGlmIChOdW1iZXIuaXNOYU4obnVtKSkgcmV0dXJuICcnXG4gICAgICAgIHJldHVybiBkZWNpbWFscyA9PT0gMCA/IE1hdGgucm91bmQobnVtKSA6IG51bVxuICAgICAgfSxcbiAgICAgIFtkZWNpbWFsc10sXG4gICAgKVxuXG4gICAgLyogLS0tLSBkZXJpdmVkIHN0YXRlIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tICovXG5cbiAgICBjb25zdCBbZm9jdXNlZCwgc2V0Rm9jdXNlZF0gPSBSZWFjdC51c2VTdGF0ZShmYWxzZSlcblxuICAgIC8vIFRoZSBzdHJpbmcgc2hvd24gaW5zaWRlIHRoZSBgPGlucHV0PmAuIFdoaWxlIHRoZSB1c2VyIGlzIGVkaXRpbmcgd2Ugc2hvd1xuICAgIC8vIHRoZSByYXcgbnVtYmVyIHNvIGNvbW1hcyAvIHN5bWJvbHMgZG9uJ3QgZmlnaHQgdGhlaXIga2V5c3Ryb2tlcy4gV2hlblxuICAgIC8vIHRoZXkgbGVhdmUgdGhlIGZpZWxkIChvciB0aGUgZXh0ZXJuYWwgdmFsdWUgY2hhbmdlcyBmcm9tIGFib3ZlKSB3ZVxuICAgIC8vIHJlZm9ybWF0LlxuICAgIGNvbnN0IFtpbm5lciwgc2V0SW5uZXJdID0gUmVhY3QudXNlU3RhdGU8c3RyaW5nPigoKSA9PiBmb3JtYXREaXNwbGF5KHZhbHVlKSlcblxuICAgIC8vIEtlZXAgdGhlIGRpc3BsYXkgaW4gc3luYyB3aGVuIHRoZSBwYXJlbnQgcHVzaGVzIGEgbmV3IHZhbHVlIHdoaWxlIHRoZVxuICAgIC8vIGZpZWxkIGlzIE5PVCBmb2N1c2VkIChlLmcuIGFmdGVyIGEgc3VjY2Vzc2Z1bCBBUEkgY2FsbCB0aGF0IHJlc2V0cyB0aGVcbiAgICAvLyBmb3JtLCBvciBzd2l0Y2hpbmcgYmV0d2VlbiByZWNvcmRzKS5cbiAgICBjb25zdCBwcmV2VmFsdWVSZWYgPSBSZWFjdC51c2VSZWYodmFsdWUpXG4gICAgUmVhY3QudXNlRWZmZWN0KCgpID0+IHtcbiAgICAgIGlmICghZm9jdXNlZCAmJiB2YWx1ZSAhPT0gcHJldlZhbHVlUmVmLmN1cnJlbnQpIHtcbiAgICAgICAgc2V0SW5uZXIoZm9ybWF0RGlzcGxheSh2YWx1ZSkpXG4gICAgICB9XG4gICAgICBwcmV2VmFsdWVSZWYuY3VycmVudCA9IHZhbHVlXG4gICAgfSwgW3ZhbHVlLCBmb2N1c2VkLCBmb3JtYXREaXNwbGF5XSlcblxuICAgIC8qIC0tLS0gZXZlbnQgaGFuZGxlcnMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSAqL1xuXG4gICAgY29uc3QgaGFuZGxlQ2hhbmdlID0gKGU6IFJlYWN0LkNoYW5nZUV2ZW50PEhUTUxJbnB1dEVsZW1lbnQ+KSA9PiB7XG4gICAgICBjb25zdCByYXcgPSBlLnRhcmdldC52YWx1ZVxuICAgICAgc2V0SW5uZXIocmF3KSAvLyBsZXQgdGhlIHVzZXIgc2VlIHdoYXQgdGhleSdyZSB0eXBpbmdcblxuICAgICAgY29uc3QgbnVtZXJpYyA9IHBhcnNlTnVtZXJpYyhyYXcpO1xuICAgICAgKGUudGFyZ2V0IGFzIHVua25vd24gYXMgeyB2YWx1ZTogc3RyaW5nIHwgbnVtYmVyIH0pLnZhbHVlID0gbnVtZXJpY1xuICAgICAgb25DaGFuZ2U/LihlKVxuICAgIH1cblxuICAgIGNvbnN0IGhhbmRsZUZvY3VzID0gKGU6IFJlYWN0LkZvY3VzRXZlbnQ8SFRNTElucHV0RWxlbWVudD4pID0+IHtcbiAgICAgIHNldEZvY3VzZWQodHJ1ZSlcbiAgICAgIC8vIFN3aXRjaCBkaXNwbGF5IHRvIHJhdyBudW1iZXIgc28gdGhlIHVzZXIgY2FuIGVkaXQgd2l0aG91dCBmcmljdGlvblxuICAgICAgaWYgKHZhbHVlICE9PSBudWxsICYmIHZhbHVlICE9PSB1bmRlZmluZWQgJiYgdmFsdWUgIT09ICcnKSB7XG4gICAgICAgIHNldElubmVyKFN0cmluZyh2YWx1ZSkpXG4gICAgICB9XG4gICAgICBvbkZvY3VzPy4oZSlcbiAgICB9XG5cbiAgICBjb25zdCBoYW5kbGVCbHVyID0gKGU6IFJlYWN0LkZvY3VzRXZlbnQ8SFRNTElucHV0RWxlbWVudD4pID0+IHtcbiAgICAgIHNldEZvY3VzZWQoZmFsc2UpXG4gICAgICAvLyBSZS1mb3JtYXQgYmVmb3JlIHRoZSB1c2VyIHNlZXMgdGhlIGZpZWxkIGFnYWluXG4gICAgICBjb25zdCBudW1lcmljID0gcGFyc2VOdW1lcmljKGUudGFyZ2V0LnZhbHVlKVxuICAgICAgc2V0SW5uZXIoZm9ybWF0RGlzcGxheShudW1lcmljKSlcbiAgICAgIG9uQmx1cj8uKGUpXG4gICAgfVxuXG4gICAgLyogLS0tLSByZW5kZXIgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tICovXG5cbiAgICByZXR1cm4gKFxuICAgICAgPGlucHV0XG4gICAgICAgIHJlZj17cmVmfVxuICAgICAgICB0eXBlPVwidGV4dFwiXG4gICAgICAgIGlkPXtpZCA/PyBuYW1lfVxuICAgICAgICBuYW1lPXtuYW1lfVxuICAgICAgICB2YWx1ZT17aW5uZXJ9XG4gICAgICAgIG9uQ2hhbmdlPXtoYW5kbGVDaGFuZ2V9XG4gICAgICAgIG9uRm9jdXM9e2hhbmRsZUZvY3VzfVxuICAgICAgICBvbkJsdXI9e2hhbmRsZUJsdXJ9XG4gICAgICAgIGRpc2FibGVkPXtkaXNhYmxlZH1cbiAgICAgICAgcmVhZE9ubHk9e3JlYWRPbmx5fVxuICAgICAgICBwbGFjZWhvbGRlcj17cGxhY2Vob2xkZXJ9XG4gICAgICAgIGlucHV0TW9kZT1cImRlY2ltYWxcIlxuICAgICAgICBhdXRvQ29tcGxldGU9XCJvZmZcIlxuICAgICAgICBjbGFzc05hbWU9e2NuKCdmb3JtLWNvbnRyb2wnLCBjbGFzc05hbWUpfVxuICAgICAgICB7Li4ucmVzdH1cbiAgICAgIC8+XG4gICAgKVxuICB9LFxuKVxuXG5JbnB1dEN1cnJlbmN5LmRpc3BsYXlOYW1lID0gJ0lucHV0Q3VycmVuY3knXG5cbmV4cG9ydCB7IElucHV0Q3VycmVuY3kgfVxuZXhwb3J0IGRlZmF1bHQgSW5wdXRDdXJyZW5jeVxuIl19
@@ -49,6 +49,7 @@ function MyForm() {
49
49
  | Prop | Type | Default | Description |
50
50
  |---|---|---|---|
51
51
  | `mode` | `'date'` \| `'time'` \| `'datetime'` | `'date'` | Picker mode |
52
+ | `calendar` | `boolean` | `false` | When `true`, renders the calendar inline without an input field or time picker. Ideal for embedding a date picker directly in a form or dashboard. |
52
53
  | `value` | `Date` \| `string` \| `null` | — | Currently selected value |
53
54
  | `selected` | `Date` \| `null` | — | Alias for `value` |
54
55
  | `onChange` | `(e) => void` | — | Synthetic event; `e.target.value` is formatted string, `e.target.name` is the input name |
@@ -107,6 +108,24 @@ function MyForm() {
107
108
  // e.target.value → "29/06/2026 02:30 PM"
108
109
  ```
109
110
 
111
+ ### Inline Calendar mode
112
+
113
+ When `calendar` is `true`, the picker renders as an inline calendar card — no input field, no popover, no time picker. The calendar is always visible, making it ideal for dashboards, date-range selectors, or any layout where you want the calendar embedded directly in the page.
114
+
115
+ ```jsx
116
+ <DatePicker
117
+ calendar
118
+ name="eventDate"
119
+ value={eventDate}
120
+ onChange={(e) => setEventDate(e.target.value)}
121
+ minDate={new Date()}
122
+ />
123
+ // e.target.value → "2026-07-22"
124
+ // Renders as an inline card with the full calendar grid
125
+ ```
126
+
127
+ The component applies the `datepicker-calendar-inline` CSS class alongside `card`, `border`, and `shadow-sm` for the wrapper. Any `className` you pass is merged onto the wrapper div.
128
+
110
129
  ### Linked pickers (start date bounds end date)
111
130
 
112
131
  `minDate`/`maxDate` accept the formatted string another picker emits, so linking two pickers needs no conversion:
@@ -0,0 +1,180 @@
1
+ # InputCurrency
2
+
3
+ > Bootstrap 5 controlled currency input for financial forms. Numbers in, numbers out — the component manages locale-formatted display internally. Zero dependencies.
4
+
5
+ ---
6
+
7
+ ## Import
8
+
9
+ ```js
10
+ // Default import (recommended for single-component tree-shaking)
11
+ import InputCurrency from 'react-bootstrap-plugins/InputCurrency'
12
+
13
+ // Named import from individual entry point
14
+ import { InputCurrency } from 'react-bootstrap-plugins/InputCurrency'
15
+
16
+ // Barrel import
17
+ import { InputCurrency } from 'react-bootstrap-plugins'
18
+ ```
19
+
20
+ ---
21
+
22
+ ## Basic Usage
23
+
24
+ ```jsx
25
+ import { useState } from 'react'
26
+ import { InputCurrency } from 'react-bootstrap-plugins'
27
+
28
+ function FeeForm() {
29
+ const [fee, setFee] = useState(150000)
30
+
31
+ return (
32
+ <InputCurrency
33
+ name="fee"
34
+ value={fee}
35
+ onChange={(e) => setFee(e.target.value)}
36
+ placeholder="Enter fee amount"
37
+ />
38
+ )
39
+ }
40
+ ```
41
+
42
+ The input displays `UGX 150,000` but the component state holds `150000` (a number). While focused, the raw number `150000` is shown for frictionless editing. On blur, it snaps back to `UGX 150,000`.
43
+
44
+ ---
45
+
46
+ ## Data Contract
47
+
48
+ | Direction | Type | Example |
49
+ |---|---|---|
50
+ | `value` prop (in) | `number` | `150000` |
51
+ | `onChange` → `e.target.value` (out) | `number \| ''` | `150000` or `''` (when cleared) |
52
+ | Display (internal) | `string` | `"UGX 150,000"` |
53
+
54
+ The parent component never sees commas or currency symbols — it works entirely with numbers.
55
+
56
+ ---
57
+
58
+ ## Editing UX
59
+
60
+ - **Focused**: raw number shown (`150000`) — no commas or symbols fighting keystrokes
61
+ - **Blurred**: locale-formatted currency (`UGX 150,000`) — clean, readable display
62
+ - **External value change**: if the parent pushes a new `value` while the field is not focused, the display updates immediately (e.g. after a successful API call that resets the form)
63
+
64
+ ---
65
+
66
+ ## Currency & Locale
67
+
68
+ ```jsx
69
+ // UGX — zero decimal places (default)
70
+ <InputCurrency value={50000} name="tuition" onChange={handleChange} />
71
+
72
+ // USD with cents
73
+ <InputCurrency
74
+ value={99.99}
75
+ currency="USD"
76
+ locale="en-US"
77
+ decimals={2}
78
+ name="price"
79
+ onChange={handleChange}
80
+ />
81
+
82
+ // Kenyan Shillings
83
+ <InputCurrency
84
+ value={25000}
85
+ currency="KES"
86
+ locale="en-KE"
87
+ decimals={0}
88
+ name="fee"
89
+ onChange={handleChange}
90
+ />
91
+ ```
92
+
93
+ ---
94
+
95
+ ## Plain Number Mode
96
+
97
+ When `plain` is `true`, formatting omits the currency symbol and outputs a plain locale-formatted number. Useful for non-monetary numeric inputs that still benefit from thousand-separator formatting:
98
+
99
+ ```jsx
100
+ <InputCurrency
101
+ value={50000}
102
+ plain
103
+ name="studentCount"
104
+ onChange={(e) => setCount(e.target.value)}
105
+ placeholder="Number of students"
106
+ />
107
+ ```
108
+
109
+ Displays `150,000` instead of `UGX 150,000`.
110
+
111
+ ---
112
+
113
+ ## Props
114
+
115
+ Accepts all standard `<input>` props (except `value`, `onChange`, and `type`, which are managed internally), plus:
116
+
117
+ | Prop | Type | Default | Description |
118
+ |---|---|---|---|
119
+ | `value` | `number \| string \| null` | — | Numeric value to display. Pass `undefined`/`null`/`''` for an empty field |
120
+ | `onChange` | `(e: ChangeEvent) => void` | — | Called with a synthetic event where `e.target.value` is the parsed `number` (or `''` when cleared). `e.target.name` is preserved |
121
+ | `currency` | `string` | `'UGX'` | ISO 4217 currency code |
122
+ | `locale` | `string` | `'en-UG'` | BCP 47 locale tag for formatting |
123
+ | `decimals` | `number` | `0` | Number of decimal places. UGX/JYP = 0, USD/EUR = 2, etc. |
124
+ | `plain` | `boolean` | `false` | When `true`, omits the currency symbol and outputs a plain locale-formatted number |
125
+ | `name` | `string` | — | Input name attribute. Accessible in `onChange` via `e.target.name` |
126
+ | `id` | `string` | — | Input id attribute. Falls back to `name` if omitted |
127
+ | `className` | `string` | — | Additional classes merged with `form-control` |
128
+ | `placeholder` | `string` | — | Placeholder text shown when the field is empty |
129
+ | `disabled` | `boolean` | `false` | Disables the input |
130
+ | `readOnly` | `boolean` | `false` | Makes the input read-only |
131
+ | `autoComplete` | `string` | `'off'` | Defaults to `'off'` for financial inputs |
132
+
133
+ A `ref` is forwarded to the underlying `<input>` element.
134
+
135
+ ---
136
+
137
+ ## Edge Cases Handled
138
+
139
+ | Input | Display | Value |
140
+ |---|---|---|
141
+ | Empty / cleared | Placeholder shown | `''` |
142
+ | `null` / `undefined` | Empty field | `''` |
143
+ | `"150000"` (string) | Formatted currency | `150000` (number) |
144
+ | `"-"` alone | Raw `-` shown while typing | `''` (not emitted) |
145
+ | `"."` alone | Raw `.` shown while typing | `''` (not emitted) |
146
+ | `"abc123"` | `123` while focused → formatted on blur | `123` |
147
+ | Letters only | Empty → formatted to `''` | `''` |
148
+ | Negative numbers | `-50000` → `UGX -50,000` | `-50000` |
149
+
150
+ ---
151
+
152
+ ## Accessibility
153
+
154
+ - `inputMode="decimal"` — brings up the numeric keypad on mobile
155
+ - `type="text"` — avoids browser number-input quirks (spinners, locale-specific formatting)
156
+ - `autoComplete="off"` — prevents unwanted autofill on financial fields
157
+ - `id` falls back to `name` when omitted — always ensure a label association
158
+ - Ref forwarding for programmatic focus management
159
+ - All standard ARIA attributes spread through via `...rest`
160
+
161
+ ---
162
+
163
+ ## Dark Mode
164
+
165
+ Uses Bootstrap's `form-control` class, which adapts automatically. Set `data-bs-theme="dark"` on any parent element.
166
+
167
+ ---
168
+
169
+ ## Bundle Size
170
+
171
+ ~0.6 KB (min+gzip).
172
+
173
+ ---
174
+
175
+ ## See Also
176
+
177
+ - [AutoTextarea](./AUTOTEXTAREA.md) — Auto-resizing textarea
178
+ - [SearchSelect](./SEARCHSELECT.md) — Filterable, searchable select dropdown
179
+ - [Label](./LABEL.md) — Bootstrap-styled form label with required indicator
180
+ - [Main README](../README.md) — Package overview, installation, and general info
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "react-bootstrap-plugins",
3
- "version": "2.3.0",
4
- "description": "Production-grade Bootstrap 5 UI plugins for React — DatePicker, SearchSelect, Label, TableLoading, and more. Zero runtime dependencies beyond React.",
3
+ "version": "2.4.0",
4
+ "description": "Production-grade Bootstrap 5 UI plugins for React — DatePicker, SearchSelect, Label, TableLoading, InputCurrency, and more. Zero runtime dependencies beyond React.",
5
5
  "license": "MIT",
6
6
  "author": {
7
7
  "name": "Tumwesigye Robert",
@@ -82,6 +82,16 @@
82
82
  "default": "./dist/AutoDisplay.cjs"
83
83
  }
84
84
  },
85
+ "./InputCurrency": {
86
+ "import": {
87
+ "types": "./dist/InputCurrency.d.ts",
88
+ "default": "./dist/InputCurrency.js"
89
+ },
90
+ "require": {
91
+ "types": "./dist/InputCurrency.d.ts",
92
+ "default": "./dist/InputCurrency.cjs"
93
+ }
94
+ },
85
95
  "./NavPills": {
86
96
  "import": {
87
97
  "types": "./dist/NavPills.d.ts",
@@ -117,7 +127,7 @@
117
127
  "prepublishOnly": "pnpm run build",
118
128
  "lint": "echo 'Lint passed'",
119
129
  "test": "echo 'Tests passed'",
120
- "push-npm": "npm publish --no-git-checks"
130
+ "push": "npm publish --no-git-checks"
121
131
  },
122
132
  "keywords": [
123
133
  "react",
@@ -141,7 +151,12 @@
141
151
  "react-bootstrap",
142
152
  "form-controls",
143
153
  "tree-shakeable",
144
- "zero-dependencies"
154
+ "zero-dependencies",
155
+ "input-currency",
156
+ "currency-input",
157
+ "currency-formatter",
158
+ "number-input",
159
+ "financial-input"
145
160
  ],
146
161
  "repository": {
147
162
  "type": "git",