Merge pull request #2757 from crazyserver/MOBILE-3753

Mobile 3753
main
Noel De Martin 2021-05-12 17:13:15 +02:00 committed by GitHub
commit 0181661327
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 340 additions and 279 deletions

View File

@ -281,6 +281,7 @@
"addon.messages.noncontacts": "message",
"addon.messages.nousersfound": "local_moodlemobileapp",
"addon.messages.numparticipants": "message",
"addon.messages.pendingcontactrequests": "message",
"addon.messages.removecontact": "message",
"addon.messages.removecontactconfirm": "message",
"addon.messages.removefromfavourites": "message",
@ -301,6 +302,8 @@
"addon.messages.unblockuser": "message",
"addon.messages.unblockuserconfirm": "message",
"addon.messages.unmuteconversation": "message",
"addon.messages.unreadconversations": "message",
"addon.messages.unreadmessages": "message",
"addon.messages.useentertosend": "message",
"addon.messages.useentertosenddescdesktop": "local_moodlemobileapp",
"addon.messages.useentertosenddescmac": "local_moodlemobileapp",
@ -1450,6 +1453,7 @@
"core.course.completion_automatic:todo": "course",
"core.course.completion_manual:aria:done": "course",
"core.course.completion_manual:aria:markdone": "course",
"core.course.completion_manual:done": "course",
"core.course.completion_manual:markdone": "course",
"core.course.completion_setby:auto:done": "course",
"core.course.completion_setby:auto:todo": "course",
@ -1748,6 +1752,7 @@
"core.hasdatatosync": "local_moodlemobileapp",
"core.help": "moodle",
"core.hide": "moodle",
"core.hideadvanced": "form",
"core.hour": "moodle",
"core.hours": "moodle",
"core.humanreadablesize": "local_moodlemobileapp",
@ -2114,6 +2119,7 @@
"core.sharedfiles.sharedfiles": "local_moodlemobileapp",
"core.sharedfiles.successstorefile": "local_moodlemobileapp",
"core.show": "moodle",
"core.showadvanced": "form",
"core.showless": "form",
"core.showmore": "form",
"core.site": "moodle",

View File

@ -18,7 +18,7 @@
</ion-button>
</ion-col>
<ion-col class="ion-text-center addon-calendar-period">
<h3>{{ periodName }}</h3>
<h3 id="addon-calendar-monthname">{{ periodName }}</h3>
</ion-col>
<ion-col class="ion-text-end" *ngIf="canNavigate">
<ion-button fill="clear" (click)="loadNext()" [attr.aria-label]="'core.next' | translate">
@ -29,58 +29,84 @@
</ion-grid>
<!-- Calendar view. -->
<ion-grid class="addon-calendar-months">
<!-- List of days. -->
<ion-row>
<ion-col class="ion-text-center addon-calendar-weekday" *ngFor="let day of weekDays">
<span class="ion-hide-md-up" [title]="day.fullname | translate">{{ day.shortname | translate }}</span>
<span class="ion-hide-md-down">{{ day.fullname | translate }}</span>
</ion-col>
</ion-row>
<ion-grid class="addon-calendar-months" role="table" aria-describedby="addon-calendar-monthname">
<div role="rowgroup">
<!-- List of days. -->
<ion-row role="row">
<ion-col class="ion-text-center addon-calendar-weekday" *ngFor="let day of weekDays" role="columnheader">
<span class="sr-only">{{ day.fullname | translate }}</span>
<span class="ion-hide-md-up" aria-hidden="true">{{ day.shortname | translate }}</span>
<span class="ion-hide-md-down" aria-hidden="true">{{ day.fullname | translate }}</span>
</ion-col>
</ion-row>
</div>
<div role="rowgroup">
<!-- Weeks. -->
<ion-row *ngFor="let week of weeks" class="addon-calendar-week">
<!-- Empty slots (first week). -->
<ion-col *ngFor="let value of week.prepadding" class="dayblank addon-calendar-day"></ion-col>
<ion-col class="addon-calendar-day ion-text-center" *ngFor="let day of week.days" (click)="dayClicked(day.mday)"
[ngClass]='{"hasevents": day.hasevents, "today": isCurrentMonth && day.istoday,
"weekend": day.isweekend, "duration_finish": day.haslastdayofevent}'
[class.addon-calendar-event-past-day]="isPastMonth || day.ispast">
<p class="addon-calendar-day-number"><span>{{ day.mday }}</span></p>
<!-- In phone, display some dots to indicate the type of events. -->
<p class="ion-hide-md-up addon-calendar-dot-types"><span *ngFor="let type of day.calendareventtypes"
class="calendar_event_type calendar_event_{{type}}"></span></p>
<!-- In tablet, display list of events. -->
<div class="ion-hide-md-down addon-calendar-day-events">
<ng-container *ngFor="let event of day.filteredEvents | slice:0:4; let index = index">
<div role="button" *ngIf="index < 3 || day.filteredEvents.length == 4" class="addon-calendar-event"
(click)="eventClicked(event, $event)" [class.addon-calendar-event-past]="event.ispast">
<span class="calendar_event_type calendar_event_{{event.formattedType}}"></span>
<ion-icon *ngIf="event.offline && !event.deleted" name="fas-clock"
[attr.aria-label]="'core.notsent' | translate"></ion-icon>
<ion-icon *ngIf="event.deleted" name="fas-trash" [attr.aria-label]="'core.deletedoffline' | translate">
</ion-icon>
<span class="addon-calendar-event-time">{{ event.timestart * 1000 | coreFormatDate: timeFormat }}</span>
<img *ngIf="event.moduleIcon" src="{{event.moduleIcon}}" alt="" role="presentation"
class="core-module-icon">
<!-- Add the icon title so accessibility tools read it. -->
<span class="sr-only">
{{ 'addon.calendar.type' + event.formattedType | translate }}
<span class="sr-only" *ngIf="event.moduleIcon && event.iconTitle">{{ event.iconTitle }}</span>
</span>
<span class="addon-calendar-event-name">{{event.name}}</span>
</div>
</ng-container>
<p *ngIf="day.filteredEvents.length > 4" class="addon-calendar-day-more">
<b>{{ 'core.nummore' | translate:{$a: day.filteredEvents.length - 3} }}</b>
<!-- Weeks. -->
<ion-row *ngFor="let week of weeks" class="addon-calendar-week" role="row">
<!-- Empty slots (first week). -->
<ion-col *ngFor="let value of week.prepadding" class="dayblank addon-calendar-day" role="cell"></ion-col>
<ion-col
*ngFor="let day of week.days"
class="addon-calendar-day ion-text-center"
[ngClass]='{
"hasevents": day.hasevents,
"today": isCurrentMonth && day.istoday,
"weekend": day.isweekend,
"duration_finish": day.haslastdayofevent
}'
[class.addon-calendar-event-past-day]="isPastMonth || day.ispast"
role="button cell"
tabindex="0"
(ariaButtonClick)="dayClicked(day.mday)"
>
<p class="addon-calendar-day-number">
<span aria-hidden="true">{{ day.mday }}</span>
<span class="sr-only">{{ day.periodName | translate }}</span>
</p>
</div>
</ion-col>
<!-- Empty slots (last week). -->
<ion-col *ngFor="let value of week.postpadding" class="dayblank addon-calendar-day"></ion-col>
</ion-row>
<!-- In phone, display some dots to indicate the type of events. -->
<p class="ion-hide-md-up addon-calendar-dot-types"><span *ngFor="let type of day.calendareventtypes"
class="calendar_event_type calendar_event_{{type}}"></span></p>
<!-- In tablet, display list of events. -->
<div class="ion-hide-md-down addon-calendar-day-events">
<ng-container *ngFor="let event of day.filteredEvents | slice:0:4; let index = index">
<div
*ngIf="index < 3 || day.filteredEvents.length == 4"
class="addon-calendar-event"
[class.addon-calendar-event-past]="event.ispast"
role="button"
tabindex="0"
(ariaButtonClick)="eventClicked(event, $event)"
>
<span class="calendar_event_type calendar_event_{{event.formattedType}}"></span>
<ion-icon *ngIf="event.offline && !event.deleted" name="fas-clock"
[attr.aria-label]="'core.notsent' | translate"></ion-icon>
<ion-icon *ngIf="event.deleted" name="fas-trash"
[attr.aria-label]="'core.deletedoffline' | translate"></ion-icon>
<span class="addon-calendar-event-time">
{{ event.timestart * 1000 | coreFormatDate: timeFormat }}
</span>
<img *ngIf="event.moduleIcon" src="{{event.moduleIcon}}" alt="" role="presentation"
class="core-module-icon">
<!-- Add the icon title so accessibility tools read it. -->
<span class="sr-only">
{{ 'addon.calendar.type' + event.formattedType | translate }}
<span class="sr-only" *ngIf="event.moduleIcon && event.iconTitle">{{ event.iconTitle }}</span>
</span>
<span class="addon-calendar-event-name">{{event.name}}</span>
</div>
</ng-container>
<p *ngIf="day.filteredEvents.length > 4" class="addon-calendar-day-more">
<b>{{ 'core.nummore' | translate:{$a: day.filteredEvents.length - 3} }}</b>
</p>
</div>
</ion-col>
<!-- Empty slots (last week). -->
<ion-col *ngFor="let value of week.postpadding" class="dayblank addon-calendar-day" role="cell"></ion-col>
</ion-row>
</div>
</ion-grid>
</core-loading>

