N. Carpeta « public / libmde »

Versión para imprimir.

1. public / libmde / md-app-bar.js

1
import { ES_APPLE } from "../libclienteweb/ES_APPLE.js"
2
import { getAttribute } from "../libclienteweb/getAttribute.js"
3
import { querySelector } from "../libclienteweb/querySelector.js"
4
5
class MdAppBar extends HTMLElement {
6
7
 getContent() {
8
  return /* HTML */`
9
   <style>
10
11
    :host {
12
     display: flex;
13
     box-sizing: border-box;
14
     align-items: center;
15
     padding: 0 0.25rem;
16
     background-color: var(--md-sys-color-surface);
17
     position: sticky;
18
     z-index: 1;
19
     left: env(titlebar-area-x, 0);
20
     top: env(titlebar-area-y, 0);
21
     height: env(titlebar-area-height, 4rem);
22
     width: env(titlebar-area-width, 100%);
23
    }
24
    
25
    :host(.apple) {
26
     height: env(titlebar-area-height, 3rem);
27
    }
28
29
    :host(.scroll) {
30
     background-color: var(--md-sys-color-surface-container-low);
31
    }
32
33
    #navigation {
34
     flex: 0 0 auto;
35
     overflow: hidden
36
    }
37
38
    #navigation ::slotted(*) {
39
     color: var(--md-sys-color-on-surface);
40
    }
41
42
    #acciones {
43
     margin-left: auto;
44
     flex: 0 0 auto;
45
     overflow: hidden
46
    }
47
48
    :host(.centered) #navigation,
49
    :host(.centered) #acciones {
50
     flex: 0 0 6rem;
51
     overflow: hidden
52
    }
53
54
    #headline::slotted(*) {
55
     -webkit-app-region: drag;
56
     flex: 1 1 auto;
57
     white-space: nowrap;
58
     text-overflow: ellipsis;
59
     overflow: hidden;
60
     font-family: var(--md-sys-typescale-title-large-font);
61
     font-weight: var(--md-sys-typescale-title-large-weight);
62
     font-size: var(--md-sys-typescale-title-large-size);
63
     font-style: var(--md-sys-typescale-title-large-font-style);
64
     letter-spacing: var(--md-sys-typescale-title-large-tracking);
65
     line-height: var(--md-sys-typescale-title-large-line-height);
66
     text-transform: var(--md-sys-typescale-title-large-text-transform);
67
     text-decoration: var(--md-sys-typescale-title-large-text-decoration);
68
     color: var(--md-sys-color-on-surface);
69
    }
70
71
    :host(.centered) #headline::slotted(*) {
72
     flex: 1 1 auto;
73
     text-align: center
74
    }
75
76
   </style>
77
78
   <span id="navigation">
79
    <slot name="navigation"></slot>
80
   </span>
81
   <slot id="headline"></slot>
82
   <span id="acciones">
83
    <slot name="action"></slot>
84
   </span>`
85
 }
86
87
 constructor() {
88
  super()
89
  if (ES_APPLE) {
90
   document.body.classList.add("apple")
91
   document.body.classList.remove("material")
92
  } else {
93
   document.body.classList.add("material")
94
   document.body.classList.remove("apple")
95
  }
96
97
  /**
98
   * @private
99
   * @readonly
100
   */
101
  const shadow = this.attachShadow({ mode: "open" })
102
  shadow.innerHTML = this.getContent()
103
  this._configuraAction = this._configuraAction.bind(this)
104
  /**
105
   * @private
106
   * @type {number}
107
   */
108
  this._posY = 0
109
  /**
110
   * @private
111
   * @type {boolean}
112
   */
113
  this._scrolling = false
114
  /**
115
    * @private
116
    * @type { HTMLSlotElement }
117
    */
118
  this._navigation = querySelector(shadow, '[name="navigation"]')
119
  /**
120
    * @private
121
    * @type { HTMLSlotElement }
122
    */
123
  this._action = querySelector(shadow, '[name="action"]')
124
  /**
125
    * @private
126
    * @type { HTMLHeadingElement | null }
127
    */
128
  this._headline = null
129
  /**
130
    * @private
131
    * @type { HTMLElement | null }
132
    */
133
  this._adicional = null
134
  this._action.addEventListener("slotchange", this._configuraAction)
135
  addEventListener("scroll", () => this._onScroll())
136
  addEventListener("load", () => this.configurOtros())
137
 }
138
139
 connectedCallback() {
140
  this.role = "toolbar"
141
  this._configuraAction()
142
 }
143
144
 configurOtros() {
145
  const idAdicional = getAttribute(this, "adicional")
146
  if (idAdicional !== "") {
147
   this._adicional = document.getElementById(idAdicional)
148
   if (this._adicional !== null) {
149
    if (this.classList.contains("apple")) {
150
     this._adicional.style.top = "env(titlebar-area-height, 3rem)"
151
    } else {
152
     this._adicional.style.top = "env(titlebar-area-height, 4rem)"
153
    }
154
   }
155
  }
156
 }
157
158
 _configuraAction() {
159
  const assignedElements = this._action.assignedElements()
160
  if (this.isConnected) {
161
   if (ES_APPLE) {
162
    this.classList.add("apple")
163
    this.classList.remove("material")
164
   } else {
165
    this.classList.add("material")
166
    this.classList.remove("apple")
167
   }
168
   if (this.classList.contains("centered")) {
169
    this.classList.remove("centrado")
170
    this.classList.remove("justificado")
171
   } else {
172
    if (ES_APPLE && assignedElements.length <= 1) {
173
     this.classList.add("centrado")
174
     this.classList.remove("justificado")
175
    } else {
176
     this.classList.add("justificado")
177
     this.classList.remove("centrado")
178
    }
179
   }
180
  }
181
 }
182
183
 /** @private */
184
 _onScroll() {
185
  this._posY = scrollY
186
  if (!this._scrolling) {
187
   requestAnimationFrame(() => this._avanza())
188
  }
189
  this._scrolling = true
190
 }
191
192
 /** @private */
193
 _avanza() {
194
  if (this._posY === 0) {
195
   this.classList.remove("scroll")
196
   if (this._headline !== null) {
197
    if (this._adicional === null) {
198
     this._headline.classList.remove("scroll")
199
    } else {
200
     this._headline.classList.remove("scroll-adicional")
201
    }
202
   }
203
   if (this._adicional !== null) {
204
    this._adicional.classList.remove("scroll")
205
   }
206
  } else {
207
   this.classList.add("scroll")
208
   if (this._headline !== null) {
209
    if (this._adicional === null) {
210
     this._headline.classList.add("scroll")
211
    } else {
212
     this._headline.classList.add("scroll-adicional")
213
    }
214
   }
215
   if (this._adicional !== null) {
216
    this._adicional.classList.add("scroll")
217
   }
218
  }
219
  this._scrolling = false
220
 }
221
222
}
223
224
customElements.define("md-app-bar", MdAppBar)

2. public / libmde / md-filled-button.css

