@autobusal/common 1.33.6 → 1.33.7

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.
@@ -1,5 +1,6 @@
1
1
  import { useState } from 'react';
2
2
  import { TFunction } from 'i18next';
3
+ import { sanitizeHtml } from '@autobusal/utilities';
3
4
  import Modal from '../Modal/Modal';
4
5
  import { useAiAvailable, useAiGenerate, useAiTranslate, useAiImprove } from './services';
5
6
  import { Row, Field, Compare, Pane, Draft, Actions } from './styles';
@@ -146,11 +147,21 @@ const AiReviewModal = ({ mode, text, targetLocale, sourceLocale, onInsert, onClo
146
147
  </Row>
147
148
  ) }
148
149
 
150
+ { /* Edited: Claude - Date: 2026-08-19
151
+ Both panes are sanitized, for two different reasons. The draft is
152
+ whatever a model wrote: a prompt-injected source document can talk
153
+ it into emitting markup, and this preview renders that straight
154
+ into an admin's browser BEFORE anyone has decided to keep it. The
155
+ source is the same admin-authored CMS HTML every other sink in this
156
+ codebase runs through sanitizeHtml (page/Page.tsx, blog, policies).
157
+ Only the preview is sanitized - Insert still hands the caller the
158
+ raw draft, so nothing the admin actually meant to save is silently
159
+ rewritten on its way into the field. */ }
149
160
  { draft === null && mode === 'translate' && (
150
161
  <Pane>
151
162
  <h4>{ tc('ai_assist.source') }</h4>
152
163
 
153
- <div dangerouslySetInnerHTML={ { __html: text ?? '' } } />
164
+ <div dangerouslySetInnerHTML={ { __html: sanitizeHtml(text) } } />
154
165
  </Pane>
155
166
  ) }
156
167
 
@@ -159,13 +170,13 @@ const AiReviewModal = ({ mode, text, targetLocale, sourceLocale, onInsert, onClo
159
170
  <Pane>
160
171
  <h4>{ tc('ai_assist.source') }</h4>
161
172
 
162
- <div dangerouslySetInnerHTML={ { __html: text ?? '' } } />
173
+ <div dangerouslySetInnerHTML={ { __html: sanitizeHtml(text) } } />
163
174
  </Pane>
164
175
 
165
176
  <Pane>
166
177
  <h4>{ tc('ai_assist.draft') }</h4>
167
178
 
168
- <div dangerouslySetInnerHTML={ { __html: draft } } />
179
+ <div dangerouslySetInnerHTML={ { __html: sanitizeHtml(draft) } } />
169
180
  </Pane>
170
181
  </Compare>
171
182
  ) }
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.33.7
4
+
5
+ ### Changed
6
+
7
+ - **`Required` renders a real `<label>`, and takes an optional `htmlFor`.** It was a `styled.span` sitting beside the field it named, which is a label to a sighted reader and nothing at all to a screen reader - every field it introduced announced as a bare "edit text". Nothing in the stylesheets selects `label` and a flex item computes identically either way, so this is semantics only.
8
+ - **`Password` and `Calendar` take an optional `id`.** A caller cannot associate a visible label with a field it has no way to name, so the shared inputs had to be able to accept one before the forms using them could be fixed.
9
+ - `AiAssist/AiReviewModal` runs its three HTML panes through `sanitizeHtml`. Everything the model returns is HTML rendered into an admin's browser, and every other sink in the codebase sanitises; this one did not.
10
+
3
11
  ## 1.33.6
4
12
 
5
13
  ### Added