View File

@ -71,6 +71,7 @@
overflow: hidden;
white-space: nowrap;
color: var(--text-color);
min-height: auto;
&.addon-calendar-event-past {
opacity: 0.5;
@ -102,6 +103,7 @@
.addon-calendar-weekday {
border-bottom: 1px solid var(--addon-calendar-border-color);
font-weight: bold;
}
.addon-calendar-day-events {

View File

@ -87,7 +87,6 @@ export class AddonCalendarCalendarComponent implements OnInit, DoCheck, OnDestro
constructor(
differs: KeyValueDiffers,
) {
this.currentSiteId = CoreSites.getCurrentSiteId();
if (CoreLocalNotifications.isAvailable()) {
@ -233,6 +232,10 @@ export class AddonCalendarCalendarComponent implements OnInit, DoCheck, OnDestro
this.weeks.forEach((week) => {
week.days.forEach((day) => {
day.periodName = CoreTimeUtils.userDate(
new Date(this.year!, this.month! - 1, day.mday).getTime(),
'core.strftimedaydate',
);
day.eventsFormated = day.eventsFormated || [];
day.filteredEvents = day.filteredEvents || [];
day.events.forEach((event) => {
@ -372,7 +375,7 @@ export class AddonCalendarCalendarComponent implements OnInit, DoCheck, OnDestro
* @param calendarEvent Calendar event..
* @param event Mouse event.
*/
eventClicked(calendarEvent: AddonCalendarEventToDisplay, event: MouseEvent): void {
eventClicked(calendarEvent: AddonCalendarEventToDisplay, event: Event): void {
this.onEventClicked.emit(calendarEvent.id);
event.stopPropagation();
}

View File

@ -16,9 +16,7 @@
<!-- Event name. -->
<ion-item class="ion-text-wrap">
<ion-label position="stacked">
<h2 [core-mark-required]="true">
{{ 'addon.calendar.eventname' | translate }}
</h2>
<h2 [core-mark-required]="true">{{ 'addon.calendar.eventname' | translate }}</h2>
</ion-label>
<ion-input type="text" name="name" [placeholder]="'addon.calendar.eventname' | translate" formControlName="name">
</ion-input>
@ -27,11 +25,7 @@
<!-- Date. -->
<ion-item class="ion-text-wrap">
<ion-label position="stacked">
<h2 [core-mark-required]="true">
{{ 'core.date' | translate }}
</h2>
</ion-label>
<ion-label position="stacked"><h2 [core-mark-required]="true">{{ 'core.date' | translate }}</h2></ion-label>
<ion-datetime formControlName="timestart" [placeholder]="'core.date' | translate" [displayFormat]="dateFormat"
[max]="maxDate" [min]="minDate">
</ion-datetime>
@ -40,13 +34,15 @@
<!-- Type. -->
<ion-item class="ion-text-wrap addon-calendar-eventtype-container">
<ion-label id="addon-calendar-eventtype-label">
<h2 [core-mark-required]="true">
{{ 'addon.calendar.eventkind' | translate }}
</h2>
<ion-label>
<h2 [core-mark-required]="true">{{ 'addon.calendar.eventkind' | translate }}</h2>
</ion-label>
<ion-select formControlName="eventtype" aria-labelledby="addon-calendar-eventtype-label" interface="action-sheet"
[disabled]="eventTypes.length == 1">
<p *ngIf="eventTypes.length == 1" slot="end">{{eventTypes[0].name | translate }}</p>
<ion-select
*ngIf="eventTypes.length > 1"
formControlName="eventtype"
interface="action-sheet"
>
<ion-select-option *ngFor="let type of eventTypes" [value]="type.value">
{{ type.name | translate }}
</ion-select-option>
@ -55,12 +51,8 @@
<!-- Category. -->
<ion-item class="ion-text-wrap" *ngIf="typeControl.value == 'category'">
<ion-label id="addon-calendar-category-label">
<h2 [core-mark-required]="true">
{{ 'core.category' | translate }}
</h2>
</ion-label>
<ion-select formControlName="categoryid" aria-labelledby="addon-calendar-category-label" interface="action-sheet"
<ion-label><h2 [core-mark-required]="true">{{ 'core.category' | translate }}</h2></ion-label>
<ion-select formControlName="categoryid" interface="action-sheet"
[placeholder]="'core.noselection' | translate">
<ion-select-option *ngFor="let category of categories" [value]="category.id">
{{ category.name }}
@ -70,12 +62,8 @@
<!-- Course. -->
<ion-item class="ion-text-wrap" *ngIf="typeControl.value == 'course'">
<ion-label id="addon-calendar-course-label">
<h2 [core-mark-required]="true">
{{ 'core.course' | translate }}
</h2>
</ion-label>
<ion-select formControlName="courseid" aria-labelledby="addon-calendar-course-label" interface="action-sheet"
<ion-label><h2 [core-mark-required]="true">{{ 'core.course' | translate }}</h2></ion-label>
<ion-select formControlName="courseid" interface="action-sheet"
[placeholder]="'core.noselection' | translate">
<ion-select-option *ngFor="let course of courses" [value]="course.id">{{ course.fullname }}</ion-select-option>
</ion-select>
@ -85,12 +73,8 @@
<ng-container *ngIf="typeControl.value == 'group'">
<!-- Select the course. -->
<ion-item class="ion-text-wrap">
<ion-label id="addon-calendar-groupcourse-label">
<h2 [core-mark-required]="true">
{{ 'core.course' | translate }}
</h2>
</ion-label>
<ion-select formControlName="groupcourseid" aria-labelledby="addon-calendar-groupcourse-label"
<ion-label><h2 [core-mark-required]="true">{{ 'core.course' | translate }}</h2></ion-label>
<ion-select formControlName="groupcourseid"
interface="action-sheet" [placeholder]="'core.noselection' | translate"
(ionChange)="groupCourseSelected($event)">
<ion-select-option *ngFor="let course of courses" [value]="course.id">
@ -104,12 +88,8 @@
</ion-item>
<!-- Select the group. -->
<ion-item class="ion-text-wrap" *ngIf="!loadingGroups && groups.length > 0">
<ion-label id="addon-calendar-group-label">
<h2 [core-mark-required]="true">
{{ 'core.group' | translate }}
</h2>
</ion-label>
<ion-select formControlName="groupid" aria-labelledby="addon-calendar-group-label" interface="action-sheet"
<ion-label><h2 [core-mark-required]="true">{{ 'core.group' | translate }}</h2></ion-label>
<ion-select formControlName="groupid" interface="action-sheet"
[placeholder]="'core.noselection' | translate">
<ion-select-option *ngFor="let group of groups" [value]="group.id">{{ group.name }}</ion-select-option>
</ion-select>
@ -121,23 +101,20 @@
</ng-container>
<!-- Advanced options. -->
<ion-item-divider class="ion-text-wrap core-expandable" (click)="toggleAdvanced()"
[attr.aria-label]="(advanced ? 'core.showless' : 'core.showmore') | translate" role="button">
<ion-item button class="ion-text-wrap core-expandable divider" (click)="toggleAdvanced()">
<ion-icon *ngIf="!advanced" name="fas-caret-right" slot="start" aria-hidden="true"></ion-icon>
<ion-icon *ngIf="advanced" name="fas-caret-down" slot="start" aria-hidden="true"></ion-icon>
<ion-label>
<h2 *ngIf="!advanced">{{ 'core.showmore' | translate }}</h2>
<h2 *ngIf="advanced">{{ 'core.showless' | translate }}</h2>
</ion-label>
</ion-item-divider>
</ion-item>
<div [hidden]="!advanced">
<!-- Description. -->
<ion-item class="ion-text-wrap">
<ion-label position="stacked">
<h2>{{ 'core.description' | translate }}</h2>
</ion-label>
<core-rich-text-editor [control]="descriptionControl"
<ion-label position="stacked"><h2>{{ 'core.description' | translate }}</h2></ion-label>
<core-rich-text-editor [control]="descriptionControl" [attr.aria-label]="'core.description' | translate"
[placeholder]="'core.description' | translate" name="description" [component]="component"
[componentId]="eventId" [autoSave]="false"></core-rich-text-editor>
</ion-item>
@ -154,9 +131,7 @@
<ion-radio-group formControlName="duration">
<ion-item class="addon-calendar-radio-title">
<ion-label>
<h2>
{{ 'addon.calendar.eventduration' | translate }}
</h2>
<h2>{{ 'addon.calendar.eventduration' | translate }}</h2>
</ion-label>
</ion-item>
<ion-item>
@ -200,9 +175,7 @@
<ion-radio-group formControlName="repeateditall">
<ion-item class="addon-calendar-radio-title">
<ion-label>
<h2>
{{ 'addon.calendar.repeatedevents' | translate }}
</h2>
<h2>{{ 'addon.calendar.repeatedevents' | translate }}</h2>
</ion-label>
</ion-item>
<ion-item>

View File

@ -2026,6 +2026,7 @@ export type AddonCalendarWeekDay = AddonCalendarDay & {
ispast?: boolean; // Calculated in the app. Whether the day is in the past.
filteredEvents?: AddonCalendarEventToDisplay[]; // Calculated in the app. Filtered events.
eventsFormated?: AddonCalendarEventToDisplay[]; // Events.
periodName?: string;
};
/**

View File

@ -51,6 +51,7 @@
"noncontacts": "Non-contacts",
"nousersfound": "No users found",
"numparticipants": "{{$a}} participants",
"pendingcontactrequests": "There are {{$a}} pending contact requests",
"removecontact": "Remove contact",
"removecontactconfirm": "Are you sure you want to remove {{$a}} from your contacts?",
"removefromfavourites": "Unstar conversation",
@ -71,6 +72,8 @@
"unblockuser": "Unblock user",
"unblockuserconfirm": "Are you sure you want to unblock {{$a}}?",
"unmuteconversation": "Unmute",
"unreadconversations": "There are {{$a}} unread conversations",
"unreadmessages": "There are {{$a}} unread messages",
"useentertosend": "Use enter to send",
"useentertosenddescdesktop": "If disabled, you can use Ctrl+Enter to send the message.",
"useentertosenddescmac": "If disabled, you can use Cmd+Enter to send the message.",
@ -81,4 +84,4 @@
"you": "You:",
"youhaveblockeduser": "You have blocked this user.",
"yourcontactrequestpending": "Your contact request is pending with {{$a}}"
}
}

View File

@ -25,22 +25,36 @@
<core-loading [hideUntil]="loaded" [message]="loadingMessage">
<ion-list>
<ion-item class="ion-text-wrap addon-message-discussion" (click)="gotoContacts()"
[attr.aria-label]="'addon.messages.contacts' | translate" detail="true" button>
<ion-item class="ion-text-wrap addon-message-discussion" (click)="gotoContacts()" detail="true" button>
<ion-icon name="fas-address-book" slot="start" aria-hidden="true"></ion-icon>
<ion-label><h2>{{ 'addon.messages.contacts' | translate }}</h2></ion-label>
<ion-badge *ngIf="contactRequestsCount > 0" slot="end">{{contactRequestsCount}}</ion-badge>
<ion-badge *ngIf="contactRequestsCount > 0" slot="end" aria-hidden="true">{{contactRequestsCount}}</ion-badge>
<span *ngIf="contactRequestsCount > 0" class="sr-only">
{{ 'addon.messages.pendingcontactrequests' | translate:{$a: contactRequestsCount} }}
</span>
</ion-item>
<!-- Favourite conversations. -->
<ion-item-divider class="ion-text-wrap core-expandable" (click)="toggle(favourites)" sticky="true"
<ion-item
button
class="ion-text-wrap core-expandable divider"
(click)="toggle(favourites)"
sticky="true"
[attr.aria-label]="(favourites.expanded ? 'core.collapse' : 'core.expand') | translate"
[attr.aria-expanded]="favourites.expanded" role="heading button">
[attr.aria-expanded]="favourites.expanded"
aria-controls="addon-messages-groupconversations-favourite"
role="heading"
detail="false"
>
<ion-icon *ngIf="!favourites.expanded" name="fas-caret-right" slot="start" aria-hidden="true"></ion-icon>
<ion-icon *ngIf="favourites.expanded" name="fas-caret-down" slot="start" aria-hidden="true"></ion-icon>
<ion-label><h2>{{ 'core.favourites' | translate }} ({{ favourites.count }})</h2></ion-label>
<ion-badge slot="end" *ngIf="favourites.unread">{{ favourites.unread }}</ion-badge>
</ion-item-divider>
<div [hidden]="!favourites.conversations || !favourites.expanded || favourites.loading" #favlist>
<ion-badge slot="end" *ngIf="favourites.unread" aria-hidden="true">{{ favourites.unread }}</ion-badge>
<span *ngIf="favourites.unread" class="sr-only">
{{ 'addon.messages.unreadconversations' | translate:{$a: favourites.unread} }}
</span>
</ion-item>
<div [hidden]="!favourites.conversations || !favourites.expanded || favourites.loading" #favlist
id="addon-messages-groupconversations-favourite">
<ng-container *ngTemplateOutlet="conversationsTemplate; context: {conversations: favourites.conversations}">
</ng-container>
<!-- The infinite loading cannot be inside the ng-template, it fails because it doesn't find ion-content. -->
@ -55,15 +69,27 @@
</ion-item>
<!-- Group conversations. -->
<ion-item-divider class="ion-text-wrap core-expandable" (click)="toggle(group)" sticky="true"
<ion-item
button
class="divider ion-text-wrap core-expandable"
(click)="toggle(group)"
sticky="true"
[attr.aria-label]="(group.expanded ? 'core.collapse' : 'core.expand') | translate"
[attr.aria-expanded]="group.expanded" role="heading button">
[attr.aria-expanded]="group.expanded"
aria-controls="addon-messages-groupconversations-group"
role="heading"
detail="false"
>
<ion-icon *ngIf="!group.expanded" name="fas-caret-right" slot="start" aria-hidden="true"></ion-icon>
<ion-icon *ngIf="group.expanded" name="fas-caret-down" slot="start" aria-hidden="true"></ion-icon>
<ion-label><h2>{{ 'addon.messages.groupconversations' | translate }} ({{ group.count }})</h2></ion-label>
<ion-badge slot="end" *ngIf="group.unread">{{ group.unread }}</ion-badge>
</ion-item-divider>
<div [hidden]="!group.conversations || !group.expanded || group.loading" #grouplist>
<ion-badge slot="end" *ngIf="group.unread" aria-hidden="true">{{ group.unread }}</ion-badge>
<span *ngIf="group.unread" class="sr-only">
{{ 'addon.messages.unreadconversations' | translate:{$a: group.unread} }}
</span>
</ion-item>
<div [hidden]="!group.conversations || !group.expanded || group.loading" #grouplist
id="addon-messages-groupconversations-group">
<ng-container *ngTemplateOutlet="conversationsTemplate; context: {conversations: group.conversations}">
</ng-container>
<!-- The infinite loading cannot be inside the ng-template, it fails because it doesn't find ion-content. -->
@ -77,17 +103,29 @@
<ion-label><ion-spinner></ion-spinner></ion-label>
</ion-item>
<ion-item-divider class="ion-text-wrap core-expandable" (click)="toggle(individual)" sticky="true"
<ion-item
button
class="divider ion-text-wrap core-expandable"
(click)="toggle(individual)"
sticky="true"
[attr.aria-label]="(individual.expanded ? 'core.collapse' : 'core.expand') | translate"
[attr.aria-expanded]="individual.expanded" role="heading button">
[attr.aria-expanded]="individual.expanded"
aria-controls="addon-messages-groupconversations-individual"
role="heading"
detail="false"
>
<ion-icon *ngIf="!individual.expanded" name="fas-caret-right" slot="start" aria-hidden="true"></ion-icon>
<ion-icon *ngIf="individual.expanded" name="fas-caret-down" slot="start" aria-hidden="true"></ion-icon>
<ion-label>
<h2>{{ 'addon.messages.individualconversations' | translate }} ({{ individual.count }})</h2>
</ion-label>
<ion-badge slot="end" *ngIf="individual.unread">{{ individual.unread }}</ion-badge>
</ion-item-divider>
<div [hidden]="!individual.conversations || !individual.expanded || individual.loading" #indlist>
<ion-badge slot="end" *ngIf="individual.unread" aria-hidden="true">{{ individual.unread }}</ion-badge>
<span *ngIf="individual.unread" class="sr-only">
{{ 'addon.messages.unreadconversations' | translate:{$a: individual.unread} }}
</span>
</ion-item>
<div [hidden]="!individual.conversations || !individual.expanded || individual.loading" #indlist
id="addon-messages-groupconversations-individual">
<ng-container *ngTemplateOutlet="conversationsTemplate; context: {conversations: individual.conversations}">
</ng-container>
<!-- The infinite loading cannot be inside the ng-template, it fails because it doesn't find ion-content. -->
@ -144,7 +182,10 @@
</p>
</ion-label>
<ion-note *ngIf="conversation.lastmessagedate > 0 || conversation.unreadcount" slot="end">
<ion-badge *ngIf="conversation.unreadcount > 0">{{ conversation.unreadcount }}</ion-badge>
<ion-badge *ngIf="conversation.unreadcount > 0" aria-label="true">{{ conversation.unreadcount }}</ion-badge>
<span *ngIf="conversation.unreadcount > 0" class="sr-only">
{{ 'addon.messages.unreadmessages' | translate:{$a: conversation.unreadcount} }}
</span>
<span *ngIf="conversation.lastmessagedate > 0">{{conversation.lastmessagedate | coreDateDayOrTime}}</span>
</ion-note>
</ion-item>

View File

@ -47,6 +47,7 @@ export class AddonMessagesMainMenuHandlerService implements CoreMainMenuHandler,
class: 'addon-messages-handler',
showBadge: true, // Do not check isMessageCountEnabled because we'll use fallback it not enabled.
badge: '',
badgeA11yText: 'addon.messages.unreadconversations',
loading: true,
};

View File

@ -25,18 +25,25 @@
(contentChanged)="onMessageChange($event)">
</core-rich-text-editor>
</ion-item>
<ion-item-divider class="ion-text-wrap core-expandable" (click)="toggleAdvanced()" role="heading button"
[attr.aria-expanded]="advanced" [attr.aria-label]="(advanced ? 'core.hideadvanced' : 'core.showadvanced') | translate">
<ion-item
button class="divider ion-text-wrap core-expandable"
(click)="toggleAdvanced()"
role="heading"
detail="false"
[attr.aria-expanded]="advanced"
aria-controls="addon-mod-forum-advanced"
[attr.aria-label]="(advanced ? 'core.hideadvanced' : 'core.showadvanced') | translate"
>
<ion-icon *ngIf="!advanced" name="fa-caret-right" slot="start" aria-hidden="true"></ion-icon>
<ion-icon *ngIf="advanced" name="fa-caret-down" slot="start" aria-hidden="true"></ion-icon>
<ion-label><h2>{{ 'addon.mod_forum.advanced' | translate }}</h2></ion-label>
</ion-item-divider>
<ng-container *ngIf="advanced">
</ion-item>
<div *ngIf="advanced" id="addon-mod-forum-advanced">
<core-attachments *ngIf="forum.id && forum.maxattachments > 0"
[maxSize]="forum.maxbytes" [maxSubmissions]="forum.maxattachments" [allowOffline]="true" [files]="replyData.files"
[component]="component" [componentId]="forum.cmid">
</core-attachments>
</ng-container>
</div>
<ion-grid>
<ion-row>
<ion-col>

View File

@ -122,20 +122,26 @@
<ion-checkbox slot="end" [(ngModel)]="replyData.isprivatereply" name="isprivatereply"></ion-checkbox>
</ion-item>
<ng-container *ngIf="forum.id && forum.maxattachments > 0">
<ion-item-divider class="core-expandable ion-text-wrap" (click)="toggleAdvanced()" [attr.aria-expanded]="advanced"
[attr.aria-label]="(advanced ? 'core.hideadvanced' : 'core.showadvanced') |translate" role="button">
<ion-item
button
class="divider core-expandable ion-text-wrap"
(click)="toggleAdvanced()" detail="false"
[attr.aria-expanded]="advanced"
[attr.aria-controls]="'addon-forum-reply-edit-form-advanced-' + uniqueId"
[attr.aria-label]="(advanced ? 'core.hideadvanced' : 'core.showadvanced') | translate"
>
<ion-label>
<ion-icon *ngIf="!advanced" name="fa-caret-right" slot="start" aria-hidden="true"></ion-icon>
<ion-icon *ngIf="advanced" name="fa-caret-down" slot="start" aria-hidden="true"></ion-icon>
<h2>{{ 'addon.mod_forum.advanced' | translate }}</h2>
</ion-label>
</ion-item-divider>
<ng-container *ngIf="advanced">
</ion-item>
<div *ngIf="advanced" [id]="'addon-forum-reply-edit-form-advanced-' + uniqueId">
<core-attachments
[files]="replyData.files" [maxSize]="forum.maxbytes" [maxSubmissions]="forum.maxattachments"
[component]="component" [componentId]="forum.cmid" [allowOffline]="true">
</core-attachments>
</ng-container>
</div>
</ng-container>
<ion-grid>
<ion-row>

View File

@ -31,13 +31,21 @@
(contentChanged)="onMessageChange($event)">
</core-rich-text-editor>
</ion-item>
<ion-item-divider class="ion-text-wrap core-expandable" (click)="toggleAdvanced()" [attr.aria-expanded]="advanced"
[attr.aria-label]="(advanced ? 'core.hideadvanced' : 'core.showadvanced') |translate" role="heading button">
<ion-item
button
class="divider ion-text-wrap core-expandable"
(click)="toggleAdvanced()"
detail="false"
[attr.aria-expanded]="advanced"
[attr.aria-label]="(advanced ? 'core.hideadvanced' : 'core.showadvanced') | translate"
role="heading"
aria-controls="addon-mod-forum-new-discussion-advanced"
>
<ion-icon *ngIf="!advanced" name="fa-caret-right" slot="start" aria-hidden="true"></ion-icon>
<ion-icon *ngIf="advanced" name="fa-caret-down" slot="start" aria-hidden="true"></ion-icon>
<ion-label><h2>{{ 'addon.mod_forum.advanced' | translate }}</h2></ion-label>
</ion-item-divider>
<ng-container *ngIf="advanced">
</ion-item>
<div *ngIf="advanced" id="addon-mod-forum-new-discussion-advanced">
<ion-item *ngIf="showGroups && groupIds.length > 1 && accessInfo.cancanposttomygroups">
<ion-label>{{ 'addon.mod_forum.posttomygroups' | translate }}</ion-label>
<ion-toggle [(ngModel)]="newDiscussion.postToAllGroups" name="postallgroups"></ion-toggle>
@ -61,7 +69,7 @@
[files]="newDiscussion.files" [maxSize]="forum.maxbytes" [maxSubmissions]="forum.maxattachments"
[component]="component" [componentId]="forum.cmid" [allowOffline]="true">
</core-attachments>
</ng-container>
</div>
<ion-item>
<ion-label>
<ion-row>

View File

@ -41,6 +41,7 @@ export class AddonNotificationsMainMenuHandlerService implements CoreMainMenuHan
class: 'addon-notifications-handler',
showBadge: true,
badge: '',
badgeA11yText: 'addon.notifications.unreadnotification',
loading: true,
};

View File

@ -1,68 +0,0 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
export abstract class CoreAriaRoleButton<T = unknown> {
componentInstance: T;
constructor(componentInstance: T) {
this.componentInstance = componentInstance;
}
/**
* A11y key functionality that prevents keyDown events.
*
* @param event Event.
*/
keyDown(event: KeyboardEvent): void {
if ((event.key == ' ' || event.key == 'Enter') && this.isAllowed()) {
event.preventDefault();
event.stopPropagation();
}
}
/**
* A11y key functionality that translates space and enter keys to click action.
*
* @param event Event.
*/
keyUp(event: KeyboardEvent): void {
if ((event.key == ' ' || event.key == 'Enter') && this.isAllowed()) {
event.preventDefault();
event.stopPropagation();
this.click(event);
}
}
/**
* A11y click functionality.
*
* @param event Event.
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
click(event?: Event): void {
// Nothing defined here.
}
/**
* Checks if action is allowed in class.
*
* @returns If allowed.
*/
isAllowed(): boolean {
return true;
}
}

View File

@ -4,18 +4,15 @@
[alt]="'core.pictureof' | translate:{$a: fullname}"
core-external-content
onError="this.src='assets/img/user-avatar.png'"
(click)="gotoProfile($event)"
(ariaButtonClick)="gotoProfile($event)"
[attr.aria-hidden]="!linkProfile"
[attr.role]="linkProfile ? 'button' : null"
(keydown)="buttonAction.keyDown($event)"
(keyup)="buttonAction.keyUp($event)"
[attr.tabindex]="linkProfile ? 0 : null"
[class.clickable]="linkProfile"
>
<img *ngIf="!avatarUrl" src="assets/img/user-avatar.png" [alt]="'core.pictureof' | translate:{$a: fullname}"
(click)="gotoProfile($event)" [attr.aria-hidden]="!linkProfile" [attr.role]="linkProfile ? 'button' : null"
(keydown)="buttonAction.keyDown($event)" (keyup)="buttonAction.keyUp($event)"
(ariaButtonClick)="gotoProfile($event)" [attr.aria-hidden]="!linkProfile" [attr.role]="linkProfile ? 'button' : null"
[attr.tabindex]="linkProfile ? 0 : null">
<span *ngIf="checkOnline && isOnline()" class="contact-status online" role="status" [attr.aria-label]="'core.online' | translate">

View File

@ -20,7 +20,6 @@ import { CoreUtils } from '@services/utils/utils';
import { CoreEventObserver, CoreEvents } from '@singletons/events';
import { CoreUserProvider, CoreUserBasicData } from '@features/user/services/user';
import { CoreNavigator } from '@services/navigator';
import { CoreAriaRoleButton } from '@classes/aria-role-button';
/**
* Component to display a "user avatar".
@ -39,15 +38,13 @@ export class CoreUserAvatarComponent implements OnInit, OnChanges, OnDestroy {
@Input() profileUrl?: string;
@Input() linkProfile = true; // Avoid linking to the profile if wanted.
@Input() fullname?: string;
@Input() userId?: number; // If provided or found it will be used to link the image to the profile.
@Input() courseId?: number;
@Input() protected userId?: number; // If provided or found it will be used to link the image to the profile.
@Input() protected courseId?: number;
@Input() checkOnline = false; // If want to check and show online status.
@Input() extraIcon?: string; // Extra icon to show near the avatar.
avatarUrl?: string;
buttonAction: CoreUserAvatarButton;
// Variable to check if we consider this user online or not.
// @TODO: Use setting when available (see MDL-63972) so we can use site setting.
protected timetoshowusers = 300000; // Miliseconds default.
@ -55,7 +52,6 @@ export class CoreUserAvatarComponent implements OnInit, OnChanges, OnDestroy {
protected pictureObserver: CoreEventObserver;
constructor() {
this.currentUserId = CoreSites.getCurrentSiteUserId();
this.pictureObserver = CoreEvents.on(
@ -67,15 +63,12 @@ export class CoreUserAvatarComponent implements OnInit, OnChanges, OnDestroy {
},
CoreSites.getCurrentSiteId(),
);
this.buttonAction = new CoreUserAvatarButton(this);
}
/**
* Component being initialized.
*/
ngOnInit(): void {
this.setFields();
}
@ -137,14 +130,19 @@ export class CoreUserAvatarComponent implements OnInit, OnChanges, OnDestroy {
* @param event Click event.
*/
gotoProfile(event: Event): void {
if (!this.buttonAction.isAllowed()) {
if (!this.linkProfile || !this.userId) {
return;
}
event.preventDefault();
event.stopPropagation();
this.buttonAction.click();
CoreNavigator.navigateToSitePath('user', {
params: {
userId: this.userId,
courseId: this.courseId,
},
});
}
/**
@ -156,32 +154,6 @@ export class CoreUserAvatarComponent implements OnInit, OnChanges, OnDestroy {
}
/**
* Helper class to manage rol button.
*/
class CoreUserAvatarButton extends CoreAriaRoleButton<CoreUserAvatarComponent> {
/**
* @inheritdoc
*/
click(): void {
CoreNavigator.navigateToSitePath('user', {
params: {
userId: this.componentInstance.userId,
courseId: this.componentInstance.courseId,
},
});
}
/**
* @inheritdoc
*/
isAllowed(): boolean {
return this.componentInstance.linkProfile && !!this.componentInstance.userId;
}
}
/**
* Type with all possible formats of user.
*/

View File

@ -0,0 +1,57 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { Directive, ElementRef, OnInit, Output, EventEmitter } from '@angular/core';
/**
* Directive to emulate click and key actions following aria role button.
*/
@Directive({
selector: '[ariaButtonClick]',
})
export class CoreAriaButtonClickDirective implements OnInit {
protected element: HTMLElement;
@Output() ariaButtonClick = new EventEmitter();
constructor(
element: ElementRef,
) {
this.element = element.nativeElement;
}
/**
* Initialize actions.
*/
ngOnInit(): void {
this.element.addEventListener('click', async (event) => {
this.ariaButtonClick.emit(event);
});
this.element.addEventListener('keydown', async (event) => {
if ((event.key == ' ' || event.key == 'Enter')) {
event.preventDefault();
event.stopPropagation();
}
});
this.element.addEventListener('keyup', async (event) => {
if ((event.key == ' ' || event.key == 'Enter')) {
this.ariaButtonClick.emit(event);
}
});
}
}

View File

@ -24,6 +24,7 @@ import { CoreLinkDirective } from './link';
import { CoreLongPressDirective } from './long-press';
import { CoreSupressEventsDirective } from './supress-events';
import { CoreUserLinkDirective } from './user-link';
import { CoreAriaButtonClickDirective } from './aria-button';
@NgModule({
declarations: [
@ -37,6 +38,7 @@ import { CoreUserLinkDirective } from './user-link';
CoreLongPressDirective,
CoreSupressEventsDirective,
CoreUserLinkDirective,
CoreAriaButtonClickDirective,
],
exports: [
CoreAutoFocusDirective,
@ -49,6 +51,7 @@ import { CoreUserLinkDirective } from './user-link';
CoreLongPressDirective,
CoreSupressEventsDirective,
CoreUserLinkDirective,
CoreAriaButtonClickDirective,
],
})
export class CoreDirectivesModule {}

View File

@ -1,4 +1,3 @@
<ion-item-divider class="ion-text-wrap" (click)="gotoBlock()" role="button">
<ion-item button class="ion-text-wrap divider" (click)="gotoBlock()" detail="true">
<ion-label><h2>{{ title | translate }}</h2></ion-label>
<ion-icon class="item-detail-icon" name="chevron-forward-outline" slot="end" flip-rtl aria-hidden="true"></ion-icon>
</ion-item-divider>
</ion-item>

View File

@ -89,8 +89,8 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentIn
protected resetObserver?: CoreEventObserver;
protected initHeightInterval?: number;
protected isCurrentView = true;
protected toolbarButtonWidth = 40;
protected toolbarArrowWidth = 28;
protected toolbarButtonWidth = 44;
protected toolbarArrowWidth = 44;
protected pageInstance: string;
protected autoSaveInterval?: number;
protected hideMessageTimeout?: number;
@ -100,6 +100,7 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentIn
protected resizeFunction?: () => Promise<number>;
protected selectionChangeFunction?: () => void;
protected languageChangedSubscription?: Subscription;
protected resizeObserver?: IntersectionObserver;
rteEnabled = false;
isPhone = false;
@ -127,6 +128,7 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentIn
initialSlide: 0,
slidesPerView: 6,
centerInsufficientSlides: true,
watchSlidesVisibility: true,
};
constructor(
@ -136,6 +138,14 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentIn
this.contentChanged = new EventEmitter<string>();
this.element = elementRef.nativeElement as HTMLDivElement;
this.pageInstance = 'app_' + Date.now(); // Generate a "unique" ID based on timestamp.
if ('IntersectionObserver' in window) {
this.resizeObserver = new IntersectionObserver((observerEntry: IntersectionObserverEntry[]) => {
if (observerEntry[0].boundingClientRect.width > 0) {
this.updateToolbarButtons();
}
});
}
}
/**
@ -231,8 +241,12 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentIn
});
this.resizeFunction = this.maximizeEditorSize.bind(this);
this.selectionChangeFunction = this.updateToolbarStyles.bind(this);
window.addEventListener('resize', this.resizeFunction!);
// Start observing the target node for configured mutations
this.resizeObserver?.observe(this.element);
this.selectionChangeFunction = this.updateToolbarStyles.bind(this);
document.addEventListener('selectionchange', this.selectionChangeFunction!);
this.keyboardObserver = CoreEvents.on(CoreEvents.KEYBOARD_CHANGE, (kbHeight: number) => {
@ -273,7 +287,7 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentIn
setTimeout(async () => {
// Editor is ready, adjust Height if needed.
let height;
let height: number;
if (CoreApp.isAndroid()) {
// In Android we ignore the keyboard height because it is not part of the web view.
@ -760,6 +774,11 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentIn
* Show the toolbar.
*/
showToolbar(event: Event): void {
if (!('IntersectionObserver' in window)) {
// Fallback if IntersectionObserver is not supported.
this.updateToolbarButtons();
}
this.element.classList.add('ion-touched');
this.element.classList.remove('ion-untouched');
this.element.classList.add('has-focus');
@ -776,7 +795,7 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentIn
* @param event Event.
*/
stopBubble(event: Event): void {
if (event.type != 'mouseup' && event.type != 'keyup') {
if (event.type != 'touchend' &&event.type != 'mouseup' && event.type != 'keyup') {
event.preventDefault();
}
event.stopPropagation();
@ -840,7 +859,7 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentIn
* Update the number of toolbar buttons displayed.
*/
async updateToolbarButtons(): Promise<void> {
if (!this.isCurrentView || !this.toolbar || !this.toolbarSlides) {
if (!this.isCurrentView || !this.toolbar || !this.toolbarSlides || this.element.offsetParent == null) {
// Don't calculate if component isn't in current view, the calculations are wrong.
return;
}
@ -856,7 +875,7 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentIn
return;
}
if (width > length * this.toolbarButtonWidth) {
if (length > 0 && width > length * this.toolbarButtonWidth) {
this.slidesOpts = { ...this.slidesOpts, slidesPerView: length };
this.toolbarArrows = false;
} else {
@ -1096,6 +1115,7 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentIn
clearInterval(this.initHeightInterval);
clearInterval(this.autoSaveInterval);
clearTimeout(this.hideMessageTimeout);
this.resizeObserver?.disconnect();
this.resetObserver?.off();
this.keyboardObserver?.off();
}

View File

@ -16,11 +16,14 @@
[selected]="tab.page === selectedTab"
[tabindex]="selectedTab == tab.page ? 0 : -1"
[attr.aria-controls]="tab.id"
[attr.aria-label]="tab.title | translate"
>
<ion-icon [name]="tab.icon" aria-hidden="true"></ion-icon>
<ion-label aria-hidden="true">{{ tab.title | translate }}</ion-label>
<ion-badge *ngIf="tab.badge">{{ tab.badge }}</ion-badge>
<ion-badge *ngIf="tab.badge" aria-hidden="true">{{ tab.badge }}</ion-badge>
<span class="sr-only">{{ tab.title | translate }}</span>
<span *ngIf="tab.badge && tab.badgeA11yText" class="sr-only">
{{ tab.badgeA11yText | translate: {$a : tab.badge } }}
</span>
</ion-tab-button>
<ion-tab-button
@ -32,10 +35,10 @@
layout="label-hide"
[tabindex]="selectedTab == morePageName ? 0 : -1"
[attr.aria-controls]="morePageName"
[attr.aria-label]="'core.more' | translate"
>
<ion-icon name="fas-bars" aria-hidden="true"></ion-icon>
<ion-label aria-hidden="true">{{ 'core.more' | translate }}</ion-label>
<span class="sr-only">{{ 'core.more' | translate }}</span>
</ion-tab-button>
</ion-tab-bar>
</ion-tabs>

View File

@ -30,8 +30,12 @@
<ion-label>
<h2>{{ handler.title | translate}}</h2>
</ion-label>
<ion-badge slot="end" *ngIf="handler.showBadge" [hidden]="handler.loading || !handler.badge">{{handler.badge}}
<ion-badge slot="end" *ngIf="handler.showBadge" [hidden]="handler.loading || !handler.badge" aria-hidden="true">
{{handler.badge}}
</ion-badge>
<span *ngIf="handler.showBadge && handler.badge && handler.badgeA11yText" class="sr-only">
{{ handler.badgeA11yText | translate: {$a : handler.badge } }}
</span>
<ion-spinner slot="end" *ngIf="handler.showBadge && handler.loading"></ion-spinner>
</ion-item>
<ng-container *ngFor="let item of customItems">

View File

@ -65,6 +65,11 @@ export interface CoreMainMenuHandlerData {
*/
badge?: string;
/**
* Accessibility text to add on the badge. Only used if showBadge is true.
*/
badgeA11yText?: string;
/**
* If true, the badge number is being loaded. Only used if showBadge is true.
*/

View File

@ -1,5 +1,5 @@
<ion-item-divider class="ion-text-wrap" detail="true" (click)="gotoBlock()" role="button">
<ion-item button class="divider ion-text-wrap" detail="true" (click)="gotoBlock()" detail="true">
<ion-label>
<h2>{{ title | translate }}</h2>
</ion-label>
</ion-item-divider>
</ion-item>

View File

@ -185,16 +185,6 @@ ion-alert.core-nohead {
}
}
// Ionic item divider.
ion-item-divider {
.item-detail-icon {
color: var(--ion-item-detail-icon-color);
font-size: var(--ion-item-detail-icon-font-size);
opacity: var(--ion-item-detail-icon-opacity);
padding-inline-end: 16px;
}
}
// Ionic list.
ion-list.list-md {
padding: 0;

View File

@ -67,8 +67,8 @@
}
--ion-item-background: #{$ion-item-background-dark};
--ion-item-detail-icon-color: var(--white);
ion-item-divider {
--ion-item-detail-icon-: var(--white);
ion-item-divider, ion-item.divider {
--background: var(--black);
--color: var(--white);
}

View File

@ -172,10 +172,11 @@
}
--item-divider-min-height: calc(var(--a11y-min-target-size) + 8px);
ion-item-divider {
ion-item-divider, ion-item.divider {
--background: var(--gray-lighter);
--color: inherit;
min-height: var(--item-divider-min-height);
--min-height: var(--item-divider-min-height);
min-height: var(--min-height);
}
--core-combobox-background: var(--ion-item-background);