1
/* container */
2
.md-filled-button::before {
3
 content: "";
4
 position: absolute;
5
 z-index: -2;
6
 top: 0;
7
 right: 0;
8
 left: 0;
9
 bottom: 0;
10
 background-color: var(--md-sys-color-primary);
11
}
12
13
/* state layer */
14
.md-filled-button::after {
15
 content: "";
16
 position: absolute;
17
 z-index: -1;
18
 top: 0;
19
 right: 0;
20
 left: 0;
21
 bottom: 0;
22
 background-color: transparent;
23
}
24
25
/* label, shape */
26
.md-filled-button {
27
 position: relative;
28
 box-sizing: border-box;
29
 border-radius: 1.25rem;
30
 height: 2.5rem;
31
 line-height: 2.5rem;
32
 padding: 0 1.5rem;
33
 border: none;
34
 background-color: transparent;
35
 box-shadow: var(--md-box_shadow_level0);
36
 font-family: var(--md-sys-typescale-label-large-font);
37
 font-weight: var(--md-sys-typescale-label-large-weight);
38
 font-size: var(--md-sys-typescale-label-large-size);
39
 font-style: var(--md-sys-typescale-label-large-font-style);
40
 letter-spacing: var(--md-sys-typescale-label-large-tracking);
41
 text-transform: var(--md-sys-typescale-label-large-text-transform);
42
 text-decoration: var(--md-sys-typescale-label-large-text-decoration);
43
 color: var(--md-sys-color-on-primary);
44
 white-space: nowrap;
45
 text-overflow: ellipsis;
46
 overflow: hidden;
47
}
48
49
/* label, shape */
50
.md-filled-button:hover {
51
 color: var(--md-sys-color-on-primary);
52
 box-shadow: var(--md-box_shadow_level1);
53
}
54
55
/* state layer */
56
.md-filled-button:hover::after {
57
 background-color: var(--md-sys-color-on-primary);
58
 opacity: var(--md-sys-state-hover-state-layer-opacity);
59
}
60
61
/* label, shape */
62
.md-filled-button:focus {
63
 outline: none;
64
 color: var(--md-sys-color-on-primary);
65
 box-shadow: var(--md-box_shadow_level0) !important;
66
}
67
68
/* state layer */
69
.md-filled-button:focus::after {
70
 background-color: var(--md-sys-color-on-primary);
71
 opacity: var(--md-sys-state-focus-state-layer-opacity);
72
}
73
74
/* label, shape */
75
.md-filled-button:active {
76
 color: var(--md-sys-color-on-primary);
77
 background-position: center;
78
 background-image:
79
  radial-gradient(circle, var(--md-sys-color-on-primary-container) 1%, transparent 1%);
80
 background-size: 100%;
81
 animation-name: md-ripple;
82
 animation-duration: var(--md-sys-motion-duration-500);
83
 box-shadow: var(--md-box_shadow_level0) !important;
84
}
85
86
/* state layer */
87
.md-filled-button:active::after {
88
 background-color: var(--md-sys-color-on-primary);
89
 opacity: var(--md-sys-state-pressed-state-layer-opacity);
90
}
91
92
/* label, shape */
93
.md-filled-button:disabled {
94
 background-color: transparent !important;
95
 color: var(--md-sys-color-on-surface) !important;
96
 opacity: 0.38 !important;
97
 box-shadow: var(--md-box_shadow_level0) !important;
98
}
99
100
/* container */
101
.md-filled-button:disabled::before {
102
 background-color: var(--md-sys-color-on-surface) !important;
103
 opacity: 0.12 !important;
104
}
105
106
/* state layer */
107
.md-filled-button:disabled::after {
108
 background-color: transparent !important;
109
 opacity: 1 !important;
110
}

3. public / libmde / md-filled-text-field.css

