Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ exports[`OcDatePicker > renders 1`] = `
<!--v-if--> <input id="oc-textinput-1" aria-invalid="false" class="oc-text-input oc-input oc-rounded" type="date" value="">
<!--v-if-->
</div>
<div class="oc-text-input-message">
<div class="oc-text-input-message" aria-live="off" aria-atomic="true" role="alert">
<!--v-if--> <span id="oc-textinput-1-message" class=""></span>
</div>
<!--v-if-->
Expand Down
1 change: 1 addition & 0 deletions packages/design-system/src/components/OcIcon/OcIcon.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
<inline-svg
:src="nameWithFillType"
:transform-source="transformSvgElement"
:tabindex="accessibleLabel ? '0' : null"
:aria-hidden="accessibleLabel === '' ? 'true' : null"
:aria-labelledby="accessibleLabel === '' ? null : svgTitleId"
:focusable="accessibleLabel === '' ? 'false' : null"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,17 +50,19 @@ describe('OcTextInput', () => {
passwordPolicy = { active: false, pass: false }
) {
const passwordPolicyMock = mock<PasswordPolicy>()
const policyRules = [
{
code: 'minLength',
message: '%{param1}+ letters',
format: ['8'],
verified: passwordPolicy.pass
}
]
passwordPolicyMock.rules = policyRules
passwordPolicyMock.missing.mockReturnValue({
rules: [
{
code: 'minLength',
message: '%{param1}+ letters',
format: ['8'],
verified: passwordPolicy.pass
}
]
rules: policyRules
})
passwordPolicyMock.check.mockReturnValueOnce(passwordPolicy.pass)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this change?

passwordPolicyMock.check.mockReturnValue(passwordPolicy.pass)

if (passwordPolicy.active) {
options.props = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@
'oc-text-input-warning': !!warningMessage,
'oc-text-input-danger': !!errorMessage
}"
:aria-live="errorMessage || warningMessage ? 'assertive' : 'off'"
aria-atomic="true"
role="alert"
>
<oc-icon
v-if="messageText !== null && !!descriptionMessage"
Expand Down Expand Up @@ -207,9 +210,20 @@ const additionalListeners = computed(() => {

const additionalAttributes = computed(() => {
const additionalAttrs: Record<string, unknown> = {}
const describedByIds: string[] = []

if (!!warningMessage || !!errorMessage || !!descriptionMessage) {
additionalAttrs['aria-describedby'] = messageId
describedByIds.push(messageId.value)
}

if (type === 'password' && Object.keys(passwordPolicy).length) {
describedByIds.push(`${id}-password-policy`)
}

if (describedByIds.length > 0) {
additionalAttrs['aria-describedby'] = describedByIds.join(' ')
}

// FIXME: placeholder usage is discouraged, we need to find a better UX concept
if (defaultValue) {
additionalAttrs['placeholder'] = defaultValue
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ exports[`OcTextInput > password input field > password policy > displays error s
<!--v-if-->
<div class="oc-text-input-password-wrapper">
<!-- Input label is handled in the OcTextInput component -->
<!-- eslint-disable-next-line vuejs-accessibility/form-control-has-label --> <input id="oc-textinput-24" aria-invalid="false" class="oc-text-input oc-input oc-rounded" type="password" value="pass"> <button type="button" aria-label="Show password" class="oc-button oc-rounded oc-button-s oc-button-justify-content-center oc-button-gap-m oc-button-passive oc-button-passive-raw oc-text-input-show-password-toggle oc-px-s oc-background-default">
<!-- eslint-disable-next-line vuejs-accessibility/form-control-has-label --> <input id="oc-textinput-24" aria-describedby="oc-textinput-24-password-policy" aria-invalid="true" class="oc-text-input oc-input oc-rounded" type="password" value="pass"> <button type="button" aria-label="Show password" class="oc-button oc-rounded oc-button-s oc-button-justify-content-center oc-button-gap-m oc-button-passive oc-button-passive-raw oc-text-input-show-password-toggle oc-px-s oc-background-default">
<!--v-if-->
<!-- @slot Content of the button --> <span class="oc-icon oc-icon-s oc-icon-passive"><!----></span>
</button> <button type="button" aria-label="Copy password" class="oc-button oc-rounded oc-button-s oc-button-justify-content-center oc-button-gap-m oc-button-passive oc-button-passive-raw oc-text-input-copy-password-button oc-px-s oc-background-default">
Expand All @@ -16,7 +16,7 @@ exports[`OcTextInput > password input field > password policy > displays error s
<!--v-if-->
</div>
<portal to="app.design-system.password-policy">
<div class="oc-flex oc-text-small oc-text-input-password-policy-rule-wrapper oc-pt-s">
<div id="oc-textinput-24-password-policy" class="oc-flex oc-text-small oc-text-input-password-policy-rule-wrapper oc-pt-s" role="status" aria-live="polite" aria-atomic="true"><span class="oc-invisible-sr">Password requirements: 8+ letters</span>
<div class="oc-flex oc-flex-middle oc-text-input-password-policy-rule"><span class="oc-icon oc-icon-s oc-icon-danger oc-mr-xs"><!----></span> <span class="oc-text-input-danger">8+ letters</span>
<!--v-if-->
</div>
Expand All @@ -35,7 +35,7 @@ exports[`OcTextInput > password input field > password policy > displays success
<!--v-if-->
<div class="oc-text-input-password-wrapper">
<!-- Input label is handled in the OcTextInput component -->
<!-- eslint-disable-next-line vuejs-accessibility/form-control-has-label --> <input id="oc-textinput-25" aria-invalid="false" class="oc-text-input oc-input oc-rounded" type="password" value="password123"> <button type="button" aria-label="Show password" class="oc-button oc-rounded oc-button-s oc-button-justify-content-center oc-button-gap-m oc-button-passive oc-button-passive-raw oc-text-input-show-password-toggle oc-px-s oc-background-default">
<!-- eslint-disable-next-line vuejs-accessibility/form-control-has-label --> <input id="oc-textinput-25" aria-describedby="oc-textinput-25-password-policy" aria-invalid="false" class="oc-text-input oc-input oc-rounded" type="password" value="password123"> <button type="button" aria-label="Show password" class="oc-button oc-rounded oc-button-s oc-button-justify-content-center oc-button-gap-m oc-button-passive oc-button-passive-raw oc-text-input-show-password-toggle oc-px-s oc-background-default">
<!--v-if-->
<!-- @slot Content of the button --> <span class="oc-icon oc-icon-s oc-icon-passive"><!----></span>
</button> <button type="button" aria-label="Copy password" class="oc-button oc-rounded oc-button-s oc-button-justify-content-center oc-button-gap-m oc-button-passive oc-button-passive-raw oc-text-input-copy-password-button oc-px-s oc-background-default">
Expand All @@ -45,7 +45,7 @@ exports[`OcTextInput > password input field > password policy > displays success
<!--v-if-->
</div>
<portal to="app.design-system.password-policy">
<div class="oc-flex oc-text-small oc-text-input-password-policy-rule-wrapper oc-pt-s">
<div id="oc-textinput-25-password-policy" class="oc-flex oc-text-small oc-text-input-password-policy-rule-wrapper oc-pt-s" role="status" aria-live="polite" aria-atomic="true"><span class="oc-invisible-sr">Password meets all requirements</span>
<div class="oc-flex oc-flex-middle oc-text-input-password-policy-rule"><span class="oc-icon oc-icon-s oc-icon-success oc-mr-xs"><!----></span> <span class="oc-text-input-success">8+ letters</span>
<!--v-if-->
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
:value="value"
:type="showPassword ? 'text' : 'password'"
:disabled="disabled"
:aria-invalid="hasPasswordPolicyViolation"
@input="$emit('input', $event)"
@change="$emit('change', $event)"
/>
Expand Down Expand Up @@ -52,7 +53,14 @@
</oc-button>
</div>
<portal v-if="showPasswordPolicyInformation" to="app.design-system.password-policy">
<div class="oc-flex oc-text-small oc-text-input-password-policy-rule-wrapper oc-pt-s">
<div
:id="passwordPolicyId"
class="oc-flex oc-text-small oc-text-input-password-policy-rule-wrapper oc-pt-s"
role="status"
aria-live="polite"
aria-atomic="true"
>
<span class="oc-invisible-sr" v-text="passwordPolicySummary" />
<div
v-for="(testedRule, index) in testedPasswordPolicy.rules"
:key="index"
Expand Down Expand Up @@ -133,14 +141,44 @@ const showPassword = ref(false)
const copyPasswordIconInitial = 'file-copy'
const copyPasswordIcon = ref(copyPasswordIconInitial)

const inputId = computed(() => attrs.id as string)
const passwordPolicyId = computed(() => `${inputId.value}-password-policy`)

const showPasswordPolicyInformation = computed(() => {
return !!Object.keys(passwordPolicy.rules || {}).length
return !!Object.keys(passwordPolicy?.rules || {}).length
})

const testedPasswordPolicy = computed(() => {
return passwordPolicy.missing(unref(value))
})

const hasPasswordPolicyViolation = computed(() => {
if (!Object.keys(passwordPolicy?.rules || {})?.length) {
return false
}
// Check if password has any policy violations or if there's an explicit error
return !passwordPolicy.check(unref(value)) || hasError
})

const passwordPolicySummary = computed(() => {
const tested = testedPasswordPolicy.value
if (!tested || !tested.rules || tested.rules.length === 0) {
return ''
}

const unmetRequirements = tested.rules.filter((rule) => !rule.verified)
if (unmetRequirements.length === 0) {
return $gettext('Password meets all requirements')
}

const messages = unmetRequirements.map((rule) => getPasswordPolicyRuleMessage(rule))
return $gettext(
'Password requirements: %{requirements}',
{ requirements: messages.join(', ') },
true
)
})

const getPasswordPolicyRuleMessage = (rule: PasswordPolicyRule) => {
const paramObj: Record<string, string> = {}

Expand Down Expand Up @@ -170,7 +208,7 @@ const generatePassword = () => {
watch(
() => value,
(value) => {
if (!Object.keys(passwordPolicy).length) {
if (!Object.keys(passwordPolicy?.rules || {})?.length) {
return
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
<template>
<div class="oc-flex oc-flex-center expiration-date-indicator">
<oc-icon v-oc-tooltip="expirationDateTooltip" name="calendar-event" fill-type="line" />
<span class="oc-invisible-sr" v-text="screenreaderShareExpiration" />
<oc-icon
v-oc-tooltip="expirationDateTooltip"
:accessible-label="screenreaderShareExpiration"
name="calendar-event"
fill-type="line"
/>
</div>
</template>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
name="lock-password"
class="oc-files-file-link-has-password oc-mr-xs"
fill-type="line"
:accessible-label="$gettext('This link is password-protected')"
:aria-label="$gettext('This link is password-protected')"
role="img"
/>
Expand Down
1 change: 1 addition & 0 deletions packages/web-pkg/src/components/CreateLinkModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
</div>
<div class="link-modal-password oc-mb-m">
<oc-text-input
id="link-password-input"
:key="passwordInputKey"
:model-value="password.value"
type="password"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ exports[`DateFilter > can use a custom attribute as display name 1`] = `
<!--v-if--> <input id="oc-textinput-7" aria-invalid="false" class="oc-text-input oc-input oc-rounded" type="date" value="">
<!--v-if-->
</div>
<div class="oc-text-input-message">
<div class="oc-text-input-message" aria-live="off" aria-atomic="true" role="alert">
<!--v-if--> <span id="oc-textinput-7-message" class=""></span>
</div>
<!--v-if-->
Expand All @@ -67,7 +67,7 @@ exports[`DateFilter > can use a custom attribute as display name 1`] = `
<!--v-if--> <input id="oc-textinput-8" aria-invalid="false" class="oc-text-input oc-input oc-rounded" type="date" value="">
<!--v-if-->
</div>
<div class="oc-text-input-message">
<div class="oc-text-input-message" aria-live="off" aria-atomic="true" role="alert">
<!--v-if--> <span id="oc-textinput-8-message" class=""></span>
</div>
<!--v-if-->
Expand Down Expand Up @@ -142,7 +142,7 @@ exports[`DateFilter > renders all items 1`] = `
<!--v-if--> <input id="oc-textinput-3" aria-invalid="false" class="oc-text-input oc-input oc-rounded" type="date" value="">
<!--v-if-->
</div>
<div class="oc-text-input-message">
<div class="oc-text-input-message" aria-live="off" aria-atomic="true" role="alert">
<!--v-if--> <span id="oc-textinput-3-message" class=""></span>
</div>
<!--v-if-->
Expand All @@ -152,7 +152,7 @@ exports[`DateFilter > renders all items 1`] = `
<!--v-if--> <input id="oc-textinput-4" aria-invalid="false" class="oc-text-input oc-input oc-rounded" type="date" value="">
<!--v-if-->
</div>
<div class="oc-text-input-message">
<div class="oc-text-input-message" aria-live="off" aria-atomic="true" role="alert">
<!--v-if--> <span id="oc-textinput-4-message" class=""></span>
</div>
<!--v-if-->
Expand Down