@@ -15,6 +15,12 @@ interface Props {
15
15
  on the search form, which is the site's primary conversion path.
16
16
  Optional, so existing callers are unaffected. */
17
17
  label?: string
18
+ /* Edited: Claude - Date: 2026-08-19: id for the visible input, so a
19
+ caller that HAS a visible label can associate it properly instead of
20
+ duplicating the text into aria-label. Preferred over `label` where
21
+ both are possible - the association also makes the label a click
22
+ target, which opens the picker. */
23
+ id?: string
18
24
  defaultValue?: string
19
25
  t: TFunction<'general'>
20
26
  validation?: string
@@ -36,7 +42,7 @@ interface Props {
36
42
  onChange?: (value: string) => void
37
43
  }
38
44
 
39
- const Calendar = ({ type, name, label, defaultValue, t, validation, minDate, refs, onUpdate, onChange }: Props): JSX.Element => {
45
+ const Calendar = ({ type, name, label, id, defaultValue, t, validation, minDate, refs, onUpdate, onChange }: Props): JSX.Element => {
40
46
  const [ show, setShow ] = useState<boolean>(false);
41
47
 
42
48
  /**
@@ -75,7 +81,7 @@ const Calendar = ({ type, name, label, defaultValue, t, validation, minDate, ref
75
81
 
76
82
  return (
77
83
  <Container ref={ ref }>
78
- <input type="text" aria-label={ label } autoComplete="off" readOnly={ true } value={ prepared } onClick={ () => setShow(true) } />
84
+ <input type="text" id={ id } aria-label={ label } autoComplete="off" readOnly={ true } value={ prepared } onClick={ () => setShow(true) } />
79
85
 
80
86
  { show && (
81
87
  <Picker
@@ -7,6 +7,15 @@ import { Container } from './styles';
7
7
 
8
8
  interface Props {
9
9
  name: string
10
+ /**
11
+ * Edited: Claude - Date: 2026-08-19
12
+ * The id to put on the inner input, so a caller's <label htmlFor> can
13
+ * reach it. Optional, because the field's own `name` is what the form
14
+ * payload uses and every existing caller relies on that unchanged - but
15
+ * without an id there is no way to attach a label from outside, and this
16
+ * component owns the only input on the login and reset forms.
17
+ */
18
+ id?: string
10
19
  tabIndex?: number
11
20
  validation?: string
12
21
  // Pre-fill value (masked). Lets an admin form show a saved secret as dots
@@ -19,7 +28,7 @@ interface Props {
19
28
  getValues?: UseFormGetValues<any>
20
29
  }
21
30
 
22
- const Password = ({ name, tabIndex, validation, defaultValue, t, refs, getValues }: Props): JSX.Element => {
31
+ const Password = ({ name, id, tabIndex, validation, defaultValue, t, refs, getValues }: Props): JSX.Element => {
23
32
  const [ show, setShow ] = useState<boolean>(false);
24
33
 
25
34
  const toggle = (): void => {
@@ -28,7 +37,7 @@ const Password = ({ name, tabIndex, validation, defaultValue, t, refs, getValues
28
37
 
29
38
  return (
30
39
  <Container>
31
- <input type={ show ? 'text' : 'password' } tabIndex={ tabIndex } defaultValue={ defaultValue } { ...refs(name, Validate(validation ?? '', t, getValues)) } />
40
+ <input type={ show ? 'text' : 'password' } id={ id } tabIndex={ tabIndex } defaultValue={ defaultValue } { ...refs(name, Validate(validation ?? '', t, getValues)) } />
32
41
 
33
42
  { show
34
43
  ? <AiFillEye onClick={ toggle } />
@@ -1,6 +1,14 @@
1
1
  import { Label, Marker } from './styles';
2
2
 
3
3
  interface Props {
4
+ /**
5
+ * Edited: Claude - Date: 2026-08-19
6
+ * The id of the field this labels. Optional so existing callers keep
7
+ * working, but pass it wherever there is a field to point at: without it
8
+ * the visible text is only visually a label, and the input announces as
9
+ * a bare "edit text".
10
+ */
11
+ htmlFor?: string
4
12
  children: React.ReactNode
5
13
  }
6
14
 
@@ -25,8 +33,8 @@ interface Props {
25
33
  * asterisk two separate flex items - and the marker dropped onto its own
26
34
  * line under the label.
27
35
  */
28
- const Required = ({ children }: Props): JSX.Element => (
29
- <Label>
36
+ const Required = ({ htmlFor, children }: Props): JSX.Element => (
37
+ <Label htmlFor={ htmlFor }>
30
38
  { children }
31
39
  <Marker aria-hidden="true">*</Marker>
32
40
  </Label>
@@ -2,7 +2,14 @@ import styled from 'styled-components';
2
2
 
3
3
  // keeps the label and its asterisk as ONE flex item - the form rows are
4
4
  // column flex containers, so two items would stack vertically
5
- export const Label = styled.span`
5
+ //
6
+ // Edited: Claude - Date: 2026-08-19
7
+ // A real <label>, not a <span>. It was already named Label and already
8
+ // sitting next to the field it names, but a span cannot be associated with
9
+ // an input - so the field reached a screen reader unnamed. Nothing in the
10
+ // stylesheets selects `label`, and a flex item computes the same either
11
+ // way, so this changes semantics only.
12
+ export const Label = styled.label`
6
13
  display: inline-flex;
7
14
  align-items: baseline;
8
15
  `;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autobusal/common",
3
- "version": "1.33.6",
3
+ "version": "1.33.7",
4
4
  "author": "Ferjolt Ozuni",
5
5
  "type": "module",
6
6
  "main": "index.ts"