1
.md-filled-text-field {
2
 position: relative;
3
 overflow: hidden;
4
 display: flex;
5
 flex-direction: column;
6
 align-items: stretch;
7
 padding-top: calc(0.5rem + var(--md-sys-typescale-body-small-line-height));
8
 border-top-left-radius: var(--md-sys-shape-corner-extra-small-top-top-left);
9
 border-top-right-radius: var(--md-sys-shape-corner-extra-small-top-top-right);
10
 overflow: hidden;
11
}
12
13
/* container */
14
.md-filled-text-field::before {
15
 content: "";
16
 position: absolute;
17
 z-index: -2;
18
 top: 0;
19
 right: 0;
20
 left: 0;
21
 bottom: 0;
22
 background-color: var(--md-sys-color-surface-container-highest);
23
}
24
25
/* state layer */
26
.md-filled-text-field::after {
27
 content: "";
28
 position: absolute;
29
 z-index: -1;
30
 top: 0;
31
 right: 0;
32
 left: 0;
33
 bottom: 0;
34
 background-color: transparent;
35
}
36
37
.md-filled-text-field span,
38
.md-filled-text-field label {
39
 position: absolute;
40
 top: 0.5rem;
41
 left: 1rem;
42
 right: 1rem;
43
 display: block;
44
 transform: translateY(1rem);
45
 transition-property: transform;
46
 transition-duration: var(--md-sys-motion-duration-300);
47
 white-space: nowrap;
48
 text-overflow: ellipsis;
49
 overflow: hidden;
50
 color: var(--md-sys-color-on-surface-variant);
51
 font-family: var(--md-sys-typescale-body-large-font);
52
 font-weight: var(--md-sys-typescale-body-large-weight);
53
 font-size: var(--md-sys-typescale-body-large-size);
54
 font-style: var(--md-sys-typescale-body-large-font-style);
55
 letter-spacing: var(--md-sys-typescale-body-large-tracking);
56
 line-height: var(--md-sys-typescale-body-large-line-height);
57
 text-transform: var(--md-sys-typescale-body-large-text-transform);
58
 text-decoration: var(--md-sys-typescale-body-large-text-decoration);
59
}
60
61
.md-filled-text-field input:not(:placeholder-shown)+span,
62
.md-filled-text-field input:not(:placeholder-shown)+label,
63
.md-filled-text-field textarea:not(:placeholder-shown)+span,
64
.md-filled-text-field textarea:not(:placeholder-shown)+label,
65
.md-filled-text-field .populated+span,
66
.md-filled-text-field .populated+label,
67
.md-filled-text-field:focus-within span,
68
.md-filled-text-field:focus-within label,
69
.md-filled-text-field.float span,
70
.md-filled-text-field.float label {
71
 transform: translateY(0);
72
 font-family: var(--md-sys-typescale-body-small-font);
73
 font-weight: var(--md-sys-typescale-body-small-weight);
74
 font-size: var(--md-sys-typescale-body-small-size);
75
 font-style: var(--md-sys-typescale-body-small-font-style);
76
 letter-spacing: var(--md-sys-typescale-body-small-tracking);
77
 line-height: var(--md-sys-typescale-body-small-line-height);
78
 text-transform: var(--md-sys-typescale-body-small-text-transform);
79
 text-decoration: var(--md-sys-typescale-body-small-text-decoration);
80
}
81
82
.md-filled-text-field :not(label, span, small) {
83
 position: relative;
84
 caret-color: var(--md-sys-color-primary);
85
 min-height: 2rem;
86
 box-sizing: border-box;
87
 padding-left: 1rem;
88
 padding-bottom: 0.5rem;
89
 padding-right: 1rem;
90
 border: none;
91
 resize: none;
92
 color: var(--md-sys-color-on-surface);
93
 font-family: var(--md-sys-typescale-body-large-font);
94
 font-weight: var(--md-sys-typescale-body-large-weight);
95
 font-size: var(--md-sys-typescale-body-large-size);
96
 font-style: var(--md-sys-typescale-body-large-font-style);
97
 letter-spacing: var(--md-sys-typescale-body-large-tracking);
98
 line-height: var(--md-sys-typescale-body-large-line-height);
99
 text-transform: var(--md-sys-typescale-body-large-text-transform);
100
 text-decoration: var(--md-sys-typescale-body-large-text-decoration);
101
 background-color: transparent;
102
 outline: none;
103
 border-bottom-width: 0.0625rem;
104
 border-bottom-style: solid;
105
 border-bottom-color: var(--md-sys-color-on-surface-variant);
106
}
107
108
.md-filled-text-field ::placeholder {
109
 color: transparent;
110
}
111
112
.md-filled-text-field small {
113
 display: block;
114
 color: var(--md-sys-color-on-surface-variant);
115
 background-color: var(--md-sys-color-background);
116
 font-family: var(--md-sys-typescale-body-small-font);
117
 font-weight: var(--md-sys-typescale-body-small-weight);
118
 font-size: var(--md-sys-typescale-body-small-size);
119
 font-style: var(--md-sys-typescale-body-small-font-style);
120
 letter-spacing: var(--md-sys-typescale-body-small-tracking);
121
 line-height: var(--md-sys-typescale-body-small-line-height);
122
 text-transform: var(--md-sys-typescale-body-small-text-transform);
123
 text-decoration: var(--md-sys-typescale-body-small-text-decoration);
124
 padding: 0.25rem 1rem 0 1rem;
125
 white-space: nowrap;
126
 text-overflow: ellipsis;
127
 overflow: hidden;
128
}
129
130
.md-filled-text-field:hover span,
131
.md-filled-text-field:hover label {
132
 color: var(--md-sys-color-on-surface-variant);
133
}
134
135
.md-filled-text-field:hover :not(label, span, small) {
136
 padding-bottom: 0.5rem;
137
 border-bottom-width: 0.0625rem;
138
 border-bottom-color: var(--md-sys-color-on-surface);
139
}
140
141
.md-filled-text-field:hover::after {
142
 background-color: var(--md-sys-color-on-surface);
143
 opacity: var(--md-sys-state-hover-state-layer-opacity);
144
}
145
146
.md-filled-text-field:hover small {
147
 color: var(--md-sys-color-on-surface-variant);
148
}
149
150
.md-filled-text-field:focus-within span,
151
.md-filled-text-field:focus-within label {
152
 color: var(--md-sys-color-primary);
153
}
154
155
.md-filled-text-field :focus {
156
 color: var(--md-sys-color-on-surface);
157
 outline: none;
158
 padding-bottom: 0.4375rem;
159
 border-bottom-width: 0.125rem;
160
 border-bottom-color: var(--md-sys-color-primary);
161
}
162
163
.md-filled-text-field:hover :focus {
164
 padding-bottom: 0.4375rem;
165
 border-bottom-width: 0.125rem;
166
}
167
168
.md-filled-text-field:focus-within small {
169
 color: var(--md-sys-color-on-surface-variant);
170
}
171
172
.md-filled-text-field :invalid {
173
 color: var(--md-sys-color-on-surface);
174
 border-bottom-color: var(--md-sys-color-error);
175
}
176
177
.md-filled-text-field :invalid+span,
178
.md-filled-text-field :invalid+label {
179
 color: var(--md-sys-color-error);
180
}
181
182
.md-filled-text-field :invalid~small,
183
.md-filled-text-field:hover :invalid~small,
184
.md-filled-text-field:focus-within .input-text:invalid~small {
185
 color: var(--md-sys-color-error);
186
}
187
188
.md-filled-text-field :invalid:focus {
189
 caret-color: var(--md-sys-color-error);
190
 border-bottom-color: var(--md-sys-color-error);
191
}
192
193
.md-filled-text-field:hover :invalid {
194
 color: var(--md-sys-color-on-surface);
195
 border-bottom-color: var(--md-sys-color-error);
196
}

4. public / libmde / md-list.css

