@transferwise/components 0.0.0-experimental-5f4dd6a → 0.0.0-experimental-cdd318f

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.
Files changed (46) hide show
  1. package/build/field/Field.js +63 -32
  2. package/build/field/Field.js.map +1 -1
  3. package/build/field/Field.messages.js +14 -0
  4. package/build/field/Field.messages.js.map +1 -0
  5. package/build/field/Field.messages.mjs +10 -0
  6. package/build/field/Field.messages.mjs.map +1 -0
  7. package/build/field/Field.mjs +65 -34
  8. package/build/field/Field.mjs.map +1 -1
  9. package/build/i18n/en.json +1 -0
  10. package/build/i18n/en.json.js +1 -0
  11. package/build/i18n/en.json.js.map +1 -1
  12. package/build/i18n/en.json.mjs +1 -0
  13. package/build/i18n/en.json.mjs.map +1 -1
  14. package/build/inputs/TextArea.js +43 -0
  15. package/build/inputs/TextArea.js.map +1 -1
  16. package/build/inputs/TextArea.mjs +45 -2
  17. package/build/inputs/TextArea.mjs.map +1 -1
  18. package/build/inputs/contexts.js +16 -0
  19. package/build/inputs/contexts.js.map +1 -1
  20. package/build/inputs/contexts.mjs +16 -2
  21. package/build/inputs/contexts.mjs.map +1 -1
  22. package/build/main.css +21 -3
  23. package/build/styles/field/Field.css +19 -3
  24. package/build/styles/main.css +21 -3
  25. package/build/types/field/Field.d.ts.map +1 -1
  26. package/build/types/field/Field.messages.d.ts +8 -0
  27. package/build/types/field/Field.messages.d.ts.map +1 -0
  28. package/build/types/inputs/TextArea.d.ts.map +1 -1
  29. package/build/types/inputs/contexts.d.ts +6 -0
  30. package/build/types/inputs/contexts.d.ts.map +1 -1
  31. package/build/types/test-utils/index.d.ts +2 -0
  32. package/build/types/test-utils/index.d.ts.map +1 -1
  33. package/package.json +2 -2
  34. package/src/field/Field.css +19 -3
  35. package/src/field/Field.less +17 -3
  36. package/src/field/Field.messages.ts +8 -0
  37. package/src/field/Field.story.tsx +5 -1
  38. package/src/field/Field.test.tsx +90 -0
  39. package/src/field/Field.tsx +84 -37
  40. package/src/i18n/en.json +1 -0
  41. package/src/inputs/TextArea.story.tsx +93 -0
  42. package/src/inputs/TextArea.test.story.tsx +142 -0
  43. package/src/inputs/TextArea.tsx +54 -3
  44. package/src/inputs/contexts.tsx +18 -1
  45. package/src/main.css +21 -3
  46. package/src/textareaWithDisplayFormat/TextareaWithDisplayFormat.story.tsx +1 -0
@@ -2,6 +2,8 @@
2
2
 
3
3
  var clsx = require('clsx');
4
4
  var React = require('react');
5
+ var reactIntl = require('react-intl');
6
+ var Body = require('../body/Body.js');
5
7
  require('../common/theme.js');
6
8
  require('../common/direction.js');
7
9
  require('../common/propsValues/control.js');
@@ -23,11 +25,10 @@ require('../common/propsValues/markdownNodeType.js');
23
25
  require('../common/fileType.js');
24
26
  require('@transferwise/formatting');
25
27
  require('@transferwise/icons');
26
- require('react-intl');
27
28
  require('../common/closeButton/CloseButton.messages.js');
28
29
  var jsxRuntime = require('react/jsx-runtime');
30
+ var Field_messages = require('./Field.messages.js');
29
31
  var InlinePrompt = require('../prompt/InlinePrompt/InlinePrompt.js');
30
- require('../body/Body.js');
31
32
  require('../button/Button.resolver.js');
32
33
  require('../prompt/PrimitivePrompt/PrimitivePrompt.js');
33
34
  require('../badge/Badge.js');