1
.md-list {
2
 margin: 0.5rem 0;
3
 padding: 0;
4
 list-style-type: none;
5
}
6
7
.md-list .md-one-line,
8
.md-list .md-two-line,
9
.md-list .md-three-line {
10
 position: relative;
11
 display: flex;
12
 box-sizing: border-box;
13
}
14
15
/* container */
16
.md-list .md-one-line::before,
17
.md-list .md-two-line::before,
18
.md-list .md-three-line::before {
19
 content: "";
20
 position: absolute;
21
 z-index: -2;
22
 top: 0;
23
 right: 0;
24
 left: 0;
25
 bottom: 0;
26
 background-color: var(--md-sys-color-surface);
27
}
28
29
/* state layer */
30
.md-list .md-one-line::after,
31
.md-list .md-two-line::after,
32
.md-list .md-three-line::after {
33
 content: "";
34
 position: absolute;
35
 z-index: -1;
36
 top: 0;
37
 right: 0;
38
 left: 0;
39
 bottom: 0;
40
 background-color: transparent;
41
}
42
43
.md-list .md-one-line {
44
 align-items: center;
45
 gap: 1rem;
46
 min-height: 3.5rem;
47
 padding: 0.5rem 1.5rem 0.5rem 1rem;
48
}
49
50
.md-list .md-one-line.video,
51
.md-list .md-two-line.video {
52
 padding: 0.75rem 1.5rem 0.75rem 0;
53
}
54
55
.md-list .md-two-line,
56
.md-list .md-three-line {
57
 flex-flow: column;
58
}
59
60
.md-list .md-two-line {
61
 justify-content: center;
62
 min-height: 4.5rem;
63
 padding: 0.5rem 1.5rem 0.5rem 1rem;
64
}
65
66
.md-list .md-two-line.icon,
67
.md-list .md-two-line.avatar,
68
.md-list .md-two-line.image,
69
.md-list .md-two-line.video,
70
.md-list .md-three-line.icon,
71
.md-list .md-three-line.avatar,
72
.md-list .md-three-line.image,
73
.md-list .md-three-line.video {
74
 display: grid;
75
 column-gap: 1rem;
76
 row-gap: 0;
77
 grid-template-areas:
78
  "img headline"
79
  "img supporting";
80
}
81
82
.md-list .md-two-line.icon,
83
.md-list .md-two-line.avatar,
84
.md-list .md-two-line.image,
85
.md-list .md-two-line.video {
86
 align-content: center;
87
 grid-template-rows: 1fr 1fr;
88
}
89
90
.md-list .md-two-line.icon,
91
.md-list .md-three-line.icon {
92
 grid-template-columns: var(--iconSize) 1fr;
93
}
94
95
.md-list .md-two-line.avatar,
96
.md-list .md-three-line.avatar {
97
 grid-template-columns: var(--avatarSize) 1fr;
98
}
99
100
.md-list .md-two-line.image,
101
.md-list .md-three-line.image {
102
 grid-template-columns: var(--imageSize) 1fr;
103
}
104
105
.md-list .md-two-line.video,
106
.md-list .md-three-line.video {
107
 grid-template-columns: var(--videoWidth) 1fr;
108
}
109
110
.md-list .md-three-line {
111
 align-content: flex-start;
112
 min-height: 5.5rem;
113
 padding: 0.75rem 1.5rem 0.75rem 1rem;
114
}
115
116
.md-list .md-three-line.video {
117
 padding: 0.75rem 1.5rem 0.75rem 0;
118
}
119
120
.md-list .md-three-line.icon,
121
.md-list .md-three-line.avatar,
122
.md-list .md-three-line.image,
123
.md-list .md-three-line.video {
124
 align-content: start;
125
 grid-template-rows: var(--md-sys-typescale-label-large-line-height) 1fr;
126
}
127
128
/* state layer */
129
.md-list .md-one-line:hover::after,
130
.md-list .md-two-line:hover::after,
131
.md-list .md-three-line:hover::after {
132
 background-color: var(--md-sys-color-on-surface);
133
 opacity: var(--md-sys-state-hover-state-layer-opacity);
134
}
135
136
/* state layer */
137
.md-list a.md-one-line:focus::after,
138
.md-list a.md-two-line:focus::after,
139
.md-list a.md-three-line:focus::after,
140
.md-list a.md-one-line:focus-visible::after,
141
.md-list a.md-two-line:focus-visible::after,
142
.md-list a.md-three-line:focus-visible::after {
143
 background-color: var(--md-sys-color-on-surface);
144
 opacity: var(--md-sys-state-focus-state-layer-opacity);
145
}
146
147
.md-list a:focus,
148
.md-list a:focus-visible {
149
 outline: none;
150
}
151
152
.md-list a:active {
153
 background-position: center;
154
 background-image:
155
  radial-gradient(circle, var(--md-riple-color) 1%, transparent 1%);
156
 background-size: 100%;
157
 animation-name: md-ripple;
158
 animation-duration: var(--md-sys-motion-duration-500);
159
 box-shadow: var(--md-box_shadow_level0) !important;
160
}
161
162
/* state layer */
163
.md-list a.md-one-line:active::after,
164
.md-list a.md-two-line:active::after,
165
.md-list a.md-three-line:active::after {
166
 background-color: var(--md-sys-color-on-surface);
167
 opacity: var(--md-sys-state-pressed-state-layer-opacity);
168
}
169
170
.md-list a.md-two-line,
171
.md-list a.md-three-line {
172
 text-decoration: none;
173
}
174
175
.md-list a.md-two-line .headline,
176
.md-list a.md-three-line .headline {
177
 text-decoration: underline;
178
}
179
180
.md-list .headline {
181
 grid-area: headline;
182
 display: block;
183
 box-sizing: border-box;
184
 color: var(--md-sys-color-on-surface);
185
 font-family: var(--md-sys-typescale-body-large-font);
186
 font-weight: var(--md-sys-typescale-body-large-weight);
187
 font-size: var(--md-sys-typescale-body-large-size);
188
 font-style: var(--md-sys-typescale-body-large-font-style);
189
 letter-spacing: var(--md-sys-typescale-body-large-tracking);
190
 line-height: var(--md-sys-typescale-body-large-line-height);
191
 text-transform: var(--md-sys-typescale-body-large-text-transform);
192
 text-decoration: var(--md-sys-typescale-body-large-text-decoration);
193
 max-height: var(--md-sys-typescale-body-large-line-height);
194
 white-space: nowrap;
195
 text-overflow: ellipsis;
196
 overflow: hidden;
197
}
198
199
.md-list .md-two-line.icon .headline,
200
.md-list .md-two-line.avatar .headline,
201
.md-list .md-two-line.image .headline,
202
.md-list .md-two-line.video .headline,
203
.md-list .md-three-line.icon .headline,
204
.md-list .md-three-line.avatar .headline,
205
.md-list .md-three-line.image .headline,
206
.md-list .md-three-line.video .headline {
207
 align-self: end;
208
}
209
210
.md-list .supporting {
211
 grid-area: supporting;
212
 display: -webkit-box;
213
 -webkit-box-orient: vertical;
214
 overflow: hidden;
215
 box-sizing: border-box;
216
 align-self: start;
217
 font-family: var(--md-sys-typescale-body-medium-font);
218
 font-weight: var(--md-sys-typescale-body-medium-weight);
219
 font-size: var(--md-sys-typescale-body-medium-size);
220
 font-style: var(--md-sys-typescale-body-medium-font-style);
221
 letter-spacing: var(--md-sys-typescale-body-medium-tracking);
222
 line-height: var(--md-sys-typescale-body-medium-line-height);
223
 text-transform: var(--md-sys-typescale-body-medium-text-transform);
224
 text-decoration: var(--md-sys-typescale-body-medium-text-decoration);
225
}
226
227
.md-list .md-two-line .supporting {
228
 max-height: var(--md-sys-typescale-body-medium-line-height);
229
 line-clamp: 1;
230
 -webkit-line-clamp: 1;
231
}
232
233
.md-list .md-three-line .supporting {
234
 max-height: calc(2 * var(--md-sys-typescale-body-medium-line-height));
235
 line-clamp: 2;
236
 -webkit-line-clamp: 2;
237
}
238
239
.md-list .avatar img,
240
.md-list .avatar label,
241
.md-list .avatar .material-symbols-outlined:first-child {
242
 flex-shrink: 0;
243
 background-color: var(--md-sys-color-primary-container);
244
 color: var(--md-sys-color-on-primary-container);
245
 border-radius: 50%;
246
 width: var(--avatarSize);
247
 height: var(--avatarSize);
248
}
249
250
.md-list .avatar label {
251
 display: inline-block;
252
 font-family: var(--md-sys-typescale-title-medium-font);
253
 font-weight: var(--md-sys-typescale-title-medium-weight);
254
 font-size: var(--md-sys-typescale-title-medium-size);
255
 font-style: var(--md-sys-typescale-title-medium-font-style);
256
 letter-spacing: var(--md-sys-typescale-title-medium-tracking);
257
 line-height: var(--md-sys-typescale-title-medium-line-height);
258
 text-transform: var(--md-sys-typescale-title-medium-text-transform);
259
 text-decoration: var(--md-sys-typescale-title-medium-text-decoration);
260
 overflow: hidden;
261
}
262
263
.md-list .avatar .material-symbols-outlined:first-child {
264
 font-size: var(--avatarSize);
265
}
266
267
.md-list .avatar.md-two-line img,
268
.md-list .avatar.md-two-line label,
269
.md-list .avatar.md-two-line .material-symbols-outlined:first-child {
270
 grid-area: img;
271
 align-self: center;
272
}
273
274
.md-list .avatar.md-three-line img,
275
.md-list .avatar.md-three-line label,
276
.md-list .avatar.md-three-line .material-symbols-outlined:first-child {
277
 grid-area: img;
278
 align-self: start;
279
}
280
281
.md-list .icon img,
282
.md-list .icon .material-symbols-outlined:first-child {
283
 flex-shrink: 0;
284
 color: var(--md-sys-color-on-surface-variant);
285
 width: var(--iconSize);
286
 height: var(--iconSize);
287
}
288
289
.md-list .icon .material-symbols-outlined:first-child {
290
 font-size: var(--iconSize);
291
}
292
293
.md-list .icon.md-two-line img,
294
.md-list .icon.md-two-line .material-symbols-outlined:first-child {
295
 grid-area: img;
296
 align-self: center;
297
}
298
299
.md-list .icon.md-three-line img,
300
.md-list .icon.md-three-line .material-symbols-outlined:first-child {
301
 grid-area: img;
302
 align-self: start;
303
}
304
305
.md-list .video img {
306
 flex-shrink: 0;
307
 color: var(--md-sys-color-on-surface-variant);
308
 width: var(--videoWidth);
309
 height: var(--videoHeight);
310
}
311
312
.md-list .video.md-two-line img {
313
 grid-area: img;
314
 align-self: center;
315
}
316
317
.md-list .video.md-three-line img {
318
 grid-area: img;
319
 align-self: start;
320
}
321
322
.md-list .image img,
323
.md-list .image .material-symbols-outlined:first-child {
324
 flex-shrink: 0;
325
 color: var(--md-sys-color-on-surface-variant);
326
 width: var(--imageSize);
327
 height: var(--imageSize);
328
}
329
330
.md-list .image .material-symbols-outlined:first-child {
331
 font-size: var(--imageSize);
332
}
333
334
.md-list .image.md-two-line img,
335
.md-list .image.md-two-line .material-symbols-outlined:first-child {
336
 grid-area: img;
337
 align-self: center;
338
}
339
340
.md-list .image.md-three-line img,
341
.md-list .image.md-three-line .material-symbols-outlined:first-child {
342
 grid-area: img;
343
 align-self: start;
344
}

5. public / libmde / md-menu.css

1
.md-menu {
2
 display: none;
3
 z-index: 2;
4
 box-sizing: border-box;
5
 cursor: default;
6
 padding: 0.25rem 0;
7
 border-radius:
8
  var(--md-sys-shape-corner-extra-small-default-size);
9
 background-color: var(--md-sys-color-surface-container-low);
10
 box-shadow: var(--md-box_shadow_level2);
11
 transform: translateY(-50%) scaleY(0);
12
 transition-timing-function:
13
  cubic-bezier(var(--md-sys-motion-easing-standard-x0),
14
   var(--md-sys-motion-easing-standard-y0),
15
   var(--md-sys-motion-easing-standard-x1),
16
   var(--md-sys-motion-easing-standard-y1));
17
 transition-property: display, transform;
18
 transition-behavior: allow-discrete;
19
 transition-duration: var(--md-sys-motion-duration-500);
20
}
21
22
.md-menu.open {
23
 display: block;
24
 transform: translateY(0) scaleY(1);
25
}
26
27
@starting-style {
28
 .md-menu.open {
29
  display: block;
30
  transform: translateY(-50%) scaleY(0);
31
 }
32
}
33
34
/* container */
35
.md-menu>*::after {
36
 content: "";
37
 position: absolute;
38
 z-index: -2;
39
 top: 0;
40
 right: 0;
41
 left: 0;
42
 bottom: 0;
43
}
44
45
/* container */
46
.md-menu>.selected::after {
47
 background-color: var(--md-sys-color-secondary-container);
48
}
49
50
/* label, shape */
51
.md-menu>* {
52
 position: relative;
53
 display: block;
54
 box-sizing: border-box;
55
 height: 3rem;
56
 line-height: 3rem;
57
 padding: 0 0.75rem;
58
 color: var(--md-sys-color-on-surface);
59
 font-family: var(--md-sys-typescale-label-large-font);
60
 font-weight: var(--md-sys-typescale-label-large-weight);
61
 font-size: var(--md-sys-typescale-label-large-size);
62
 font-style: var(--md-sys-typescale-label-large-font-style);
63
 letter-spacing: var(--md-sys-typescale-label-large-tracking);
64
 text-transform: var(--md-sys-typescale-label-large-text-transform);
65
 text-decoration: var(--md-sys-typescale-label-large-text-decoration);
66
 white-space: nowrap;
67
 text-overflow: ellipsis;
68
 overflow: hidden;
69
}
70
71
/* label, shape */
72
.md-menu>.selected {
73
 color: var(--md-sys-color-on-secondary-container);
74
}
75
76
/* state layer */
77
.md-menu>*::before {
78
 content: "";
79
 position: absolute;
80
 z-index: -1;
81
 top: 0;
82
 right: 0;
83
 left: 0;
84
 bottom: 0;
85
}
86
87
/* icon */
88
.md-menu>* span {
89
 position: relative;
90
 margin-right: 0.75rem;
91
 vertical-align: middle;
92
 color: var(--md-sys-color-on-surface-variant);
93
 font-size: 1.5rem;
94
 width: 1.5rem;
95
 height: 1.5rem;
96
}
97
98
/* icon */
99
.md-menu>.selected span {
100
 color: var(--md-sys-color-on-secondary-container);
101
}
102
103
/* state layer */
104
.md-menu>:hover::before {
105
 background-color: var(--md-sys-color-on-surface);
106
 opacity: var(--md-sys-state-hover-state-layer-opacity);
107
}
108
109
/* label, shape */
110
.md-menu>:hover {
111
 color: var(--md-sys-color-on-surface);
112
}
113
114
/* icon */
115
.md-menu>:hover span {
116
 color: var(--md-sys-color-on-surface-variant);
117
}
118
119
/* state layer */
120
.md-menu>:focus::before {
121
 background-color: var(--md-sys-color-on-surface);
122
 opacity: var(--md-sys-state-focus-state-layer-opacity);
123
}
124
125
/* label, shape */
126
.md-menu>:focus {
127
 color: var(--md-sys-color-on-surface);
128
 outline: none;
129
}
130
131
/* icon */
132
.md-menu>:focus span {
133
 color: var(--md-sys-color-on-surface-variant);
134
}
135
136
/* label, shape */
137
.md-menu>:active {
138
 background-position: center;
139
 background-image:
140
  radial-gradient(circle, var(--md-riple-color) 1%, transparent 1%);
141
 background-size: 100%;
142
 animation-name: md-ripple;
143
 animation-duration: var(--md-sys-motion-duration-500);
144
 color: var(--md-sys-color-on-surface);
145
}
146
147
/* state layer */
148
.md-menu>:active::before {
149
 background-color: var(--md-sys-color-on-surface);
150
 opacity: var(--md-sys-state-pressed-state-layer-opacity);
151
}
152
153
154
/* icon */
155
.md-menu>:active span {
156
 color: var(--md-sys-color-on-surface-variant);
157
}
158
159
.md-menu input[type="radio"] {
160
 appearance: none;
161
 transform: scaleX(0);
162
}

6. public / libmde / md-options-menu.js