@@ -56,6 +57,9 @@ const Field = ({
56
57
  children,
57
58
  ...props
58
59
  }) => {
60
+ const {
61
+ formatMessage
62
+ } = reactIntl.useIntl();
59
63
  const labelRef = React.useRef(null);
60
64
  const sentiment$1 = props.error ? sentiment.Sentiment.NEGATIVE : propType;
61
65
  const message = propMessage || props.error;
@@ -65,6 +69,10 @@ const Field = ({
65
69
  const inputId = id !== null ? id ?? fallbackInputId : undefined;
66
70
  const messageId = React.useId();
67
71
  const descriptionId = React.useId();
72
+ const textareaCharCounterId = React.useId();
73
+ const [textareaCharacterCount, setTextareaCharacterCount] = React.useState(null);
74
+ const handleTextareaCharacterCount = React.useCallback(state => setTextareaCharacterCount(state), []);
75
+ const isNearCharLimit = textareaCharacterCount != null && textareaCharacterCount.current >= textareaCharacterCount.max * 0.8;
68
76
  /**
69
77
  * form control can have multiple messages to describe it,
70
78
  * e.g the description underneath the label and inline alert
@@ -77,6 +85,9 @@ const Field = ({
77
85
  if (message) {
78
86
  messageIds.push(messageId);
79
87
  }
88
+ if (textareaCharacterCount) {
89
+ messageIds.push(textareaCharCounterId);
90
+ }
80
91
  return messageIds.length > 0 ? messageIds.join(' ') : undefined;
81
92
  }
82
93
  return /*#__PURE__*/jsxRuntime.jsx(contexts.FieldLabelContextProvider, {
@@ -90,37 +101,57 @@ const Field = ({
90
101
  value: ariaDescribedbyByIds(),
91
102
  children: /*#__PURE__*/jsxRuntime.jsx(contexts.InputInvalidProvider, {
92
103
  value: hasError,
93
- children: /*#__PURE__*/jsxRuntime.jsxs("div", {
94
- className: clsx.clsx('np-field form-group d-block', {
95
- 'has-success': sentiment$1 === sentiment.Sentiment.POSITIVE,
96
- 'has-warning': sentiment$1 === sentiment.Sentiment.WARNING,
97
- 'has-error': hasError,
98
- 'has-info': sentiment$1 === sentiment.Sentiment.NEUTRAL
99
- }, className),
100
- children: [label != null ? /*#__PURE__*/jsxRuntime.jsxs(jsxRuntime.Fragment, {
101
- children: [/*#__PURE__*/jsxRuntime.jsx(Label.Label, {
102
- ref: labelRef,
103
- id: labelId,
104
- htmlFor: inputId,
105
- children: required ? label : /*#__PURE__*/jsxRuntime.jsx(Label.Label.Optional, {
106
- children: label
107
- })
108
- }), /*#__PURE__*/jsxRuntime.jsx(Label.Label.Description, {
109
- id: descriptionId,
110
- children: description
111
- }), /*#__PURE__*/jsxRuntime.jsx("div", {
112
- className: "np-field-control",
113
- children: children
104
+ children: /*#__PURE__*/jsxRuntime.jsx(contexts.TextareaCharacterCountProvider, {
105
+ value: handleTextareaCharacterCount,
106
+ children: /*#__PURE__*/jsxRuntime.jsxs("div", {
107
+ className: clsx.clsx('np-field form-group d-block', {
108
+ 'has-success': sentiment$1 === sentiment.Sentiment.POSITIVE,
109
+ 'has-warning': sentiment$1 === sentiment.Sentiment.WARNING,
110
+ 'has-error': hasError,
111
+ 'has-info': sentiment$1 === sentiment.Sentiment.NEUTRAL
112
+ }, className),
113
+ children: [label != null ? /*#__PURE__*/jsxRuntime.jsxs(jsxRuntime.Fragment, {
114
+ children: [/*#__PURE__*/jsxRuntime.jsx(Label.Label, {
115
+ ref: labelRef,
116
+ id: labelId,
117
+ htmlFor: inputId,
118
+ children: required ? label : /*#__PURE__*/jsxRuntime.jsx(Label.Label.Optional, {
119
+ children: label
120
+ })
121
+ }), /*#__PURE__*/jsxRuntime.jsx(Label.Label.Description, {
122
+ id: descriptionId,
123
+ children: description
124
+ }), /*#__PURE__*/jsxRuntime.jsx("div", {
125
+ className: "np-field-control",
126
+ children: children
127
+ })]
128
+ }) : children, (message || textareaCharacterCount) && /*#__PURE__*/jsxRuntime.jsxs("div", {
129
+ className: "np-field-validation",
130
+ children: [message && /*#__PURE__*/jsxRuntime.jsx(InlinePrompt.InlinePrompt, {
131
+ sentiment: sentiment$1,
132
+ id: messageId,
133
+ mediaLabel: messageIconLabel,
134
+ className: "np-field__prompt",
135
+ loading: messageLoading,
136
+ width: "full",
137
+ children: message
138
+ }), textareaCharacterCount && /*#__PURE__*/jsxRuntime.jsxs(Body.default, {
139
+ as: "span",
140
+ id: textareaCharCounterId,
141
+ ...(isNearCharLimit ? {
142
+ role: 'status',
143
+ 'aria-live': 'polite',
144
+ 'aria-atomic': 'true'
145
+ } : {}),
146
+ "aria-label": formatMessage(Field_messages.default.characterCount, {
147
+ current: textareaCharacterCount.current,
148
+ max: textareaCharacterCount.max
149
+ }),
150
+ className: "np-field-textarea-char-counter",
151
+ children: [textareaCharacterCount.current, "/", textareaCharacterCount.max]
152
+ })]
114
153
  })]
115
- }) : children, message && /*#__PURE__*/jsxRuntime.jsx(InlinePrompt.InlinePrompt, {
116
- sentiment: sentiment$1,
117
- id: messageId,
118
- mediaLabel: messageIconLabel,
119
- className: "np-field__prompt",
120
- loading: messageLoading,
121
- width: "full",
122
- children: message
123
- })]
154
+ })
124
155
  })
125
156
  })
126
157
  })
@@ -1 +1 @@
1
- {"version":3,"file":"Field.js","sources":["../../src/field/Field.tsx"],"sourcesContent":["import { clsx } from 'clsx';\nimport { useId, useRef } from 'react';\n\nimport { Sentiment } from '../common';\nimport { InlinePrompt, type InlinePromptProps } from '../prompt';\nimport {\n FieldLabelContextProvider,\n InputDescribedByProvider,\n InputIdContextProvider,\n InputInvalidProvider,\n} from '../inputs/contexts';\nimport { Label } from '../label';\n\nexport type FieldProps = {\n /** `null` disables auto-generating the `id` attribute, falling back to nesting-based label association over setting `htmlFor` explicitly. */\n id?: string | null;\n /** Should be specified unless the wrapped control has its own labeling mechanism, e.g. `Checkbox`. */\n label?: React.ReactNode;\n /** @default true */\n required?: boolean;\n /** @deprecated Use `description` prop instead. */\n hint?: React.ReactNode;\n message?: React.ReactNode;\n /**\n * Override for the [InlinePrompt icon's default, accessible name](/?path=/docs/other-statusicon-accessibility--docs)\n * announced by the screen readers\n * */\n messageIconLabel?: string;\n /**\n * If true, shows a loading spinner in place of the message icon of the InlinePrompt\n * @default false\n */\n messageLoading?: boolean;\n description?: React.ReactNode;\n /** @deprecated Use `message` and `type={Sentiment.NEGATIVE}` prop instead. */\n error?: React.ReactNode;\n /** @default Sentiment.NEUTRAL */\n sentiment?: InlinePromptProps['sentiment'];\n className?: string;\n children?: React.ReactNode;\n};\n\nexport const Field = ({\n id,\n label,\n required = true,\n message: propMessage,\n messageIconLabel,\n messageLoading,\n hint,\n description = hint,\n sentiment: propType = Sentiment.NEUTRAL,\n className,\n children,\n ...props\n}: FieldProps) => {\n const labelRef = useRef<HTMLLabelElement>(null);\n const sentiment = props.error ? Sentiment.NEGATIVE : propType;\n const message = propMessage || props.error;\n const hasError = sentiment === Sentiment.NEGATIVE;\n\n const labelId = useId();\n\n const fallbackInputId = useId();\n const inputId = id !== null ? (id ?? fallbackInputId) : undefined;\n\n const messageId = useId();\n const descriptionId = useId();\n\n /**\n * form control can have multiple messages to describe it,\n * e.g the description underneath the label and inline alert\n */\n function ariaDescribedbyByIds() {\n const messageIds = [];\n if (description) {\n messageIds.push(descriptionId);\n }\n if (message) {\n messageIds.push(messageId);\n }\n return messageIds.length > 0 ? messageIds.join(' ') : undefined;\n }\n\n return (\n <FieldLabelContextProvider value={{ id: labelId, ref: labelRef }}>\n <InputIdContextProvider value={inputId}>\n <InputDescribedByProvider value={ariaDescribedbyByIds()}>\n <InputInvalidProvider value={hasError}>\n <div\n className={clsx(\n 'np-field form-group d-block',\n {\n 'has-success': sentiment === Sentiment.POSITIVE,\n 'has-warning': sentiment === Sentiment.WARNING,\n 'has-error': hasError,\n 'has-info': sentiment === Sentiment.NEUTRAL,\n },\n className,\n )}\n >\n {label != null ? (\n <>\n <Label ref={labelRef} id={labelId} htmlFor={inputId}>\n {required ? label : <Label.Optional>{label}</Label.Optional>}\n </Label>\n <Label.Description id={descriptionId}>{description}</Label.Description>\n <div className=\"np-field-control\">{children}</div>\n </>\n ) : (\n children\n )}\n\n {message && (\n <InlinePrompt\n sentiment={sentiment}\n id={messageId}\n mediaLabel={messageIconLabel}\n className=\"np-field__prompt\"\n loading={messageLoading}\n width=\"full\"\n >\n {message}\n </InlinePrompt>\n )}\n </div>\n </InputInvalidProvider>\n </InputDescribedByProvider>\n </InputIdContextProvider>\n </FieldLabelContextProvider>\n );\n};\n"],"names":["Field","id","label","required","message","propMessage","messageIconLabel","messageLoading","hint","description","sentiment","propType","Sentiment","NEUTRAL","className","children","props","labelRef","useRef","error","NEGATIVE","hasError","labelId","useId","fallbackInputId","inputId","undefined","messageId","descriptionId","ariaDescribedbyByIds","messageIds","push","length","join","_jsx","FieldLabelContextProvider","value","ref","InputIdContextProvider","InputDescribedByProvider","InputInvalidProvider","_jsxs","clsx","POSITIVE","WARNING","_Fragment","Label","htmlFor","Optional","Description","InlinePrompt","mediaLabel","loading","width"],"mappings":"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"}
1
+ {"version":3,"file":"Field.js","sources":["../../src/field/Field.tsx"],"sourcesContent":["import { clsx } from 'clsx';\nimport { useCallback, useId, useRef, useState } from 'react';\nimport { useIntl } from 'react-intl';\n\nimport Body from '../body';\nimport { Sentiment } from '../common';\nimport messages from './Field.messages';\nimport { InlinePrompt, type InlinePromptProps } from '../prompt';\nimport {\n TextareaCharacterCountProvider,\n type TextareaCharacterCountState,\n FieldLabelContextProvider,\n InputDescribedByProvider,\n InputIdContextProvider,\n InputInvalidProvider,\n} from '../inputs/contexts';\nimport { Label } from '../label';\n\nexport type FieldProps = {\n /** `null` disables auto-generating the `id` attribute, falling back to nesting-based label association over setting `htmlFor` explicitly. */\n id?: string | null;\n /** Should be specified unless the wrapped control has its own labeling mechanism, e.g. `Checkbox`. */\n label?: React.ReactNode;\n /** @default true */\n required?: boolean;\n /** @deprecated Use `description` prop instead. */\n hint?: React.ReactNode;\n message?: React.ReactNode;\n /**\n * Override for the [InlinePrompt icon's default, accessible name](/?path=/docs/other-statusicon-accessibility--docs)\n * announced by the screen readers\n * */\n messageIconLabel?: string;\n /**\n * If true, shows a loading spinner in place of the message icon of the InlinePrompt\n * @default false\n */\n messageLoading?: boolean;\n description?: React.ReactNode;\n /** @deprecated Use `message` and `type={Sentiment.NEGATIVE}` prop instead. */\n error?: React.ReactNode;\n /** @default Sentiment.NEUTRAL */\n sentiment?: InlinePromptProps['sentiment'];\n className?: string;\n children?: React.ReactNode;\n};\n\nexport const Field = ({\n id,\n label,\n required = true,\n message: propMessage,\n messageIconLabel,\n messageLoading,\n hint,\n description = hint,\n sentiment: propType = Sentiment.NEUTRAL,\n className,\n children,\n ...props\n}: FieldProps) => {\n const { formatMessage } = useIntl();\n const labelRef = useRef<HTMLLabelElement>(null);\n const sentiment = props.error ? Sentiment.NEGATIVE : propType;\n const message = propMessage || props.error;\n const hasError = sentiment === Sentiment.NEGATIVE;\n\n const labelId = useId();\n\n const fallbackInputId = useId();\n const inputId = id !== null ? (id ?? fallbackInputId) : undefined;\n\n const messageId = useId();\n const descriptionId = useId();\n const textareaCharCounterId = useId();\n\n const [textareaCharacterCount, setTextareaCharacterCount] =\n useState<TextareaCharacterCountState>(null);\n const handleTextareaCharacterCount = useCallback(\n (state: TextareaCharacterCountState) => setTextareaCharacterCount(state),\n [],\n );\n\n const isNearCharLimit =\n textareaCharacterCount != null &&\n textareaCharacterCount.current >= textareaCharacterCount.max * 0.8;\n\n /**\n * form control can have multiple messages to describe it,\n * e.g the description underneath the label and inline alert\n */\n function ariaDescribedbyByIds() {\n const messageIds = [];\n if (description) {\n messageIds.push(descriptionId);\n }\n if (message) {\n messageIds.push(messageId);\n }\n if (textareaCharacterCount) {\n messageIds.push(textareaCharCounterId);\n }\n return messageIds.length > 0 ? messageIds.join(' ') : undefined;\n }\n\n return (\n <FieldLabelContextProvider value={{ id: labelId, ref: labelRef }}>\n <InputIdContextProvider value={inputId}>\n <InputDescribedByProvider value={ariaDescribedbyByIds()}>\n <InputInvalidProvider value={hasError}>\n <TextareaCharacterCountProvider value={handleTextareaCharacterCount}>\n <div\n className={clsx(\n 'np-field form-group d-block',\n {\n 'has-success': sentiment === Sentiment.POSITIVE,\n 'has-warning': sentiment === Sentiment.WARNING,\n 'has-error': hasError,\n 'has-info': sentiment === Sentiment.NEUTRAL,\n },\n className,\n )}\n >\n {label != null ? (\n <>\n <Label ref={labelRef} id={labelId} htmlFor={inputId}>\n {required ? label : <Label.Optional>{label}</Label.Optional>}\n </Label>\n <Label.Description id={descriptionId}>{description}</Label.Description>\n <div className=\"np-field-control\">{children}</div>\n </>\n ) : (\n children\n )}\n\n {(message || textareaCharacterCount) && (\n <div className=\"np-field-validation\">\n {message && (\n <InlinePrompt\n sentiment={sentiment}\n id={messageId}\n mediaLabel={messageIconLabel}\n className=\"np-field__prompt\"\n loading={messageLoading}\n width=\"full\"\n >\n {message}\n </InlinePrompt>\n )}\n {textareaCharacterCount && (\n <Body\n as=\"span\"\n id={textareaCharCounterId}\n {...(isNearCharLimit\n ? {\n role: 'status' as const,\n 'aria-live': 'polite' as const,\n 'aria-atomic': 'true' as const,\n }\n : {})}\n aria-label={formatMessage(messages.characterCount, {\n current: textareaCharacterCount.current,\n max: textareaCharacterCount.max,\n })}\n className=\"np-field-textarea-char-counter\"\n >\n {textareaCharacterCount.current}/{textareaCharacterCount.max}\n </Body>\n )}\n </div>\n )}\n </div>\n </TextareaCharacterCountProvider>\n </InputInvalidProvider>\n </InputDescribedByProvider>\n </InputIdContextProvider>\n </FieldLabelContextProvider>\n );\n};\n"],"names":["Field","id","label","required","message","propMessage","messageIconLabel","messageLoading","hint","description","sentiment","propType","Sentiment","NEUTRAL","className","children","props","formatMessage","useIntl","labelRef","useRef","error","NEGATIVE","hasError","labelId","useId","fallbackInputId","inputId","undefined","messageId","descriptionId","textareaCharCounterId","textareaCharacterCount","setTextareaCharacterCount","useState","handleTextareaCharacterCount","useCallback","state","isNearCharLimit","current","max","ariaDescribedbyByIds","messageIds","push","length","join","_jsx","FieldLabelContextProvider","value","ref","InputIdContextProvider","InputDescribedByProvider","InputInvalidProvider","TextareaCharacterCountProvider","_jsxs","clsx","POSITIVE","WARNING","_Fragment","Label","htmlFor","Optional","Description","InlinePrompt","mediaLabel","loading","width","Body","as","role","messages","characterCount"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+CO,MAAMA,KAAK,GAAGA,CAAC;EACpBC,EAAE;EACFC,KAAK;AACLC,EAAAA,QAAQ,GAAG,IAAI;AACfC,EAAAA,OAAO,EAAEC,WAAW;EACpBC,gBAAgB;EAChBC,cAAc;EACdC,IAAI;AACJC,EAAAA,WAAW,GAAGD,IAAI;AAClBE,EAAAA,SAAS,EAAEC,QAAQ,GAAGC,mBAAS,CAACC,OAAO;EACvCC,SAAS;EACTC,QAAQ;EACR,GAAGC;AAAK,CACG,KAAI;EACf,MAAM;AAAEC,IAAAA;GAAe,GAAGC,iBAAO,EAAE;AACnC,EAAA,MAAMC,QAAQ,GAAGC,YAAM,CAAmB,IAAI,CAAC;EAC/C,MAAMV,WAAS,GAAGM,KAAK,CAACK,KAAK,GAAGT,mBAAS,CAACU,QAAQ,GAAGX,QAAQ;AAC7D,EAAA,MAAMP,OAAO,GAAGC,WAAW,IAAIW,KAAK,CAACK,KAAK;AAC1C,EAAA,MAAME,QAAQ,GAAGb,WAAS,KAAKE,mBAAS,CAACU,QAAQ;AAEjD,EAAA,MAAME,OAAO,GAAGC,WAAK,EAAE;AAEvB,EAAA,MAAMC,eAAe,GAAGD,WAAK,EAAE;EAC/B,MAAME,OAAO,GAAG1B,EAAE,KAAK,IAAI,GAAIA,EAAE,IAAIyB,eAAe,GAAIE,SAAS;AAEjE,EAAA,MAAMC,SAAS,GAAGJ,WAAK,EAAE;AACzB,EAAA,MAAMK,aAAa,GAAGL,WAAK,EAAE;AAC7B,EAAA,MAAMM,qBAAqB,GAAGN,WAAK,EAAE;EAErC,MAAM,CAACO,sBAAsB,EAAEC,yBAAyB,CAAC,GACvDC,cAAQ,CAA8B,IAAI,CAAC;AAC7C,EAAA,MAAMC,4BAA4B,GAAGC,iBAAW,CAC7CC,KAAkC,IAAKJ,yBAAyB,CAACI,KAAK,CAAC,EACxE,EAAE,CACH;AAED,EAAA,MAAMC,eAAe,GACnBN,sBAAsB,IAAI,IAAI,IAC9BA,sBAAsB,CAACO,OAAO,IAAIP,sBAAsB,CAACQ,GAAG,GAAG,GAAG;AAEpE;;;AAGG;EACH,SAASC,oBAAoBA,GAAA;IAC3B,MAAMC,UAAU,GAAG,EAAE;AACrB,IAAA,IAAIjC,WAAW,EAAE;AACfiC,MAAAA,UAAU,CAACC,IAAI,CAACb,aAAa,CAAC;AAChC,IAAA;AACA,IAAA,IAAI1B,OAAO,EAAE;AACXsC,MAAAA,UAAU,CAACC,IAAI,CAACd,SAAS,CAAC;AAC5B,IAAA;AACA,IAAA,IAAIG,sBAAsB,EAAE;AAC1BU,MAAAA,UAAU,CAACC,IAAI,CAACZ,qBAAqB,CAAC;AACxC,IAAA;AACA,IAAA,OAAOW,UAAU,CAACE,MAAM,GAAG,CAAC,GAAGF,UAAU,CAACG,IAAI,CAAC,GAAG,CAAC,GAAGjB,SAAS;AACjE,EAAA;EAEA,oBACEkB,cAAA,CAACC,kCAAyB,EAAA;AAACC,IAAAA,KAAK,EAAE;AAAE/C,MAAAA,EAAE,EAAEuB,OAAO;AAAEyB,MAAAA,GAAG,EAAE9B;KAAW;IAAAJ,QAAA,eAC/D+B,cAAA,CAACI,+BAAsB,EAAA;AAACF,MAAAA,KAAK,EAAErB,OAAQ;MAAAZ,QAAA,eACrC+B,cAAA,CAACK,iCAAwB,EAAA;QAACH,KAAK,EAAEP,oBAAoB,EAAG;QAAA1B,QAAA,eACtD+B,cAAA,CAACM,6BAAoB,EAAA;AAACJ,UAAAA,KAAK,EAAEzB,QAAS;UAAAR,QAAA,eACpC+B,cAAA,CAACO,uCAA8B,EAAA;AAACL,YAAAA,KAAK,EAAEb,4BAA6B;AAAApB,YAAAA,QAAA,eAClEuC,eAAA,CAAA,KAAA,EAAA;AACExC,cAAAA,SAAS,EAAEyC,SAAI,CACb,6BAA6B,EAC7B;AACE,gBAAA,aAAa,EAAE7C,WAAS,KAAKE,mBAAS,CAAC4C,QAAQ;AAC/C,gBAAA,aAAa,EAAE9C,WAAS,KAAKE,mBAAS,CAAC6C,OAAO;AAC9C,gBAAA,WAAW,EAAElC,QAAQ;AACrB,gBAAA,UAAU,EAAEb,WAAS,KAAKE,mBAAS,CAACC;eACrC,EACDC,SAAS,CACT;AAAAC,cAAAA,QAAA,GAEDb,KAAK,IAAI,IAAI,gBACZoD,eAAA,CAAAI,mBAAA,EAAA;gBAAA3C,QAAA,EAAA,cACE+B,cAAA,CAACa,WAAK,EAAA;AAACV,kBAAAA,GAAG,EAAE9B,QAAS;AAAClB,kBAAAA,EAAE,EAAEuB,OAAQ;AAACoC,kBAAAA,OAAO,EAAEjC,OAAQ;kBAAAZ,QAAA,EACjDZ,QAAQ,GAAGD,KAAK,gBAAG4C,cAAA,CAACa,WAAK,CAACE,QAAQ,EAAA;AAAA9C,oBAAAA,QAAA,EAAEb;mBAAsB;AAAC,iBACvD,CACP,eAAA4C,cAAA,CAACa,WAAK,CAACG,WAAW,EAAA;AAAC7D,kBAAAA,EAAE,EAAE6B,aAAc;AAAAf,kBAAAA,QAAA,EAAEN;iBAA+B,CACtE,eAAAqC,cAAA,CAAA,KAAA,EAAA;AAAKhC,kBAAAA,SAAS,EAAC,kBAAkB;AAAAC,kBAAAA,QAAA,EAAEA;AAAQ,iBAAM,CACnD;eAAA,CAAG,GAEHA,QACD,EAEA,CAACX,OAAO,IAAI4B,sBAAsB,kBACjCsB,eAAA,CAAA,KAAA,EAAA;AAAKxC,gBAAAA,SAAS,EAAC,qBAAqB;AAAAC,gBAAAA,QAAA,EAAA,CACjCX,OAAO,iBACN0C,cAAA,CAACiB,yBAAY,EAAA;AACXrD,kBAAAA,SAAS,EAAEA,WAAU;AACrBT,kBAAAA,EAAE,EAAE4B,SAAU;AACdmC,kBAAAA,UAAU,EAAE1D,gBAAiB;AAC7BQ,kBAAAA,SAAS,EAAC,kBAAkB;AAC5BmD,kBAAAA,OAAO,EAAE1D,cAAe;AACxB2D,kBAAAA,KAAK,EAAC,MAAM;AAAAnD,kBAAAA,QAAA,EAEXX;AAAO,iBACI,CACf,EACA4B,sBAAsB,iBACrBsB,eAAA,CAACa,YAAI,EAAA;AACHC,kBAAAA,EAAE,EAAC,MAAM;AACTnE,kBAAAA,EAAE,EAAE8B,qBAAsB;AAAA,kBAAA,IACrBO,eAAe,GAChB;AACE+B,oBAAAA,IAAI,EAAE,QAAiB;AACvB,oBAAA,WAAW,EAAE,QAAiB;AAC9B,oBAAA,aAAa,EAAE;mBAChB,GACD,EAAE,CAAA;AACN,kBAAA,YAAA,EAAYpD,aAAa,CAACqD,sBAAQ,CAACC,cAAc,EAAE;oBACjDhC,OAAO,EAAEP,sBAAsB,CAACO,OAAO;oBACvCC,GAAG,EAAER,sBAAsB,CAACQ;AAC7B,mBAAA,CAAE;AACH1B,kBAAAA,SAAS,EAAC,gCAAgC;kBAAAC,QAAA,EAAA,CAEzCiB,sBAAsB,CAACO,OAAO,EAAC,GAAC,EAACP,sBAAsB,CAACQ,GAAG;AAAA,iBACxD,CACP;AAAA,eACE,CACN;aACE;WACyB;SACZ;OACE;KACJ;AAC1B,GAA2B,CAAC;AAEhC;;;;"}
@@ -0,0 +1,14 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var reactIntl = require('react-intl');
6
+
7
+ var messages = reactIntl.defineMessages({
8
+ characterCount: {
9
+ id: "neptune.Field.characterCount"
10
+ }
11
+ });
12
+
13
+ exports.default = messages;
14
+ //# sourceMappingURL=Field.messages.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Field.messages.js","sources":["../../src/field/Field.messages.ts"],"sourcesContent":["import { defineMessages } from 'react-intl';\n\nexport default defineMessages({\n characterCount: {\n id: 'neptune.Field.characterCount',\n defaultMessage: '{current} of {max} characters used',\n },\n});\n"],"names":["defineMessages","characterCount","id"],"mappings":";;;;;;AAEA,eAAeA,wBAAc,CAAC;AAC5BC,EAAAA,cAAc,EAAE;IACdC,EAAE,EAAA;AAEH;AACF,CAAA,CAAC;;;;"}
@@ -0,0 +1,10 @@
1
+ import { defineMessages } from 'react-intl';
2
+
3
+ var messages = defineMessages({
4
+ characterCount: {
5
+ id: "neptune.Field.characterCount"
6
+ }
7
+ });
8
+
9
+ export { messages as default };
10
+ //# sourceMappingURL=Field.messages.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Field.messages.mjs","sources":["../../src/field/Field.messages.ts"],"sourcesContent":["import { defineMessages } from 'react-intl';\n\nexport default defineMessages({\n characterCount: {\n id: 'neptune.Field.characterCount',\n defaultMessage: '{current} of {max} characters used',\n },\n});\n"],"names":["defineMessages","characterCount","id"],"mappings":";;AAEA,eAAeA,cAAc,CAAC;AAC5BC,EAAAA,cAAc,EAAE;IACdC,EAAE,EAAA;AAEH;AACF,CAAA,CAAC;;;;"}
@@ -1,5 +1,7 @@
1
1
  import { clsx } from 'clsx';
2
- import { useRef, useId } from 'react';
2
+ import { useRef, useId, useState, useCallback } from 'react';
3
+ import { useIntl } from 'react-intl';
4
+ import Body from '../body/Body.mjs';
3
5
  import '../common/theme.mjs';
4
6
  import '../common/direction.mjs';
5
7
  import '../common/propsValues/control.mjs';
@@ -21,11 +23,10 @@ import '../common/propsValues/markdownNodeType.mjs';
21
23
  import '../common/fileType.mjs';
22
24
  import '@transferwise/formatting';
23
25
  import '@transferwise/icons';
24
- import 'react-intl';
25
26
  import '../common/closeButton/CloseButton.messages.mjs';
26
27
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
28
+ import messages from './Field.messages.mjs';
27
29
  import { InlinePrompt } from '../prompt/InlinePrompt/InlinePrompt.mjs';
28
- import '../body/Body.mjs';
29
30
  import '../button/Button.resolver.mjs';
30
31
  import '../prompt/PrimitivePrompt/PrimitivePrompt.mjs';
31
32
  import '../badge/Badge.mjs';
@@ -37,7 +38,7 @@ import '../primitives/PrimitiveButton/PrimitiveButton.mjs';
37
38
  import '../primitives/PrimitiveAnchor/PrimitiveAnchor.mjs';
38
39
  import '../link/Link.messages.mjs';
39
40
  import '../prompt/common/Expander/Expander.messages.mjs';
40
- import { FieldLabelContextProvider, InputIdContextProvider, InputDescribedByProvider, InputInvalidProvider } from '../inputs/contexts.mjs';
41
+ import { FieldLabelContextProvider, InputIdContextProvider, InputDescribedByProvider, InputInvalidProvider, TextareaCharacterCountProvider } from '../inputs/contexts.mjs';
41
42
  import { Label as LabelNamespace } from '../label/Label.mjs';
42
43
 
43
44
  const Field = ({
@@ -54,6 +55,9 @@ const Field = ({
54
55
  children,
55
56
  ...props
56
57
  }) => {
58
+ const {
59
+ formatMessage
60
+ } = useIntl();
57
61
  const labelRef = useRef(null);
58
62
  const sentiment = props.error ? Sentiment.NEGATIVE : propType;
59
63
  const message = propMessage || props.error;
@@ -63,6 +67,10 @@ const Field = ({
63
67
  const inputId = id !== null ? id ?? fallbackInputId : undefined;
64
68
  const messageId = useId();
65
69
  const descriptionId = useId();
70
+ const textareaCharCounterId = useId();
71
+ const [textareaCharacterCount, setTextareaCharacterCount] = useState(null);
72
+ const handleTextareaCharacterCount = useCallback(state => setTextareaCharacterCount(state), []);
73
+ const isNearCharLimit = textareaCharacterCount != null && textareaCharacterCount.current >= textareaCharacterCount.max * 0.8;
66
74
  /**
67
75
  * form control can have multiple messages to describe it,
68
76
  * e.g the description underneath the label and inline alert
@@ -75,6 +83,9 @@ const Field = ({
75
83
  if (message) {
76
84
  messageIds.push(messageId);
77
85
  }
86
+ if (textareaCharacterCount) {
87
+ messageIds.push(textareaCharCounterId);
88
+ }
78
89
  return messageIds.length > 0 ? messageIds.join(' ') : undefined;
79
90
  }
80
91
  return /*#__PURE__*/jsx(FieldLabelContextProvider, {
@@ -88,37 +99,57 @@ const Field = ({
88
99
  value: ariaDescribedbyByIds(),
89
100
  children: /*#__PURE__*/jsx(InputInvalidProvider, {
90
101
  value: hasError,
91
- children: /*#__PURE__*/jsxs("div", {
92
- className: clsx('np-field form-group d-block', {
93
- 'has-success': sentiment === Sentiment.POSITIVE,
94
- 'has-warning': sentiment === Sentiment.WARNING,
95
- 'has-error': hasError,
96
- 'has-info': sentiment === Sentiment.NEUTRAL
97
- }, className),
98
- children: [label != null ? /*#__PURE__*/jsxs(Fragment, {
99
- children: [/*#__PURE__*/jsx(LabelNamespace, {
100
- ref: labelRef,
101
- id: labelId,
102
- htmlFor: inputId,
103
- children: required ? label : /*#__PURE__*/jsx(LabelNamespace.Optional, {
104
- children: label
105
- })
106
- }), /*#__PURE__*/jsx(LabelNamespace.Description, {
107
- id: descriptionId,
108
- children: description
109
- }), /*#__PURE__*/jsx("div", {
110
- className: "np-field-control",
111
- children: children
102
+ children: /*#__PURE__*/jsx(TextareaCharacterCountProvider, {
103
+ value: handleTextareaCharacterCount,
104
+ children: /*#__PURE__*/jsxs("div", {
105
+ className: clsx('np-field form-group d-block', {
106
+ 'has-success': sentiment === Sentiment.POSITIVE,
107
+ 'has-warning': sentiment === Sentiment.WARNING,
108
+ 'has-error': hasError,
109
+ 'has-info': sentiment === Sentiment.NEUTRAL
110
+ }, className),
111
+ children: [label != null ? /*#__PURE__*/jsxs(Fragment, {
112
+ children: [/*#__PURE__*/jsx(LabelNamespace, {
113
+ ref: labelRef,
114
+ id: labelId,
115
+ htmlFor: inputId,
116
+ children: required ? label : /*#__PURE__*/jsx(LabelNamespace.Optional, {
117
+ children: label
118
+ })
119
+ }), /*#__PURE__*/jsx(LabelNamespace.Description, {
120
+ id: descriptionId,
121
+ children: description
122
+ }), /*#__PURE__*/jsx("div", {
123
+ className: "np-field-control",
124
+ children: children
125
+ })]
126
+ }) : children, (message || textareaCharacterCount) && /*#__PURE__*/jsxs("div", {
127
+ className: "np-field-validation",
128
+ children: [message && /*#__PURE__*/jsx(InlinePrompt, {
129
+ sentiment: sentiment,
130
+ id: messageId,
131
+ mediaLabel: messageIconLabel,
132
+ className: "np-field__prompt",
133
+ loading: messageLoading,
134
+ width: "full",
135
+ children: message
136
+ }), textareaCharacterCount && /*#__PURE__*/jsxs(Body, {
137
+ as: "span",
138
+ id: textareaCharCounterId,
139
+ ...(isNearCharLimit ? {
140
+ role: 'status',
141
+ 'aria-live': 'polite',
142
+ 'aria-atomic': 'true'
143
+ } : {}),
144
+ "aria-label": formatMessage(messages.characterCount, {
145
+ current: textareaCharacterCount.current,
146
+ max: textareaCharacterCount.max
147
+ }),
148
+ className: "np-field-textarea-char-counter",
149
+ children: [textareaCharacterCount.current, "/", textareaCharacterCount.max]
150
+ })]
112
151
  })]
113
- }) : children, message && /*#__PURE__*/jsx(InlinePrompt, {
114
- sentiment: sentiment,
115
- id: messageId,
116
- mediaLabel: messageIconLabel,
117
- className: "np-field__prompt",
118
- loading: messageLoading,
119
- width: "full",
120
- children: message
121
- })]
152
+ })
122
153
  })
123
154
  })
124
155
  })
@@ -1 +1 @@
1
- {"version":3,"file":"Field.mjs","sources":["../../src/field/Field.tsx"],"sourcesContent":["import { clsx } from 'clsx';\nimport { useId, useRef } from 'react';\n\nimport { Sentiment } from '../common';\nimport { InlinePrompt, type InlinePromptProps } from '../prompt';\nimport {\n FieldLabelContextProvider,\n InputDescribedByProvider,\n InputIdContextProvider,\n InputInvalidProvider,\n} from '../inputs/contexts';\nimport { Label } from '../label';\n\nexport type FieldProps = {\n /** `null` disables auto-generating the `id` attribute, falling back to nesting-based label association over setting `htmlFor` explicitly. */\n id?: string | null;\n /** Should be specified unless the wrapped control has its own labeling mechanism, e.g. `Checkbox`. */\n label?: React.ReactNode;\n /** @default true */\n required?: boolean;\n /** @deprecated Use `description` prop instead. */\n hint?: React.ReactNode;\n message?: React.ReactNode;\n /**\n * Override for the [InlinePrompt icon's default, accessible name](/?path=/docs/other-statusicon-accessibility--docs)\n * announced by the screen readers\n * */\n messageIconLabel?: string;\n /**\n * If true, shows a loading spinner in place of the message icon of the InlinePrompt\n * @default false\n */\n messageLoading?: boolean;\n description?: React.ReactNode;\n /** @deprecated Use `message` and `type={Sentiment.NEGATIVE}` prop instead. */\n error?: React.ReactNode;\n /** @default Sentiment.NEUTRAL */\n sentiment?: InlinePromptProps['sentiment'];\n className?: string;\n children?: React.ReactNode;\n};\n\nexport const Field = ({\n id,\n label,\n required = true,\n message: propMessage,\n messageIconLabel,\n messageLoading,\n hint,\n description = hint,\n sentiment: propType = Sentiment.NEUTRAL,\n className,\n children,\n ...props\n}: FieldProps) => {\n const labelRef = useRef<HTMLLabelElement>(null);\n const sentiment = props.error ? Sentiment.NEGATIVE : propType;\n const message = propMessage || props.error;\n const hasError = sentiment === Sentiment.NEGATIVE;\n\n const labelId = useId();\n\n const fallbackInputId = useId();\n const inputId = id !== null ? (id ?? fallbackInputId) : undefined;\n\n const messageId = useId();\n const descriptionId = useId();\n\n /**\n * form control can have multiple messages to describe it,\n * e.g the description underneath the label and inline alert\n */\n function ariaDescribedbyByIds() {\n const messageIds = [];\n if (description) {\n messageIds.push(descriptionId);\n }\n if (message) {\n messageIds.push(messageId);\n }\n return messageIds.length > 0 ? messageIds.join(' ') : undefined;\n }\n\n return (\n <FieldLabelContextProvider value={{ id: labelId, ref: labelRef }}>\n <InputIdContextProvider value={inputId}>\n <InputDescribedByProvider value={ariaDescribedbyByIds()}>\n <InputInvalidProvider value={hasError}>\n <div\n className={clsx(\n 'np-field form-group d-block',\n {\n 'has-success': sentiment === Sentiment.POSITIVE,\n 'has-warning': sentiment === Sentiment.WARNING,\n 'has-error': hasError,\n 'has-info': sentiment === Sentiment.NEUTRAL,\n },\n className,\n )}\n >\n {label != null ? (\n <>\n <Label ref={labelRef} id={labelId} htmlFor={inputId}>\n {required ? label : <Label.Optional>{label}</Label.Optional>}\n </Label>\n <Label.Description id={descriptionId}>{description}</Label.Description>\n <div className=\"np-field-control\">{children}</div>\n </>\n ) : (\n children\n )}\n\n {message && (\n <InlinePrompt\n sentiment={sentiment}\n id={messageId}\n mediaLabel={messageIconLabel}\n className=\"np-field__prompt\"\n loading={messageLoading}\n width=\"full\"\n >\n {message}\n </InlinePrompt>\n )}\n </div>\n </InputInvalidProvider>\n </InputDescribedByProvider>\n </InputIdContextProvider>\n </FieldLabelContextProvider>\n );\n};\n"],"names":["Field","id","label","required","message","propMessage","messageIconLabel","messageLoading","hint","description","sentiment","propType","Sentiment","NEUTRAL","className","children","props","labelRef","useRef","error","NEGATIVE","hasError","labelId","useId","fallbackInputId","inputId","undefined","messageId","descriptionId","ariaDescribedbyByIds","messageIds","push","length","join","_jsx","FieldLabelContextProvider","value","ref","InputIdContextProvider","InputDescribedByProvider","InputInvalidProvider","_jsxs","clsx","POSITIVE","WARNING","_Fragment","Label","htmlFor","Optional","Description","InlinePrompt","mediaLabel","loading","width"],"mappings":"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"}
1
+ {"version":3,"file":"Field.mjs","sources":["../../src/field/Field.tsx"],"sourcesContent":["import { clsx } from 'clsx';\nimport { useCallback, useId, useRef, useState } from 'react';\nimport { useIntl } from 'react-intl';\n\nimport Body from '../body';\nimport { Sentiment } from '../common';\nimport messages from './Field.messages';\nimport { InlinePrompt, type InlinePromptProps } from '../prompt';\nimport {\n TextareaCharacterCountProvider,\n type TextareaCharacterCountState,\n FieldLabelContextProvider,\n InputDescribedByProvider,\n InputIdContextProvider,\n InputInvalidProvider,\n} from '../inputs/contexts';\nimport { Label } from '../label';\n\nexport type FieldProps = {\n /** `null` disables auto-generating the `id` attribute, falling back to nesting-based label association over setting `htmlFor` explicitly. */\n id?: string | null;\n /** Should be specified unless the wrapped control has its own labeling mechanism, e.g. `Checkbox`. */\n label?: React.ReactNode;\n /** @default true */\n required?: boolean;\n /** @deprecated Use `description` prop instead. */\n hint?: React.ReactNode;\n message?: React.ReactNode;\n /**\n * Override for the [InlinePrompt icon's default, accessible name](/?path=/docs/other-statusicon-accessibility--docs)\n * announced by the screen readers\n * */\n messageIconLabel?: string;\n /**\n * If true, shows a loading spinner in place of the message icon of the InlinePrompt\n * @default false\n */\n messageLoading?: boolean;\n description?: React.ReactNode;\n /** @deprecated Use `message` and `type={Sentiment.NEGATIVE}` prop instead. */\n error?: React.ReactNode;\n /** @default Sentiment.NEUTRAL */\n sentiment?: InlinePromptProps['sentiment'];\n className?: string;\n children?: React.ReactNode;\n};\n\nexport const Field = ({\n id,\n label,\n required = true,\n message: propMessage,\n messageIconLabel,\n messageLoading,\n hint,\n description = hint,\n sentiment: propType = Sentiment.NEUTRAL,\n className,\n children,\n ...props\n}: FieldProps) => {\n const { formatMessage } = useIntl();\n const labelRef = useRef<HTMLLabelElement>(null);\n const sentiment = props.error ? Sentiment.NEGATIVE : propType;\n const message = propMessage || props.error;\n const hasError = sentiment === Sentiment.NEGATIVE;\n\n const labelId = useId();\n\n const fallbackInputId = useId();\n const inputId = id !== null ? (id ?? fallbackInputId) : undefined;\n\n const messageId = useId();\n const descriptionId = useId();\n const textareaCharCounterId = useId();\n\n const [textareaCharacterCount, setTextareaCharacterCount] =\n useState<TextareaCharacterCountState>(null);\n const handleTextareaCharacterCount = useCallback(\n (state: TextareaCharacterCountState) => setTextareaCharacterCount(state),\n [],\n );\n\n const isNearCharLimit =\n textareaCharacterCount != null &&\n textareaCharacterCount.current >= textareaCharacterCount.max * 0.8;\n\n /**\n * form control can have multiple messages to describe it,\n * e.g the description underneath the label and inline alert\n */\n function ariaDescribedbyByIds() {\n const messageIds = [];\n if (description) {\n messageIds.push(descriptionId);\n }\n if (message) {\n messageIds.push(messageId);\n }\n if (textareaCharacterCount) {\n messageIds.push(textareaCharCounterId);\n }\n return messageIds.length > 0 ? messageIds.join(' ') : undefined;\n }\n\n return (\n <FieldLabelContextProvider value={{ id: labelId, ref: labelRef }}>\n <InputIdContextProvider value={inputId}>\n <InputDescribedByProvider value={ariaDescribedbyByIds()}>\n <InputInvalidProvider value={hasError}>\n <TextareaCharacterCountProvider value={handleTextareaCharacterCount}>\n <div\n className={clsx(\n 'np-field form-group d-block',\n {\n 'has-success': sentiment === Sentiment.POSITIVE,\n 'has-warning': sentiment === Sentiment.WARNING,\n 'has-error': hasError,\n 'has-info': sentiment === Sentiment.NEUTRAL,\n },\n className,\n )}\n >\n {label != null ? (\n <>\n <Label ref={labelRef} id={labelId} htmlFor={inputId}>\n {required ? label : <Label.Optional>{label}</Label.Optional>}\n </Label>\n <Label.Description id={descriptionId}>{description}</Label.Description>\n <div className=\"np-field-control\">{children}</div>\n </>\n ) : (\n children\n )}\n\n {(message || textareaCharacterCount) && (\n <div className=\"np-field-validation\">\n {message && (\n <InlinePrompt\n sentiment={sentiment}\n id={messageId}\n mediaLabel={messageIconLabel}\n className=\"np-field__prompt\"\n loading={messageLoading}\n width=\"full\"\n >\n {message}\n </InlinePrompt>\n )}\n {textareaCharacterCount && (\n <Body\n as=\"span\"\n id={textareaCharCounterId}\n {...(isNearCharLimit\n ? {\n role: 'status' as const,\n 'aria-live': 'polite' as const,\n 'aria-atomic': 'true' as const,\n }\n : {})}\n aria-label={formatMessage(messages.characterCount, {\n current: textareaCharacterCount.current,\n max: textareaCharacterCount.max,\n })}\n className=\"np-field-textarea-char-counter\"\n >\n {textareaCharacterCount.current}/{textareaCharacterCount.max}\n </Body>\n )}\n </div>\n )}\n </div>\n </TextareaCharacterCountProvider>\n </InputInvalidProvider>\n </InputDescribedByProvider>\n </InputIdContextProvider>\n </FieldLabelContextProvider>\n );\n};\n"],"names":["Field","id","label","required","message","propMessage","messageIconLabel","messageLoading","hint","description","sentiment","propType","Sentiment","NEUTRAL","className","children","props","formatMessage","useIntl","labelRef","useRef","error","NEGATIVE","hasError","labelId","useId","fallbackInputId","inputId","undefined","messageId","descriptionId","textareaCharCounterId","textareaCharacterCount","setTextareaCharacterCount","useState","handleTextareaCharacterCount","useCallback","state","isNearCharLimit","current","max","ariaDescribedbyByIds","messageIds","push","length","join","_jsx","FieldLabelContextProvider","value","ref","InputIdContextProvider","InputDescribedByProvider","InputInvalidProvider","TextareaCharacterCountProvider","_jsxs","clsx","POSITIVE","WARNING","_Fragment","Label","htmlFor","Optional","Description","InlinePrompt","mediaLabel","loading","width","Body","as","role","messages","characterCount"],"mappings":"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"}
@@ -20,6 +20,7 @@
20
20
  "neptune.Expander.expandAriaLabel": "Expand",
21
21
  "neptune.ExpressiveMoneyInput.currency.search.placeholder": "Type a currency / country",
22
22
  "neptune.ExpressiveMoneyInput.currency.select.currency": "Select currency",
23
+ "neptune.Field.characterCount": "{current} of {max} characters used",
23
24
  "neptune.FlowNavigation.back": "back to previous step",
24
25
  "neptune.Info.ariaLabel": "More information",
25
26
  "neptune.Label.optional": "(Optional)",
@@ -24,6 +24,7 @@ var en = {
24
24
  "neptune.Expander.expandAriaLabel": "Expand",
25
25
  "neptune.ExpressiveMoneyInput.currency.search.placeholder": "Type a currency / country",
26
26
  "neptune.ExpressiveMoneyInput.currency.select.currency": "Select currency",
27
+ "neptune.Field.characterCount": "{current} of {max} characters used",
27
28
  "neptune.FlowNavigation.back": "back to previous step",
28
29
  "neptune.Info.ariaLabel": "More information",
29
30
  "neptune.Label.optional": "(Optional)",
@@ -1 +1 @@
1
- {"version":3,"file":"en.json.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"en.json.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -20,6 +20,7 @@ var en = {
20
20
  "neptune.Expander.expandAriaLabel": "Expand",
21
21
  "neptune.ExpressiveMoneyInput.currency.search.placeholder": "Type a currency / country",
22
22
  "neptune.ExpressiveMoneyInput.currency.select.currency": "Select currency",
23
+ "neptune.Field.characterCount": "{current} of {max} characters used",
23
24
  "neptune.FlowNavigation.back": "back to previous step",
24
25
  "neptune.Info.ariaLabel": "More information",
25
26
  "neptune.Label.optional": "(Optional)",
@@ -1 +1 @@
1
- {"version":3,"file":"en.json.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"en.json.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -8,16 +8,59 @@ var jsxRuntime = require('react/jsx-runtime');
8
8
 
9
9
  const TextArea = /*#__PURE__*/React.forwardRef(function TextArea({
10
10
  className,
11
+ maxLength,
12
+ onChange,
11
13
  ...restProps
12
14
  }, reference) {
13
15
  const inputAttributes = contexts.useInputAttributes();
16
+ const value = restProps.value ?? restProps.defaultValue ?? '';
17
+ const currentLength = getGraphemeLength(typeof value === 'string' ? value : String(value));
18
+ contexts.useTextareaCharacterCount(currentLength, maxLength);
19
+ const handleChange = React.useCallback(e => {
20
+ if (maxLength != null) {
21
+ const truncated = truncateToGraphemes(e.target.value, maxLength);
22
+ if (truncated !== e.target.value) {
23
+ Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value')?.set?.call(e.target, truncated);
24
+ }
25
+ }
26
+ onChange?.(e);
27
+ }, [maxLength, onChange]);
14
28
  return /*#__PURE__*/jsxRuntime.jsx("textarea", {
15
29
  ref: reference,
16
30
  className: clsx.clsx(className, _common.inputClassNameBase(), 'np-text-area'),
31
+ onChange: handleChange,
17
32
  ...inputAttributes,
18
33
  ...restProps
19
34
  });
20
35
  });
36
+ const segmenter = typeof Intl.Segmenter === 'function' ? new Intl.Segmenter(undefined, {
37
+ granularity: 'grapheme'
38
+ }) : undefined;
39
+ function getGraphemeLength(str) {
40
+ if (segmenter) {
41
+ let count = 0;
42
+ for (const _ of segmenter.segment(str)) {
43
+ count += 1;
44
+ }
45
+ return count;
46
+ }
47
+ return Array.from(str).length;
48
+ }
49
+ function truncateToGraphemes(str, max) {
50
+ if (segmenter) {
51
+ let result = '';
52
+ let count = 0;
53
+ for (const {
54
+ segment
55
+ } of segmenter.segment(str)) {
56
+ if (count >= max) break;
57
+ result += segment;
58
+ count += 1;
59
+ }
60
+ return result;
61
+ }
62
+ return Array.from(str).slice(0, max).join('');
63
+ }
21
64
 
22
65
  exports.TextArea = TextArea;
23
66
  //# sourceMappingURL=TextArea.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"TextArea.js","sources":["../../src/inputs/TextArea.tsx"],"sourcesContent":["import { clsx } from 'clsx';\nimport { forwardRef } from 'react';\n\nimport { Merge } from '../utils';\nimport { inputClassNameBase } from './_common';\nimport { useInputAttributes } from './contexts';\n\nexport interface TextAreaProps extends Merge<\n React.ComponentPropsWithRef<'textarea'>,\n {\n 'aria-invalid'?: boolean;\n }\n> {}\n\nexport const TextArea = forwardRef(function TextArea(\n { className, ...restProps }: TextAreaProps,\n reference: React.ForwardedRef<HTMLTextAreaElement | null>,\n) {\n const inputAttributes = useInputAttributes();\n\n return (\n <textarea\n ref={reference}\n className={clsx(className, inputClassNameBase(), 'np-text-area')}\n {...inputAttributes}\n {...restProps}\n />\n );\n});\n"],"names":["TextArea","forwardRef","className","restProps","reference","inputAttributes","useInputAttributes","_jsx","ref","clsx","inputClassNameBase"],"mappings":";;;;;;;;MAcaA,QAAQ,gBAAGC,gBAAU,CAAC,SAASD,QAAQA,CAClD;EAAEE,SAAS;EAAE,GAAGC;AAAS,CAAiB,EAC1CC,SAAyD,EAAA;AAEzD,EAAA,MAAMC,eAAe,GAAGC,2BAAkB,EAAE;AAE5C,EAAA,oBACEC,cAAA,CAAA,UAAA,EAAA;AACEC,IAAAA,GAAG,EAAEJ,SAAU;IACfF,SAAS,EAAEO,SAAI,CAACP,SAAS,EAAEQ,0BAAkB,EAAE,EAAE,cAAc,CAAE;AAAA,IAAA,GAC7DL,eAAe;IAAA,GACfF;AAAS,GAAC,CACd;AAEN,CAAC;;;;"}
1
+ {"version":3,"file":"TextArea.js","sources":["../../src/inputs/TextArea.tsx"],"sourcesContent":["import { clsx } from 'clsx';\nimport { forwardRef, useCallback } from 'react';\n\nimport { Merge } from '../utils';\nimport { inputClassNameBase } from './_common';\nimport { useTextareaCharacterCount, useInputAttributes } from './contexts';\n\nexport interface TextAreaProps extends Merge<\n React.ComponentPropsWithRef<'textarea'>,\n {\n 'aria-invalid'?: boolean;\n }\n> {}\n\nexport const TextArea = forwardRef(function TextArea(\n { className, maxLength, onChange, ...restProps }: TextAreaProps,\n reference: React.ForwardedRef<HTMLTextAreaElement | null>,\n) {\n const inputAttributes = useInputAttributes();\n const value = restProps.value ?? restProps.defaultValue ?? '';\n const currentLength = getGraphemeLength(typeof value === 'string' ? value : String(value));\n\n useTextareaCharacterCount(currentLength, maxLength);\n\n const handleChange = useCallback(\n (e: React.ChangeEvent<HTMLTextAreaElement>) => {\n if (maxLength != null) {\n const truncated = truncateToGraphemes(e.target.value, maxLength);\n if (truncated !== e.target.value) {\n Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value')?.set?.call(\n e.target,\n truncated,\n );\n }\n }\n onChange?.(e);\n },\n [maxLength, onChange],\n );\n\n return (\n <textarea\n ref={reference}\n className={clsx(className, inputClassNameBase(), 'np-text-area')}\n onChange={handleChange}\n {...inputAttributes}\n {...restProps}\n />\n );\n});\n\nconst segmenter =\n typeof Intl.Segmenter === 'function'\n ? new Intl.Segmenter(undefined, { granularity: 'grapheme' })\n : undefined;\n\nfunction getGraphemeLength(str: string): number {\n if (segmenter) {\n let count = 0;\n for (const _ of segmenter.segment(str)) {\n count += 1;\n }\n return count;\n }\n return Array.from(str).length;\n}\n\nfunction truncateToGraphemes(str: string, max: number): string {\n if (segmenter) {\n let result = '';\n let count = 0;\n for (const { segment } of segmenter.segment(str)) {\n if (count >= max) break;\n result += segment;\n count += 1;\n }\n return result;\n }\n return Array.from(str).slice(0, max).join('');\n}\n"],"names":["TextArea","forwardRef","className","maxLength","onChange","restProps","reference","inputAttributes","useInputAttributes","value","defaultValue","currentLength","getGraphemeLength","String","useTextareaCharacterCount","handleChange","useCallback","e","truncated","truncateToGraphemes","target","Object","getOwnPropertyDescriptor","HTMLTextAreaElement","prototype","set","call","_jsx","ref","clsx","inputClassNameBase","segmenter","Intl","Segmenter","undefined","granularity","str","count","_","segment","Array","from","length","max","result","slice","join"],"mappings":";;;;;;;;MAcaA,QAAQ,gBAAGC,gBAAU,CAAC,SAASD,QAAQA,CAClD;EAAEE,SAAS;EAAEC,SAAS;EAAEC,QAAQ;EAAE,GAAGC;AAAS,CAAiB,EAC/DC,SAAyD,EAAA;AAEzD,EAAA,MAAMC,eAAe,GAAGC,2BAAkB,EAAE;EAC5C,MAAMC,KAAK,GAAGJ,SAAS,CAACI,KAAK,IAAIJ,SAAS,CAACK,YAAY,IAAI,EAAE;AAC7D,EAAA,MAAMC,aAAa,GAAGC,iBAAiB,CAAC,OAAOH,KAAK,KAAK,QAAQ,GAAGA,KAAK,GAAGI,MAAM,CAACJ,KAAK,CAAC,CAAC;AAE1FK,EAAAA,kCAAyB,CAACH,aAAa,EAAER,SAAS,CAAC;AAEnD,EAAA,MAAMY,YAAY,GAAGC,iBAAW,CAC7BC,CAAyC,IAAI;IAC5C,IAAId,SAAS,IAAI,IAAI,EAAE;MACrB,MAAMe,SAAS,GAAGC,mBAAmB,CAACF,CAAC,CAACG,MAAM,CAACX,KAAK,EAAEN,SAAS,CAAC;AAChE,MAAA,IAAIe,SAAS,KAAKD,CAAC,CAACG,MAAM,CAACX,KAAK,EAAE;AAChCY,QAAAA,MAAM,CAACC,wBAAwB,CAACC,mBAAmB,CAACC,SAAS,EAAE,OAAO,CAAC,EAAEC,GAAG,EAAEC,IAAI,CAChFT,CAAC,CAACG,MAAM,EACRF,SAAS,CACV;AACH,MAAA;AACF,IAAA;IACAd,QAAQ,GAAGa,CAAC,CAAC;AACf,EAAA,CAAC,EACD,CAACd,SAAS,EAAEC,QAAQ,CAAC,CACtB;AAED,EAAA,oBACEuB,cAAA,CAAA,UAAA,EAAA;AACEC,IAAAA,GAAG,EAAEtB,SAAU;IACfJ,SAAS,EAAE2B,SAAI,CAAC3B,SAAS,EAAE4B,0BAAkB,EAAE,EAAE,cAAc,CAAE;AACjE1B,IAAAA,QAAQ,EAAEW,YAAa;AAAA,IAAA,GACnBR,eAAe;IAAA,GACfF;AAAS,GAAC,CACd;AAEN,CAAC;AAED,MAAM0B,SAAS,GACb,OAAOC,IAAI,CAACC,SAAS,KAAK,UAAU,GAChC,IAAID,IAAI,CAACC,SAAS,CAACC,SAAS,EAAE;AAAEC,EAAAA,WAAW,EAAE;CAAY,CAAC,GAC1DD,SAAS;AAEf,SAAStB,iBAAiBA,CAACwB,GAAW,EAAA;AACpC,EAAA,IAAIL,SAAS,EAAE;IACb,IAAIM,KAAK,GAAG,CAAC;IACb,KAAK,MAAMC,CAAC,IAAIP,SAAS,CAACQ,OAAO,CAACH,GAAG,CAAC,EAAE;AACtCC,MAAAA,KAAK,IAAI,CAAC;AACZ,IAAA;AACA,IAAA,OAAOA,KAAK;AACd,EAAA;AACA,EAAA,OAAOG,KAAK,CAACC,IAAI,CAACL,GAAG,CAAC,CAACM,MAAM;AAC/B;AAEA,SAASvB,mBAAmBA,CAACiB,GAAW,EAAEO,GAAW,EAAA;AACnD,EAAA,IAAIZ,SAAS,EAAE;IACb,IAAIa,MAAM,GAAG,EAAE;IACf,IAAIP,KAAK,GAAG,CAAC;AACb,IAAA,KAAK,MAAM;AAAEE,MAAAA;AAAO,KAAE,IAAIR,SAAS,CAACQ,OAAO,CAACH,GAAG,CAAC,EAAE;MAChD,IAAIC,KAAK,IAAIM,GAAG,EAAE;AAClBC,MAAAA,MAAM,IAAIL,OAAO;AACjBF,MAAAA,KAAK,IAAI,CAAC;AACZ,IAAA;AACA,IAAA,OAAOO,MAAM;AACf,EAAA;AACA,EAAA,OAAOJ,KAAK,CAACC,IAAI,CAACL,GAAG,CAAC,CAACS,KAAK,CAAC,CAAC,EAAEF,GAAG,CAAC,CAACG,IAAI,CAAC,EAAE,CAAC;AAC/C;;;;"}
@@ -1,21 +1,64 @@
1
1
  import { clsx } from 'clsx';
2
- import { forwardRef } from 'react';
2
+ import { forwardRef, useCallback } from 'react';
3
3
  import { inputClassNameBase } from './_common.mjs';
4
- import { useInputAttributes } from './contexts.mjs';
4
+ import { useInputAttributes, useTextareaCharacterCount } from './contexts.mjs';
5
5
  import { jsx } from 'react/jsx-runtime';
6
6
 
7
7
  const TextArea = /*#__PURE__*/forwardRef(function TextArea({
8
8
  className,
9
+ maxLength,
10
+ onChange,
9
11
  ...restProps
10
12
  }, reference) {
11
13
  const inputAttributes = useInputAttributes();
14
+ const value = restProps.value ?? restProps.defaultValue ?? '';
15
+ const currentLength = getGraphemeLength(typeof value === 'string' ? value : String(value));
16
+ useTextareaCharacterCount(currentLength, maxLength);
17
+ const handleChange = useCallback(e => {
18
+ if (maxLength != null) {
19
+ const truncated = truncateToGraphemes(e.target.value, maxLength);
20
+ if (truncated !== e.target.value) {
21
+ Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value')?.set?.call(e.target, truncated);
22
+ }
23
+ }
24
+ onChange?.(e);
25
+ }, [maxLength, onChange]);
12
26
  return /*#__PURE__*/jsx("textarea", {
13
27
  ref: reference,
14
28
  className: clsx(className, inputClassNameBase(), 'np-text-area'),
29
+ onChange: handleChange,
15
30
  ...inputAttributes,
16
31
  ...restProps
17
32
  });
18
33
  });
34
+ const segmenter = typeof Intl.Segmenter === 'function' ? new Intl.Segmenter(undefined, {
35
+ granularity: 'grapheme'
36
+ }) : undefined;
37
+ function getGraphemeLength(str) {
38
+ if (segmenter) {
39
+ let count = 0;
40
+ for (const _ of segmenter.segment(str)) {
41
+ count += 1;
42
+ }
43
+ return count;
44
+ }
45
+ return Array.from(str).length;
46
+ }
47
+ function truncateToGraphemes(str, max) {
48
+ if (segmenter) {
49
+ let result = '';
50
+ let count = 0;
51
+ for (const {
52
+ segment
53
+ } of segmenter.segment(str)) {
54
+ if (count >= max) break;
55
+ result += segment;
56
+ count += 1;
57
+ }
58
+ return result;
59
+ }
60
+ return Array.from(str).slice(0, max).join('');
61
+ }
19
62
 
20
63
  export { TextArea };
21
64
  //# sourceMappingURL=TextArea.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"TextArea.mjs","sources":["../../src/inputs/TextArea.tsx"],"sourcesContent":["import { clsx } from 'clsx';\nimport { forwardRef } from 'react';\n\nimport { Merge } from '../utils';\nimport { inputClassNameBase } from './_common';\nimport { useInputAttributes } from './contexts';\n\nexport interface TextAreaProps extends Merge<\n React.ComponentPropsWithRef<'textarea'>,\n {\n 'aria-invalid'?: boolean;\n }\n> {}\n\nexport const TextArea = forwardRef(function TextArea(\n { className, ...restProps }: TextAreaProps,\n reference: React.ForwardedRef<HTMLTextAreaElement | null>,\n) {\n const inputAttributes = useInputAttributes();\n\n return (\n <textarea\n ref={reference}\n className={clsx(className, inputClassNameBase(), 'np-text-area')}\n {...inputAttributes}\n {...restProps}\n />\n );\n});\n"],"names":["TextArea","forwardRef","className","restProps","reference","inputAttributes","useInputAttributes","_jsx","ref","clsx","inputClassNameBase"],"mappings":";;;;;;MAcaA,QAAQ,gBAAGC,UAAU,CAAC,SAASD,QAAQA,CAClD;EAAEE,SAAS;EAAE,GAAGC;AAAS,CAAiB,EAC1CC,SAAyD,EAAA;AAEzD,EAAA,MAAMC,eAAe,GAAGC,kBAAkB,EAAE;AAE5C,EAAA,oBACEC,GAAA,CAAA,UAAA,EAAA;AACEC,IAAAA,GAAG,EAAEJ,SAAU;IACfF,SAAS,EAAEO,IAAI,CAACP,SAAS,EAAEQ,kBAAkB,EAAE,EAAE,cAAc,CAAE;AAAA,IAAA,GAC7DL,eAAe;IAAA,GACfF;AAAS,GAAC,CACd;AAEN,CAAC;;;;"}
1
+ {"version":3,"file":"TextArea.mjs","sources":["../../src/inputs/TextArea.tsx"],"sourcesContent":["import { clsx } from 'clsx';\nimport { forwardRef, useCallback } from 'react';\n\nimport { Merge } from '../utils';\nimport { inputClassNameBase } from './_common';\nimport { useTextareaCharacterCount, useInputAttributes } from './contexts';\n\nexport interface TextAreaProps extends Merge<\n React.ComponentPropsWithRef<'textarea'>,\n {\n 'aria-invalid'?: boolean;\n }\n> {}\n\nexport const TextArea = forwardRef(function TextArea(\n { className, maxLength, onChange, ...restProps }: TextAreaProps,\n reference: React.ForwardedRef<HTMLTextAreaElement | null>,\n) {\n const inputAttributes = useInputAttributes();\n const value = restProps.value ?? restProps.defaultValue ?? '';\n const currentLength = getGraphemeLength(typeof value === 'string' ? value : String(value));\n\n useTextareaCharacterCount(currentLength, maxLength);\n\n const handleChange = useCallback(\n (e: React.ChangeEvent<HTMLTextAreaElement>) => {\n if (maxLength != null) {\n const truncated = truncateToGraphemes(e.target.value, maxLength);\n if (truncated !== e.target.value) {\n Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value')?.set?.call(\n e.target,\n truncated,\n );\n }\n }\n onChange?.(e);\n },\n [maxLength, onChange],\n );\n\n return (\n <textarea\n ref={reference}\n className={clsx(className, inputClassNameBase(), 'np-text-area')}\n onChange={handleChange}\n {...inputAttributes}\n {...restProps}\n />\n );\n});\n\nconst segmenter =\n typeof Intl.Segmenter === 'function'\n ? new Intl.Segmenter(undefined, { granularity: 'grapheme' })\n : undefined;\n\nfunction getGraphemeLength(str: string): number {\n if (segmenter) {\n let count = 0;\n for (const _ of segmenter.segment(str)) {\n count += 1;\n }\n return count;\n }\n return Array.from(str).length;\n}\n\nfunction truncateToGraphemes(str: string, max: number): string {\n if (segmenter) {\n let result = '';\n let count = 0;\n for (const { segment } of segmenter.segment(str)) {\n if (count >= max) break;\n result += segment;\n count += 1;\n }\n return result;\n }\n return Array.from(str).slice(0, max).join('');\n}\n"],"names":["TextArea","forwardRef","className","maxLength","onChange","restProps","reference","inputAttributes","useInputAttributes","value","defaultValue","currentLength","getGraphemeLength","String","useTextareaCharacterCount","handleChange","useCallback","e","truncated","truncateToGraphemes","target","Object","getOwnPropertyDescriptor","HTMLTextAreaElement","prototype","set","call","_jsx","ref","clsx","inputClassNameBase","segmenter","Intl","Segmenter","undefined","granularity","str","count","_","segment","Array","from","length","max","result","slice","join"],"mappings":";;;;;;MAcaA,QAAQ,gBAAGC,UAAU,CAAC,SAASD,QAAQA,CAClD;EAAEE,SAAS;EAAEC,SAAS;EAAEC,QAAQ;EAAE,GAAGC;AAAS,CAAiB,EAC/DC,SAAyD,EAAA;AAEzD,EAAA,MAAMC,eAAe,GAAGC,kBAAkB,EAAE;EAC5C,MAAMC,KAAK,GAAGJ,SAAS,CAACI,KAAK,IAAIJ,SAAS,CAACK,YAAY,IAAI,EAAE;AAC7D,EAAA,MAAMC,aAAa,GAAGC,iBAAiB,CAAC,OAAOH,KAAK,KAAK,QAAQ,GAAGA,KAAK,GAAGI,MAAM,CAACJ,KAAK,CAAC,CAAC;AAE1FK,EAAAA,yBAAyB,CAACH,aAAa,EAAER,SAAS,CAAC;AAEnD,EAAA,MAAMY,YAAY,GAAGC,WAAW,CAC7BC,CAAyC,IAAI;IAC5C,IAAId,SAAS,IAAI,IAAI,EAAE;MACrB,MAAMe,SAAS,GAAGC,mBAAmB,CAACF,CAAC,CAACG,MAAM,CAACX,KAAK,EAAEN,SAAS,CAAC;AAChE,MAAA,IAAIe,SAAS,KAAKD,CAAC,CAACG,MAAM,CAACX,KAAK,EAAE;AAChCY,QAAAA,MAAM,CAACC,wBAAwB,CAACC,mBAAmB,CAACC,SAAS,EAAE,OAAO,CAAC,EAAEC,GAAG,EAAEC,IAAI,CAChFT,CAAC,CAACG,MAAM,EACRF,SAAS,CACV;AACH,MAAA;AACF,IAAA;IACAd,QAAQ,GAAGa,CAAC,CAAC;AACf,EAAA,CAAC,EACD,CAACd,SAAS,EAAEC,QAAQ,CAAC,CACtB;AAED,EAAA,oBACEuB,GAAA,CAAA,UAAA,EAAA;AACEC,IAAAA,GAAG,EAAEtB,SAAU;IACfJ,SAAS,EAAE2B,IAAI,CAAC3B,SAAS,EAAE4B,kBAAkB,EAAE,EAAE,cAAc,CAAE;AACjE1B,IAAAA,QAAQ,EAAEW,YAAa;AAAA,IAAA,GACnBR,eAAe;IAAA,GACfF;AAAS,GAAC,CACd;AAEN,CAAC;AAED,MAAM0B,SAAS,GACb,OAAOC,IAAI,CAACC,SAAS,KAAK,UAAU,GAChC,IAAID,IAAI,CAACC,SAAS,CAACC,SAAS,EAAE;AAAEC,EAAAA,WAAW,EAAE;CAAY,CAAC,GAC1DD,SAAS;AAEf,SAAStB,iBAAiBA,CAACwB,GAAW,EAAA;AACpC,EAAA,IAAIL,SAAS,EAAE;IACb,IAAIM,KAAK,GAAG,CAAC;IACb,KAAK,MAAMC,CAAC,IAAIP,SAAS,CAACQ,OAAO,CAACH,GAAG,CAAC,EAAE;AACtCC,MAAAA,KAAK,IAAI,CAAC;AACZ,IAAA;AACA,IAAA,OAAOA,KAAK;AACd,EAAA;AACA,EAAA,OAAOG,KAAK,CAACC,IAAI,CAACL,GAAG,CAAC,CAACM,MAAM;AAC/B;AAEA,SAASvB,mBAAmBA,CAACiB,GAAW,EAAEO,GAAW,EAAA;AACnD,EAAA,IAAIZ,SAAS,EAAE;IACb,IAAIa,MAAM,GAAG,EAAE;IACf,IAAIP,KAAK,GAAG,CAAC;AACb,IAAA,KAAK,MAAM;AAAEE,MAAAA;AAAO,KAAE,IAAIR,SAAS,CAACQ,OAAO,CAACH,GAAG,CAAC,EAAE;MAChD,IAAIC,KAAK,IAAIM,GAAG,EAAE;AAClBC,MAAAA,MAAM,IAAIL,OAAO;AACjBF,MAAAA,KAAK,IAAI,CAAC;AACZ,IAAA;AACA,IAAA,OAAOO,MAAM;AACf,EAAA;AACA,EAAA,OAAOJ,KAAK,CAACC,IAAI,CAACL,GAAG,CAAC,CAACS,KAAK,CAAC,CAAC,EAAEF,GAAG,CAAC,CAACG,IAAI,CAAC,EAAE,CAAC;AAC/C;;;;"}