1
import { abreElementoHtml } from "../libclienteweb/abreElementoHtml.js"
2
import { cierraElementoHtmo } from "../libclienteweb/cierraElementoHtmo.js"
3
import { querySelector } from "../libclienteweb/querySelector.js"
4
5
export class MdOptionsMenu extends HTMLElement {
6
7
 getContent() {
8
  return /* HTML */`
9
10
   <style>
11
12
    :host {
13
     position: absolute;
14
    }
15
16
   </style>
17
18
   <slot></slot>`
19
 }
20
21
 constructor() {
22
  super()
23
  const shadow = this.attachShadow({ mode: "open" })
24
  shadow.innerHTML = this.getContent()
25
  this._configuraOpciones = this._configuraOpciones.bind(this)
26
27
  /**
28
   * @private
29
   * @type { HTMLSlotElement }
30
   */
31
  this._slot = querySelector(shadow, "slot")
32
  /**
33
   * @private
34
   * @type { HTMLElement[] }
35
   */
36
  this._opciones = []
37
  this._slot.addEventListener("slotchange", this._configuraOpciones)
38
 }
39
40
 connectedCallback() {
41
  this.classList.add("md-menu")
42
  this.role = "listbox"
43
 }
44
45
 /**
46
  * @returns {readonly Readonly<HTMLElement>[]}
47
  */
48
 get opciones() {
49
  return this._opciones
50
 }
51
52
 get seleccion() {
53
  /** @type { HTMLInputElement | null } */
54
  const seleccionado = this.querySelector(".selected")
55
  return seleccionado === null ? "" : seleccionado.value
56
 }
57
58
 _configuraOpciones() {
59
  /**
60
   * @type {HTMLElement[]}
61
  */
62
  const opciones = []
63
  for (const opcion of this._slot.assignedElements()) {
64
   opcion.role = "option"
65
   if (opcion instanceof HTMLElement) {
66
    opciones.push(opcion)
67
   }
68
  }
69
  this._opciones = opciones
70
 }
71
72
 abre() {
73
  abreElementoHtml(this)
74
 }
75
76
77
 cierra() {
78
  cierraElementoHtmo(this)
79
 }
80
81
 /**
82
  * @param {string} value
83
  */
84
 muestraValue(value) {
85
  let texto = ""
86
  for (const opcion of this._opciones) {
87
   if (opcion.dataset.value === value) {
88
    opcion.classList.add("selected")
89
    let textContent = opcion.textContent
90
    if (texto === "" && textContent !== null) {
91
     textContent = textContent.trim()
92
     if (textContent !== "") {
93
      texto = textContent
94
     }
95
    }
96
   } else {
97
    opcion.classList.remove("selected")
98
   }
99
  }
100
  return texto
101
 }
102
103
}
104
105
customElements.define("md-options-menu", MdOptionsMenu)

7. public / libmde / md-outline-button.css

1
.md-outline-button {
2
 position: relative;
3
 box-sizing: border-box;
4
 border-radius: 1.25rem;
5
 height: 2.5rem;
6
 padding: 0 1.5rem;
7
 border: 0.0625rem solid var(--md-sys-color-outline);
8
 background-color: transparent;
9
 box-shadow: var(--md-box_shadow_level0);
10
 font-family: var(--md-sys-typescale-label-large-font);
11
 font-weight: var(--md-sys-typescale-label-large-weight);
12
 font-size: var(--md-sys-typescale-label-large-size);
13
 font-style: var(--md-sys-typescale-label-large-font-style);
14
 letter-spacing: var(--md-sys-typescale-label-large-tracking);
15
 text-transform: var(--md-sys-typescale-label-large-text-transform);
16
 text-decoration: var(--md-sys-typescale-label-large-text-decoration);
17
 color: var(--md-sys-color-primary);
18
 white-space: nowrap;
19
 text-overflow: ellipsis;
20
 overflow: hidden;
21
}
22
23
/* state layer */
24
.md-outline-button::after {
25
 content: "";
26
 position: absolute;
27
 z-index: -1;
28
 top: 0;
29
 right: 0;
30
 left: 0;
31
 bottom: 0;
32
 background-color: transparent;
33
}
34
35
.md-outline-button:hover {
36
 color: var(--md-sys-color-primary);
37
 border-color: var(--md-sys-color-outline);
38
}
39
40
/* state layer */
41
.md-outline-button:hover::after {
42
 background-color: var(--md-sys-color-primary);
43
 opacity: var(--md-sys-state-hover-state-layer-opacity);
44
}
45
46
.md-outline-button:focus {
47
 outline: none;
48
 color: var(--md-sys-color-primary);
49
 border-color: var(--md-sys-color-outline);
50
}
51
52
/* state layer */
53
.md-outline-button:focus::after {
54
 background-color: var(--md-sys-color-primary);
55
 opacity: var(--md-sys-state-focus-state-layer-opacity);
56
}
57
58
.md-outline-button:active {
59
 color: var(--md-sys-color-primary);
60
 border-color: var(--md-sys-color-outline);
61
 background-position: center;
62
 background-image:
63
  radial-gradient(circle, var(--md-riple-color) 1%, transparent 1%);
64
 background-size: 100%;
65
 animation-name: md-ripple;
66
 animation-duration: var(--md-sys-motion-duration-500);
67
 box-shadow: var(--md-box_shadow_level0) !important;
68
}
69
70
/* state layer */
71
.md-outline-button:active::after {
72
 background-color: var(--md-sys-color-primary);
73
 opacity: var(--md-sys-state-pressed-state-layer-opacity);
74
}
75
76
.md-outline-button:disabled {
77
 background-color: transparent !important;
78
 border-color: var(--md-sys-color-on-surface) !important;
79
 color: var(--md-sys-color-on-surface) !important;
80
 opacity: 0.38 !important;
81
}
82
83
/* container */
84
.md-outline-button:disabled::after {
85
 background-color: transparent !important;
86
 opacity: 1 !important;
87
}

8. public / libmde / md-select-menu.js

1
import { getAttribute } from "../libclienteweb/getAttribute.js"
2
import { querySelector } from "../libclienteweb/querySelector.js"
3
import { MdOptionsMenu } from "./md-options-menu.js"
4
5
export class MdSelectMenu extends HTMLElement {
6
7
 static get observedAttributes() {
8
  return ["options", "value", "required"]
9
 }
10
11
 getContent() {
12
  return /* HTML */ `
13
   <link rel="stylesheet" href="/css/material-symbols-outlined.css">
14
15
   <style>
16
    :host {
17
     display: block;
18
     cursor: default;
19
    }
20
21
    output {
22
     display: block;
23
     padding-right: 2rem;
24
     white-space: nowrap;
25
     text-overflow: ellipsis;
26
     overflow: hidden;
27
    }
28
29
    #up {
30
     position: absolute;
31
     bottom: 0.5rem;
32
     right: 0.75rem;
33
     display: none;
34
     color: var(--md-sys-color-on-surface-variant);
35
    }
36
37
    #down {
38
     position: absolute;
39
     bottom: 0.5rem;
40
     right: 0.75rem;
41
     color: var(--md-sys-color-on-surface-variant);
42
    }
43
44
    :host(.open) #up {
45
     display: inline-block;
46
    }
47
48
    :host(.open) #down {
49
     display: none;
50
    }
51
52
    :host(:invalid) #up,
53
    :host(:invalid) #down {
54
     color: var(--md-sys-color-error);
55
    }
56
57
   </style>
58
   <output></output>
59
   <span id="down" class="material-symbols-outlined">
60
    arrow_drop_down
61
   </span>
62
   <span id="up" class="material-symbols-outlined">
63
    arrow_drop_up
64
   </span>`
65
 }
66
67
 constructor() {
68
  super()
69
70
  const shadow = this.attachShadow({ mode: "open" })
71
  shadow.innerHTML = this.getContent()
72
73
  this._alterna = this._alterna.bind(this)
74
  this._onKeyDown = this._onKeyDown.bind(this)
75
  this._cierra = this._cierra.bind(this)
76
  this._clicEnDialogo = this._clicEnDialogo.bind(this)
77
  this.clicExterno = this.clicExterno.bind(this)
78
  this.muestraValue = this.muestraValue.bind(this)
79
80
  /**
81
   * @private
82
   * @type {string}
83
   */
84
  this._customValidity = ""
85
86
  /**
87
   * @private
88
   * @type { HTMLOutputElement }
89
   */
90
  this.output = querySelector(shadow, "output")
91
  /**
92
   * @private
93
   * @type { MdOptionsMenu | null }
94
   */
95
  this._optionsMenu = null
96
  /**
97
   * @protected
98
   * @readonly
99
   */
100
  this._internals = this.attachInternals()
101
  this._internals.role = "select"
102
  addEventListener("load", this.muestraValue)
103
 }
104
105
 connectedCallback() {
106
  this.tabIndex = 0
107
  this.role = "combobox"
108
  this.ariaHasPopup = "listbox"
109
  this.ariaExpanded = "false"
110
  this["aria-controls"] = this.options
111
  this.addEventListener("keydown", this._onKeyDown)
112
  const parentElement = this.parentElement
113
  if (parentElement !== null) {
114
   parentElement.addEventListener("click", this._alterna)
115
  }
116
 }
117
118
 /**
119
  * @param {string} nombreDeAtributo
120
  * @param {string} _valorAnterior
121
  * @param {string} _nuevoValor
122
  */
123
 attributeChangedCallback(nombreDeAtributo, _valorAnterior, _nuevoValor) {
124
  switch (nombreDeAtributo) {
125
   case "options":
126
    this._cambiaOptions()
127
    break
128
   case "value":
129
    this.muestraValue()
130
    break
131
   case "required":
132
    this._checkValidity()
133
    break
134
  }
135
 }
136
137
 get options() {
138
  return getAttribute(this, "options")
139
 }
140
141
 set options(options) {
142
  this.setAttribute("options", options)
143
 }
144
145
 _cambiaOptions() {
146
  if (this._optionsMenu !== null) {
147
   this._optionsMenu = null
148
  }
149
  this["aria-controls"] = this.options
150
 }
151
152
 get required() {
153
  return this.hasAttribute("required")
154
 }
155
156
 set required(required) {
157
  this.toggleAttribute("required", Boolean(required))
158
 }
159
160
 get value() {
161
  return getAttribute(this, "value")
162
 }
163
164
 set value(value) {
165
  this.setAttribute("value", value)
166
 }
167
168
 get name() {
169
  return getAttribute(this, "name")
170
 }
171
172
 set name(name) {
173
  this.setAttribute("name", name)
174
 }
175
176
 muestraValue() {
177
  const value = this.value
178
  this._internals.setFormValue(value)
179
180
  // En un futuro se usará esto en vez de la clase populated.
181
  // if (value === "") {
182
  //  this._internals.states.delete("populated")
183
  // } else {
184
  //  this._internals.states.add("populated")
185
  // }
186
187
  if (this.isConnected) {
188
   if (value === "") {
189
    this.classList.remove("populated")
190
   } else {
191
    this.classList.add("populated")
192
   }
193
   this._checkValidity()
194
   const optionsMenu = this.optionsMenu
195
   if (optionsMenu !== null) {
196
    this.output.value = optionsMenu.muestraValue(value)
197
   }
198
  }
199
 }
200
201
 get form() {
202
  return this._internals && this._internals.form
203
 }
204
205
 get willValidate() {
206
  return this._internals ? this._internals.willValidate : true
207
 }
208
209
 /**
210
  * @param {string} message
211
  */
212
 setCustomValidity(message) {
213
  this._customValidity = message
214
  this._checkValidity()
215
 }
216
217
 /**
218
  * @returns {ValidityState}
219
  */
220
 get validity() {
221
  return this._internals.validity
222
 }
223
224
 checkValidity() {
225
  return this._internals.checkValidity()
226
 }
227
228
 reportValidity() {
229
  return this._internals.reportValidity()
230
 }
231
232
 get validationMessage() {
233
  return this._internals.validationMessage
234
 }
235
 /** @returns {boolean} */
236
 _checkValidity() {
237
  if (this._customValidity !== "") {
238
   this._internals.setValidity({ customError: true }, this._customValidity)
239
   return false
240
  } else if (this.required && this.value === "") {
241
   this._internals.setValidity({ valueMissing: true }, "Seleccione una opción.")
242
   return false
243
  } else {
244
   this._internals.setValidity({})
245
   return true
246
  }
247
 }
248
249
 /** @private */
250
 _alterna() {
251
  if (this.classList.contains("open")) {
252
   this._cierra()
253
  } else {
254
   this._abre()
255
  }
256
 }
257
258
 /** @private */
259
 _abre() {
260
  this.classList.add("open")
261
  const bounds = this.getBoundingClientRect()
262
   const optionsMenu = this.optionsMenu
263
   if (optionsMenu !== null) {
264
    optionsMenu.style.top = `${ bounds.bottom}px`
265
    optionsMenu.style.left = `${ bounds.left}px`
266
    optionsMenu.style.width = `${ bounds.width}px`
267
    optionsMenu.abre()
268
    this.focus()
269
    optionsMenu.addEventListener("click", this._clicEnDialogo)
270
   }
271
   this.ariaExpanded = "true"
272
   document.addEventListener("click", this.clicExterno)
273
 }
274
275
 /** @private */
276
 _cierra() {
277
  this.classList.remove("open")
278
  const optionsMenu = this.optionsMenu
279
  if (optionsMenu !== null) {
280
   optionsMenu.cierra()
281
   optionsMenu.removeEventListener("click", this._clicEnDialogo)
282
  }
283
  this.ariaExpanded = "false"
284
  document.removeEventListener("click", this.clicExterno)
285
  this.dispatchEvent(new Event("input", { bubbles: true }))
286
 }
287
288
 get optionsMenu() {
289
  if (this._optionsMenu === null) {
290
   if (this.options !== "") {
291
    const optionsMenu = document.getElementById(this.options)
292
    if (optionsMenu instanceof MdOptionsMenu) {
293
     this._optionsMenu = optionsMenu
294
    } else {
295
     throw new Error(`Valor incorrecto para options: "${this.options}".`)
296
    }
297
   }
298
  }
299
  return this._optionsMenu
300
 }
301
302
 /** @private */
303
 _avanzaOpcion() {
304
  const i = this._valueIndex
305
  if (i > -1) {
306
   const optionsMenu = this.optionsMenu
307
   if (optionsMenu !== null) {
308
    const opciones = optionsMenu.opciones
309
    if (i < opciones.length - 1) {
310
     this.value = getAttribute(opciones[i + 1], "data-value")
311
    }
312
   }
313
  }
314
 }
315
316
 /** @private */
317
 _retrocedeOpcion() {
318
  const i = this._valueIndex
319
  if (i > -1) {
320
   const optionsMenu = this.optionsMenu
321
   if (optionsMenu !== null) {
322
    const opciones = optionsMenu.opciones
323
    if (i > 0) {
324
     this.value = getAttribute(opciones[i - 1], "data-value")
325
    }
326
   }
327
  }
328
 }
329
330
 /**
331
  * @private
332
  * @returns {number}
333
  */
334
 get _valueIndex() {
335
  const value = this.value
336
  const optionsMenu = this.optionsMenu
337
  return (optionsMenu === null
338
   ? -1
339
   : optionsMenu.opciones.findIndex(opcion => opcion.dataset.value === value))
340
 }
341
342
 /**
343
  * @private
344
  * @param {Event} event
345
  */
346
 _clicEnDialogo(event) {
347
  const target = event.target
348
  const optionsMenu = this.optionsMenu
349
  let value = ""
350
  if (optionsMenu !== null) {
351
   for (const opcion of optionsMenu.opciones) {
352
    if (opcion === target) {
353
     opcion.classList.add("selected")
354
     value = getAttribute(opcion, "data-value")
355
    } else {
356
     opcion.classList.remove("selected")
357
    }
358
   }
359
  }
360
  this.value = value
361
  this._cierra()
362
  this.focus()
363
}
364
365
 /**
366
  * @param {Event} evt
367
  */
368
 clicExterno(evt) {
369
  const target = evt.target
370
  const parentElement = this.parentElement
371
  const optionsMenu = this._optionsMenu
372
  if (this.classList.contains("open")
373
   && target instanceof HTMLElement
374
   && parentElement !== null
375
   && !parentElement.contains(target)
376
   && optionsMenu !== null
377
   && !optionsMenu.contains(target)) {
378
   this._cierra()
379
  }
380
 }
381
382
 /**
383
  * @param { KeyboardEvent } event
384
  */
385
 _onKeyDown(event) {
386
  const key = event.key
387
  const optionsMenu = this._optionsMenu
388
  if (optionsMenu !== null) {
389
   if (optionsMenu.classList.contains("open")) {
390
    if (key === "ArrowDown") {
391
     event.preventDefault()
392
     this._avanzaOpcion()
393
    } else if (key === "ArrowUp") {
394
     event.preventDefault()
395
     this._retrocedeOpcion()
396
    } else if (key === "Escape") {
397
     event.preventDefault()
398
     this._cierra()
399
    } else if (key === " ") {
400
     event.preventDefault()
401
     this._cierra()
402
    } else if (key === "Tab") {
403
     this._cierra()
404
    } else {
405
     event.preventDefault()
406
    }
407
   } else if (key === " ") {
408
    event.preventDefault()
409
    this._abre()
410
   } else if (key === "Tab") {
411
    this._cierra()
412
   } else {
413
    event.preventDefault()
414
   }
415
  }
416
 }
417
418
}
419
420
MdSelectMenu.formAssociated = true
421
422
customElements.define("md-select-menu", MdSelectMenu)

9. public / libmde / md-tab.css

1
.md-tab {
2
 display: flex;
3
 background-color: transparent;
4
 align-items: stretch;
5
 flex-wrap: nowrap;
6
 overflow-x: auto;
7
 position: sticky;
8
 z-index: 1;
9
}
10
11
.md-tab.fixed {
12
 justify-content: center;
13
}
14
15
.md-tab.scrollable {
16
 padding-left: 2rem;
17
 gap: 1rem;
18
}
19
20
.md-tab.scroll {
21
 background-color: var(--md-sys-color-surface-container-low);
22
}
23
24
.md-tab a {
25
 position: relative;
26
 display: flex;
27
 flex-direction: column;
28
 justify-content: start;
29
 align-items: center;
30
 color: var(--md-sys-color-on-surface-variant);
31
 font-family: var(--md-sys-typescale-title-small-font);
32
 font-weight: var(--md-sys-typescale-title-small-weight);
33
 font-size: var(--md-sys-typescale-title-small-size);
34
 font-style: var(--md-sys-typescale-title-small-font-style);
35
 letter-spacing: var(--md-sys-typescale-title-small-tracking);
36
 line-height: var(--md-sys-typescale-title-small-line-height);
37
 text-transform: var(--md-sys-typescale-title-small-text-transform);
38
 text-decoration: var(--md-sys-typescale-title-small-text-decoration);
39
 text-align: center;
40
 box-sizing: border-box;
41
 border-bottom: 0.1875rem solid var(--md-sys-color-surface);
42
}
43
44
.md-tab.fixed a {
45
 flex: 0 0 var(--tabWidth);
46
}
47
48
.md-tab.scrollable a {
49
 flex: 0 0 auto;
50
}
51
52
.md-tab a.active {
53
 border-bottom-color: var(--md-sys-color-primary);
54
}
55
56
/* state layer */
57
.md-tab a::after {
58
 content: "";
59
 position: absolute;
60
 z-index: -1;
61
 top: 0;
62
 right: 0;
63
 left: 0;
64
 bottom: 0;
65
 background-color: transparent;
66
}
67
68
.md-tab span {
69
 font-size: var(--iconSize);
70
 height: var(--iconSize);
71
 width: var(--iconSize);
72
 color: var(--md-sys-color-on-surface-variant);
73
}
74
75
.md-tab .active span {
76
 color: var(--md-sys-color-primary);
77
 font-variation-settings: 'FILL' 1, 'wght' 700, 'GRAD' 0, 'opsz' 48;
78
}
79
80
.md-tab a:hover {
81
 color: var(--md-sys-color-on-surface);
82
}
83
84
/* state layer */
85
.md-tab a:hover::after {
86
 background-color: var(--md-sys-color-on-surface);
87
 opacity: var(--md-sys-state-hover-state-layer-opacity);
88
}
89
90
.md-tab a.active:hover {
91
 color: var(--md-sys-color-primary);
92
}
93
94
/* state layer */
95
.md-tab a.active:hover::after {
96
 background-color: var(--md-sys-color-primary);
97
 opacity: var(--md-sys-state-hover-state-layer-opacity);
98
}
99
100
.md-tab a:hover span {
101
 color: var(--md-sys-color-on-surface);
102
}
103
104
.md-tab a.active:hover span {
105
 color: var(--md-sys-color-primary);
106
}
107
108
.md-tab a:focus {
109
 outline: none;
110
}
111
112
/* state layer */
113
.md-tab a:focus::after {
114
 background-color: var(--md-sys-color-on-surface);
115
 opacity: var(--md-sys-state-focus-state-layer-opacity);
116
}
117
118
/* state layer */
119
.md-tab a.active:focus::after {
120
 background-color: var(--md-sys-color-primary);
121
 opacity: var(--md-sys-state-hover-state-layer-opacity);
122
}
123
124
.md-tab a:active {
125
 background-position: center;
126
 background-image:
127
   radial-gradient(circle, var(--md-riple-color) 1%, transparent 1%);
128
 background-size: 100%;
129
 animation-name: md-ripple;
130
 animation-duration: var(--md-sys-motion-duration-500);
131
}
132
133
/* state layer */
134
.md-tab a:active::after {
135
 background-color: var(--md-sys-color-on-surface);
136
 opacity: var(--md-sys-state-pressed-state-layer-opacity);
137
}
138
139
/* state layer */
140
.md-tab a.active:active::after {
141
 background-color: var(--md-sys-color-primary);
142
 opacity: var(--md-sys-state-pressed-state-layer-opacity);
143
}