@rdlabo/ionic-theme-ios27 0.2.1 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +84 -126
- package/dist/css/utils/structured-list.css +1 -0
- package/dist/native/components/searchable-tabs.d.ts +2 -0
- package/dist/native/components/searchable-tabs.d.ts.map +1 -1
- package/dist/native/components/searchable-tabs.js +131 -21
- package/dist/native/runtime.d.ts.map +1 -1
- package/dist/native/runtime.js +14 -7
- package/docs/migration.md +105 -3
- package/docs/native-ui-shell.md +15 -3
- package/ios/Sources/IonicNativeUIShellPlugin/Components/ShellSearchController.swift +243 -64
- package/ios/Sources/IonicNativeUIShellPlugin/Components/ShellTabBar.swift +77 -6
- package/ios/Sources/IonicNativeUIShellPlugin/IonicNativeUIShellPlugin.swift +79 -12
- package/ios/Tests/IonicNativeUIShellPluginTests/ShellSnapshotTests.swift +258 -2
- package/node_modules/@rdlabo/ionic-theme-utils/package.json +4 -0
- package/node_modules/@rdlabo/ionic-theme-utils/scripts/copy-structured-list.mjs +14 -0
- package/package.json +4 -3
- package/src/native/components/searchable-tabs.ts +142 -22
- package/src/native/runtime.ts +11 -6
- package/src/styles/components/ion-list.scss +1 -1
- package/src/styles/md-ion-list-inset.scss +1 -1
- package/src/styles/utils/dark/ion-list.scss +1 -1
- package/src/styles/utils/structured-list.scss +310 -0
- package/docs/ios-adaptive.md +0 -90
- /package/docs/{experimental-animation.md → animation.md} +0 -0
|
@@ -25,7 +25,55 @@ interface SearchState {
|
|
|
25
25
|
export const createSearchSupport = (doc: Document, id: (element: Element) => string, schedule: () => void) => {
|
|
26
26
|
const states = new Map<NativeSearchBinding, SearchState>();
|
|
27
27
|
let keyboardVisible = false;
|
|
28
|
+
let heldKeyboardResize: string | null = null;
|
|
29
|
+
let keyboardResizeGeneration = 0;
|
|
30
|
+
type KeyboardPlugin = {
|
|
31
|
+
setResizeMode?: (options: { mode: string }) => Promise<void>;
|
|
32
|
+
getResizeMode?: () => Promise<{ mode?: string }>;
|
|
33
|
+
};
|
|
34
|
+
const keyboardPlugin = (): KeyboardPlugin | undefined =>
|
|
35
|
+
(
|
|
36
|
+
doc.defaultView as Window & {
|
|
37
|
+
Capacitor?: { Plugins?: { Keyboard?: KeyboardPlugin } };
|
|
38
|
+
}
|
|
39
|
+
)?.Capacitor?.Plugins?.Keyboard;
|
|
28
40
|
const projected = (binding: NativeSearchBinding) => binding.footer.hasAttribute(marker);
|
|
41
|
+
// Hold Cap resize at none while search is active so WebView shrink does not fight UISearchTab.
|
|
42
|
+
const holdKeyboardResize = () => {
|
|
43
|
+
const keyboard = keyboardPlugin();
|
|
44
|
+
if (!keyboard?.setResizeMode || heldKeyboardResize !== null) return;
|
|
45
|
+
const setResizeMode = keyboard.setResizeMode.bind(keyboard);
|
|
46
|
+
const generation = ++keyboardResizeGeneration;
|
|
47
|
+
heldKeyboardResize = 'native';
|
|
48
|
+
void (async () => {
|
|
49
|
+
let restoreMode = 'native';
|
|
50
|
+
try {
|
|
51
|
+
const current = await keyboard.getResizeMode?.();
|
|
52
|
+
if (typeof current?.mode === 'string') restoreMode = current.mode;
|
|
53
|
+
} catch {
|
|
54
|
+
/* keep default restore target */
|
|
55
|
+
}
|
|
56
|
+
if (generation !== keyboardResizeGeneration) return;
|
|
57
|
+
heldKeyboardResize = restoreMode;
|
|
58
|
+
try {
|
|
59
|
+
await setResizeMode({ mode: 'none' });
|
|
60
|
+
} catch {
|
|
61
|
+
if (generation === keyboardResizeGeneration) heldKeyboardResize = null;
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
// Leave raced ahead of setResizeMode(none) — undo the stale none apply.
|
|
65
|
+
if (generation !== keyboardResizeGeneration) void setResizeMode({ mode: restoreMode }).catch(() => undefined);
|
|
66
|
+
})();
|
|
67
|
+
};
|
|
68
|
+
const hasActiveProjection = () => [...states.keys()].some((binding) => binding.active && projected(binding));
|
|
69
|
+
const releaseKeyboardResize = () => {
|
|
70
|
+
if (hasActiveProjection()) return;
|
|
71
|
+
keyboardResizeGeneration++;
|
|
72
|
+
const mode = heldKeyboardResize;
|
|
73
|
+
heldKeyboardResize = null;
|
|
74
|
+
if (!mode) return;
|
|
75
|
+
void keyboardPlugin()?.setResizeMode?.({ mode });
|
|
76
|
+
};
|
|
29
77
|
const isCurrent = (state: SearchState) =>
|
|
30
78
|
state.bar.isConnected &&
|
|
31
79
|
state.binding.footer.querySelector('ion-searchbar') === state.bar &&
|
|
@@ -118,7 +166,10 @@ export const createSearchSupport = (doc: Document, id: (element: Element) => str
|
|
|
118
166
|
return {
|
|
119
167
|
keyboard(visible: boolean) {
|
|
120
168
|
keyboardVisible = visible;
|
|
121
|
-
if (!visible)
|
|
169
|
+
if (!visible) {
|
|
170
|
+
states.forEach((state) => (state.keyboardHidden = false));
|
|
171
|
+
releaseKeyboardResize();
|
|
172
|
+
}
|
|
122
173
|
schedule();
|
|
123
174
|
},
|
|
124
175
|
keepSearchTabsVisible() {
|
|
@@ -142,6 +193,13 @@ export const createSearchSupport = (doc: Document, id: (element: Element) => str
|
|
|
142
193
|
}
|
|
143
194
|
}
|
|
144
195
|
const inactive: [Candidate, ShellSearch][] = [];
|
|
196
|
+
const pageHidden = (element: HTMLElement) => !!element.closest('.ion-page-hidden, .ion-page-invisible');
|
|
197
|
+
const retainInactive = (candidate: Candidate, state?: SearchState, keepAvailable = false) => {
|
|
198
|
+
// Keep ShellSearchController across page transitions instead of demoting to UITabBar.
|
|
199
|
+
// While the searchable page is only blocked mid-transition (not yet hidden), keep
|
|
200
|
+
// available:true so native chrome does not swap to ordinary idleBar over album content.
|
|
201
|
+
if (state?.last) inactive.push([candidate, { ...state.last, active: false, available: keepAvailable, focused: false }]);
|
|
202
|
+
};
|
|
145
203
|
for (const binding of bindings) {
|
|
146
204
|
const candidate = candidates.find((c) => c.element === binding.tabBar);
|
|
147
205
|
if (!candidate || candidate.control.search || binding.tabBar.getAttribute('slot') !== 'bottom') continue;
|
|
@@ -153,9 +211,37 @@ export const createSearchSupport = (doc: Document, id: (element: Element) => str
|
|
|
153
211
|
)
|
|
154
212
|
continue;
|
|
155
213
|
if (isShellDisabled(fab) || isShellDisabled(binding.footer)) continue;
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
214
|
+
const pageUnavailable = blocked(binding.footer) || !!binding.footer.closest(excluded);
|
|
215
|
+
const existing = states.get(binding);
|
|
216
|
+
if (pageUnavailable) {
|
|
217
|
+
retainInactive(candidate, existing, !!existing?.last?.available && !pageHidden(binding.footer));
|
|
218
|
+
if (existing?.last) continue;
|
|
219
|
+
// First registration during a transition: fall through and measure available:false.
|
|
220
|
+
}
|
|
221
|
+
if (binding.active && existing && isCurrent(existing) && existing.last && !pageUnavailable) {
|
|
222
|
+
const back = binding.footer.querySelector<HTMLIonButtonElement>('ion-buttons[slot=start] ion-button');
|
|
223
|
+
// Close must stay eligible; otherwise fall through and demote instead of caching active.
|
|
224
|
+
if (back && !back.disabled && !back.closest(excluded)) {
|
|
225
|
+
candidate.control.search = {
|
|
226
|
+
...existing.last,
|
|
227
|
+
active: true,
|
|
228
|
+
available: true,
|
|
229
|
+
focused: binding.focused,
|
|
230
|
+
value: existing.bar.value ?? '',
|
|
231
|
+
placeholder: existing.bar.placeholder ?? '',
|
|
232
|
+
disabled: existing.bar.disabled,
|
|
233
|
+
editSequence: existing.editSequence,
|
|
234
|
+
valueVersion: existing.valueVersion,
|
|
235
|
+
};
|
|
236
|
+
existing.last = candidate.control.search;
|
|
237
|
+
candidate.sources = [binding.tabBar, binding.trigger.closest<HTMLElement>('ion-fab') ?? binding.trigger, binding.footer];
|
|
238
|
+
candidate.actions.set(existing.last.trigger.id, binding.trigger);
|
|
239
|
+
candidate.actions.set(existing.last.closeId, back);
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
if (!visible(binding.trigger) && !pageUnavailable) {
|
|
244
|
+
retainInactive(candidate, existing);
|
|
159
245
|
continue;
|
|
160
246
|
}
|
|
161
247
|
const bar = binding.footer.querySelector<HTMLIonSearchbarElement>('ion-searchbar');
|
|
@@ -168,11 +254,13 @@ export const createSearchSupport = (doc: Document, id: (element: Element) => str
|
|
|
168
254
|
!binding.footer.matches('ion-footer') ||
|
|
169
255
|
!inFixedToolbar(bar) ||
|
|
170
256
|
!inFixedToolbar(back) ||
|
|
171
|
-
bar.closest(excluded) ||
|
|
257
|
+
(!pageUnavailable && bar.closest(excluded)) ||
|
|
172
258
|
!bar.classList.contains('ios') ||
|
|
173
259
|
bar.classList.contains('searchbar-classic')
|
|
174
|
-
)
|
|
260
|
+
) {
|
|
261
|
+
retainInactive(candidate, existing);
|
|
175
262
|
continue;
|
|
263
|
+
}
|
|
176
264
|
if (
|
|
177
265
|
bar.color ||
|
|
178
266
|
back.fill !== 'default' ||
|
|
@@ -187,14 +275,19 @@ export const createSearchSupport = (doc: Document, id: (element: Element) => str
|
|
|
187
275
|
bar.spellcheck ||
|
|
188
276
|
bar.showClearButton !== 'always' ||
|
|
189
277
|
bar.clearIcon
|
|
190
|
-
)
|
|
278
|
+
) {
|
|
279
|
+
retainInactive(candidate, existing);
|
|
191
280
|
continue;
|
|
192
|
-
|
|
281
|
+
}
|
|
282
|
+
if (bar.showCancelButton !== 'never' || bar.type !== 'search' || !getComputedStyle(input).backdropFilter.includes('blur')) {
|
|
283
|
+
retainInactive(candidate, existing);
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
193
286
|
const fabButton = binding.trigger as HTMLIonFabButtonElement;
|
|
194
287
|
const nativeTrigger = fabButton.shadowRoot?.querySelector<HTMLElement>('[part=native]');
|
|
195
288
|
if (
|
|
196
|
-
binding.trigger.closest(excluded) ||
|
|
197
|
-
back.closest(excluded) ||
|
|
289
|
+
(!pageUnavailable && binding.trigger.closest(excluded)) ||
|
|
290
|
+
(!pageUnavailable && back.closest(excluded)) ||
|
|
198
291
|
back.disabled ||
|
|
199
292
|
fabButton.disabled ||
|
|
200
293
|
fabButton.color ||
|
|
@@ -203,15 +296,20 @@ export const createSearchSupport = (doc: Document, id: (element: Element) => str
|
|
|
203
296
|
!nativeTrigger ||
|
|
204
297
|
getComputedStyle(fabButton).backgroundColor !== 'rgba(0, 0, 0, 0)' ||
|
|
205
298
|
!getComputedStyle(nativeTrigger).backdropFilter.includes('blur')
|
|
206
|
-
)
|
|
299
|
+
) {
|
|
300
|
+
retainInactive(candidate, existing);
|
|
207
301
|
continue;
|
|
302
|
+
}
|
|
303
|
+
const state = existing ?? install(binding, bar, input);
|
|
208
304
|
const trigger = readIcon(binding.trigger, binding.trigger, candidate);
|
|
209
305
|
const searchIcon = bar.querySelector<HTMLElement>('.searchbar-search-icon');
|
|
210
306
|
const field = searchIcon && readIcon(searchIcon, bar, candidate);
|
|
211
|
-
if (!trigger || !field)
|
|
307
|
+
if (!trigger || !field) {
|
|
308
|
+
retainInactive(candidate, state);
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
212
311
|
field.label = '';
|
|
213
312
|
field.accessibilityLabel = bar.getAttribute('aria-label') ?? input.getAttribute('aria-label') ?? 'Search';
|
|
214
|
-
const state = states.get(binding) ?? install(binding, bar, input);
|
|
215
313
|
state.layout = JSON.stringify([
|
|
216
314
|
innerWidth,
|
|
217
315
|
innerHeight,
|
|
@@ -231,15 +329,19 @@ export const createSearchSupport = (doc: Document, id: (element: Element) => str
|
|
|
231
329
|
bar.placeholder,
|
|
232
330
|
bar.disabled,
|
|
233
331
|
]);
|
|
234
|
-
if (state.rejectedLayout === state.layout)
|
|
332
|
+
if (state.rejectedLayout === state.layout) {
|
|
333
|
+
retainInactive(candidate, state);
|
|
334
|
+
continue;
|
|
335
|
+
}
|
|
336
|
+
const available = !pageUnavailable && visible(binding.trigger);
|
|
235
337
|
candidate.control.search = {
|
|
236
338
|
id: id(input),
|
|
237
339
|
field,
|
|
238
340
|
trigger,
|
|
239
341
|
closeId: id(back),
|
|
240
|
-
active: binding.active,
|
|
241
|
-
available
|
|
242
|
-
focused: binding.focused,
|
|
342
|
+
active: available && binding.active,
|
|
343
|
+
available,
|
|
344
|
+
focused: available && binding.focused,
|
|
243
345
|
value: bar.value ?? '',
|
|
244
346
|
placeholder: bar.placeholder ?? '',
|
|
245
347
|
disabled: bar.disabled,
|
|
@@ -247,19 +349,36 @@ export const createSearchSupport = (doc: Document, id: (element: Element) => str
|
|
|
247
349
|
valueVersion: state.valueVersion,
|
|
248
350
|
};
|
|
249
351
|
state.last = candidate.control.search;
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
352
|
+
if (available) {
|
|
353
|
+
candidate.sources = [binding.tabBar, binding.trigger.closest<HTMLElement>('ion-fab') ?? binding.trigger, binding.footer];
|
|
354
|
+
candidate.actions.set(id(binding.trigger), binding.trigger);
|
|
355
|
+
candidate.actions.set(id(back), back);
|
|
356
|
+
}
|
|
253
357
|
}
|
|
254
358
|
for (const [candidate, configuration] of inactive) candidate.control.search ??= configuration;
|
|
255
359
|
const groups = candidates.filter((c) => c.control.search);
|
|
256
360
|
return candidates.filter((c) => !groups.some((group) => group !== c && group.sources?.some((source) => source.contains(c.element))));
|
|
257
361
|
},
|
|
258
362
|
projected,
|
|
363
|
+
hasActive() {
|
|
364
|
+
for (const [binding] of states) if (binding.active && projected(binding)) return true;
|
|
365
|
+
return false;
|
|
366
|
+
},
|
|
259
367
|
begin(binding: NativeSearchBinding, revision: number) {
|
|
260
368
|
const state = states.get(binding);
|
|
261
369
|
if (state) state.minimumRevision = revision;
|
|
262
370
|
},
|
|
371
|
+
/** Enter / focus / leave. `focused` is ignored when leaving. */
|
|
372
|
+
setStatus(binding: NativeSearchBinding, active: boolean, focused = false) {
|
|
373
|
+
if (!active) {
|
|
374
|
+
this.retire(binding);
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
binding.active = true;
|
|
378
|
+
// Projection intent only. Ionic focus/blur events still wait for the native phase.
|
|
379
|
+
binding.focused = focused;
|
|
380
|
+
holdKeyboardResize();
|
|
381
|
+
},
|
|
263
382
|
release(element: HTMLElement) {
|
|
264
383
|
for (const binding of states.keys()) if (binding.footer === element) this.retire(binding);
|
|
265
384
|
},
|
|
@@ -267,8 +386,7 @@ export const createSearchSupport = (doc: Document, id: (element: Element) => str
|
|
|
267
386
|
states.forEach((state) => {
|
|
268
387
|
if (ids.includes(id(state.binding.tabBar))) {
|
|
269
388
|
state.rejectedLayout = state.layout;
|
|
270
|
-
//
|
|
271
|
-
// cached configuration on an inactive page; project ordinary tabs instead.
|
|
389
|
+
// UIKit removed the search surface — do not reuse its cache; project ordinary tabs.
|
|
272
390
|
state.last = undefined;
|
|
273
391
|
}
|
|
274
392
|
});
|
|
@@ -319,6 +437,7 @@ export const createSearchSupport = (doc: Document, id: (element: Element) => str
|
|
|
319
437
|
state.input.dispatchEvent(new FocusEvent('blur'));
|
|
320
438
|
}
|
|
321
439
|
binding.active = binding.focused = false;
|
|
440
|
+
releaseKeyboardResize();
|
|
322
441
|
},
|
|
323
442
|
destroy() {
|
|
324
443
|
states.forEach((state) => {
|
|
@@ -326,6 +445,7 @@ export const createSearchSupport = (doc: Document, id: (element: Element) => str
|
|
|
326
445
|
state.restore();
|
|
327
446
|
});
|
|
328
447
|
states.clear();
|
|
448
|
+
releaseKeyboardResize();
|
|
329
449
|
},
|
|
330
450
|
};
|
|
331
451
|
};
|
package/src/native/runtime.ts
CHANGED
|
@@ -148,8 +148,8 @@ export const createRuntime = async (
|
|
|
148
148
|
search.keepSearchTabsVisible();
|
|
149
149
|
for (const page of pages) if (!page.isConnected) pages.delete(page);
|
|
150
150
|
for (const surface of moving.keys()) if (!surface.isConnected) moving.delete(surface);
|
|
151
|
-
if (doc.hidden || overlayOpen()
|
|
152
|
-
|
|
151
|
+
if (doc.hidden || overlayOpen()) return [];
|
|
152
|
+
if (win.visualViewport && (win.visualViewport.scale !== 1 || win.visualViewport.offsetTop !== 0) && !search.hasActive()) return [];
|
|
153
153
|
return unprojected(sources.keys(), () =>
|
|
154
154
|
search
|
|
155
155
|
.decorate(
|
|
@@ -246,6 +246,7 @@ export const createRuntime = async (
|
|
|
246
246
|
}
|
|
247
247
|
if (result.rejectedSearches?.length) {
|
|
248
248
|
search.reject(result.rejectedSearches);
|
|
249
|
+
lastSnapshot = '';
|
|
249
250
|
dirty = true;
|
|
250
251
|
}
|
|
251
252
|
for (const candidate of candidates) {
|
|
@@ -496,10 +497,14 @@ export const createRuntime = async (
|
|
|
496
497
|
setNativeUIShellIntegration(doc, {
|
|
497
498
|
async search(binding, active, focus) {
|
|
498
499
|
if (!getNativeSearchBindings(doc).includes(binding) || !search.projected(binding)) return false;
|
|
499
|
-
if (
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
500
|
+
if (!active) {
|
|
501
|
+
search.setStatus(binding, false);
|
|
502
|
+
} else {
|
|
503
|
+
const entering = !binding.active;
|
|
504
|
+
if (entering) search.begin(binding, revision + 1);
|
|
505
|
+
const nextFocused = focus === true ? true : focus === false ? false : entering ? false : binding.focused;
|
|
506
|
+
search.setStatus(binding, true, nextFocused);
|
|
507
|
+
}
|
|
503
508
|
await flush();
|
|
504
509
|
return !stopped && search.projected(binding);
|
|
505
510
|
},
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
$groups: ion-item-group, ion-reorder-group, ion-accordion-group, ion-radio-group;
|
|
2
|
+
|
|
3
|
+
@function _disabled-suffix($disabled-selector) {
|
|
4
|
+
@if $disabled-selector {
|
|
5
|
+
@return ':not(#{$disabled-selector})';
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
@return '';
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// Derive neutral list colors from the application's Ionic medium palette.
|
|
12
|
+
@mixin colors($supporting-opacity, $detail-opacity) {
|
|
13
|
+
ion-item {
|
|
14
|
+
--detail-icon-color: rgb(var(--ion-color-medium-rgb));
|
|
15
|
+
--detail-icon-opacity: #{$detail-opacity};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
&.list-inset {
|
|
19
|
+
ion-list-header ion-label:not(.ion-color) {
|
|
20
|
+
color: rgba(var(--ion-color-medium-rgb), $supporting-opacity);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
ion-list-header ion-note:not(.ion-color),
|
|
24
|
+
> ion-note:not(.ion-color) {
|
|
25
|
+
--color: rgba(var(--ion-color-medium-rgb), #{$supporting-opacity});
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
@mixin support-text($disabled-selector: null) {
|
|
31
|
+
$d: _disabled-suffix($disabled-selector);
|
|
32
|
+
|
|
33
|
+
ion-item#{$d} {
|
|
34
|
+
&:has(ion-input.input-fill-outline),
|
|
35
|
+
&:has(ion-textarea.textarea-fill-outline),
|
|
36
|
+
&:has(ion-select:is([helpertext]:not([helpertext='']), [errortext]:not([errortext='']))),
|
|
37
|
+
&:has(ion-input .input-bottom > :is(.helper-text:not(:empty), .error-text:not(:empty), .counter:not(:empty))),
|
|
38
|
+
&:has(ion-textarea .textarea-bottom > :is(.helper-text:not(:empty), .error-text:not(:empty), .counter:not(:empty))) {
|
|
39
|
+
--inner-border-width: 0;
|
|
40
|
+
--inner-padding-bottom: 4px;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
ion-input#{$d} .input-bottom:not(:has(> :is(.helper-text:not(:empty), .error-text:not(:empty), .counter:not(:empty)))),
|
|
44
|
+
ion-textarea#{$d} .textarea-bottom:not(:has(> :is(.helper-text:not(:empty), .error-text:not(:empty), .counter:not(:empty)))) {
|
|
45
|
+
display: none;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
@mixin layout($outer-note-inset, $group-border-radius, $variant: classic, $disabled-selector: null) {
|
|
51
|
+
$d: _disabled-suffix($disabled-selector);
|
|
52
|
+
$is-ios27: $variant == ios27;
|
|
53
|
+
|
|
54
|
+
background: transparent;
|
|
55
|
+
|
|
56
|
+
ion-list-header#{$d} {
|
|
57
|
+
display: flex;
|
|
58
|
+
align-items: center;
|
|
59
|
+
|
|
60
|
+
ion-label#{$d} {
|
|
61
|
+
@if $is-ios27 {
|
|
62
|
+
font-size: 1rem;
|
|
63
|
+
line-height: 1.29rem;
|
|
64
|
+
font-weight: 600;
|
|
65
|
+
} @else {
|
|
66
|
+
font-size: 0.9rem;
|
|
67
|
+
}
|
|
68
|
+
margin: 8px 4px 4px;
|
|
69
|
+
|
|
70
|
+
&:not(.ion-color) {
|
|
71
|
+
color: var(--ion-color-medium-tint);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
ion-button#{$d} {
|
|
76
|
+
margin-top: 8px;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
ion-note#{$d} {
|
|
80
|
+
@if $is-ios27 {
|
|
81
|
+
font-size: 0.76rem;
|
|
82
|
+
line-height: 1.06rem;
|
|
83
|
+
font-weight: 400;
|
|
84
|
+
margin-block: 8px 4px;
|
|
85
|
+
margin-inline: 0 16px;
|
|
86
|
+
|
|
87
|
+
&:not(.ion-color) {
|
|
88
|
+
--color: var(--ion-color-medium-tint);
|
|
89
|
+
}
|
|
90
|
+
} @else {
|
|
91
|
+
font-size: 0.9rem;
|
|
92
|
+
margin-block: 8px 4px;
|
|
93
|
+
margin-inline: 0 16px;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
@if $is-ios27 {
|
|
99
|
+
ion-item-group.item-group-header#{$d} {
|
|
100
|
+
border-radius: $group-border-radius;
|
|
101
|
+
|
|
102
|
+
> ion-item#{$d} {
|
|
103
|
+
--inner-padding-end: 0;
|
|
104
|
+
--inner-border-width: 0;
|
|
105
|
+
|
|
106
|
+
&::part(native) {
|
|
107
|
+
padding-inline: 0;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
& > ion-item#{$d} > ion-label#{$d} {
|
|
112
|
+
width: 100%;
|
|
113
|
+
margin: 0;
|
|
114
|
+
text-align: start;
|
|
115
|
+
white-space: normal;
|
|
116
|
+
padding: 16px 20px;
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* ion-header.header-collapse-condenseを使って、スクロールによってHeaderを変化させるトリック
|
|
120
|
+
*/
|
|
121
|
+
ion-header.header-collapse-condense > ion-toolbar {
|
|
122
|
+
--min-height: 0;
|
|
123
|
+
visibility: hidden;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
ion-icon#{$d} {
|
|
127
|
+
font-size: 2.82rem;
|
|
128
|
+
border-radius: 20%;
|
|
129
|
+
padding: 6px;
|
|
130
|
+
vertical-align: top;
|
|
131
|
+
|
|
132
|
+
&:not(.ion-color) {
|
|
133
|
+
color: #ffffff;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
h2 {
|
|
138
|
+
font-size: 1.29rem;
|
|
139
|
+
font-weight: 700;
|
|
140
|
+
line-height: 1.53rem;
|
|
141
|
+
margin: 20px 0 8px;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
ion-text#{$d} {
|
|
145
|
+
display: block;
|
|
146
|
+
font-size: 1rem;
|
|
147
|
+
line-height: 1.29rem;
|
|
148
|
+
|
|
149
|
+
&:not(.ion-color) {
|
|
150
|
+
color: var(--ion-color-step-500, var(--ion-text-color-step-500, #808080));
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
} @else {
|
|
156
|
+
ion-item-group.item-group-header#{$d} {
|
|
157
|
+
& > ion-item#{$d} > ion-label#{$d} {
|
|
158
|
+
width: 100%;
|
|
159
|
+
text-align: center;
|
|
160
|
+
padding: 32px 16px 16px;
|
|
161
|
+
transform: translateX(5px);
|
|
162
|
+
|
|
163
|
+
&:dir(rtl) {
|
|
164
|
+
transform: translateX(-5px);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* ion-header.header-collapse-condenseを使って、スクロールによってHeaderを変化させるトリック
|
|
169
|
+
*/
|
|
170
|
+
ion-header.header-collapse-condense > ion-toolbar {
|
|
171
|
+
--min-height: 0;
|
|
172
|
+
visibility: hidden;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
ion-icon#{$d} {
|
|
176
|
+
font-size: 2.8rem;
|
|
177
|
+
border-radius: 20%;
|
|
178
|
+
padding: 6px;
|
|
179
|
+
|
|
180
|
+
&:not(.ion-color) {
|
|
181
|
+
color: #ffffff;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
h2 {
|
|
186
|
+
font-size: 1.2rem;
|
|
187
|
+
font-weight: bold;
|
|
188
|
+
margin-top: 8px;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
ion-text#{$d} {
|
|
192
|
+
font-size: 0.9rem;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
> :is(#{$groups})#{$d} {
|
|
199
|
+
display: block;
|
|
200
|
+
border-radius: $group-border-radius;
|
|
201
|
+
overflow: hidden;
|
|
202
|
+
|
|
203
|
+
&:has(> .radio-group-top) {
|
|
204
|
+
display: flex;
|
|
205
|
+
flex-direction: column;
|
|
206
|
+
border-radius: 0;
|
|
207
|
+
overflow: visible;
|
|
208
|
+
|
|
209
|
+
> .radio-group-top {
|
|
210
|
+
order: 1;
|
|
211
|
+
font-size: 0.9rem;
|
|
212
|
+
padding-block-start: 8px;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
> ion-item#{$d}:first-of-type {
|
|
216
|
+
--border-radius: #{$group-border-radius} #{$group-border-radius} 0 0;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
> ion-item#{$d}:last-of-type {
|
|
220
|
+
--border-radius: 0 0 #{$group-border-radius} #{$group-border-radius};
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
&:not(:first-of-type) {
|
|
225
|
+
margin-top: 16px;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
ion-item#{$d} {
|
|
229
|
+
&:has(> ion-label:not([slot])):has(> ion-note:not([slot])) {
|
|
230
|
+
&::part(container) {
|
|
231
|
+
flex-direction: column;
|
|
232
|
+
justify-content: center;
|
|
233
|
+
padding-top: 14px;
|
|
234
|
+
padding-bottom: 14px;
|
|
235
|
+
min-height: 64px;
|
|
236
|
+
}
|
|
237
|
+
& > ion-label#{$d},
|
|
238
|
+
& > ion-note#{$d} {
|
|
239
|
+
width: auto;
|
|
240
|
+
align-self: flex-start;
|
|
241
|
+
}
|
|
242
|
+
& > ion-label#{$d} {
|
|
243
|
+
margin: 0;
|
|
244
|
+
font-weight: 500;
|
|
245
|
+
@if $is-ios27 {
|
|
246
|
+
font-size: 1rem;
|
|
247
|
+
line-height: 1.29rem;
|
|
248
|
+
} @else {
|
|
249
|
+
font-size: 1.15rem;
|
|
250
|
+
line-height: 0.9rem;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
& > ion-note#{$d} {
|
|
254
|
+
@if $is-ios27 {
|
|
255
|
+
font-size: 0.88rem;
|
|
256
|
+
line-height: 1.18rem;
|
|
257
|
+
} @else {
|
|
258
|
+
font-size: 0.85rem;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
ion-text[slot='end']#{$d} {
|
|
264
|
+
padding-inline-start: 8px;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
&.item-disabled {
|
|
268
|
+
--detail-icon-opacity: 0.1;
|
|
269
|
+
opacity: 1;
|
|
270
|
+
|
|
271
|
+
& > * {
|
|
272
|
+
opacity: 0.4;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
ion-button[slot='end']#{$d} {
|
|
277
|
+
&.ion-align-self-end {
|
|
278
|
+
transform: translateY(-7px);
|
|
279
|
+
}
|
|
280
|
+
ion-icon[slot='icon-only']#{$d} {
|
|
281
|
+
font-size: 1.2rem;
|
|
282
|
+
transform: translateY(4px);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
& > ion-note#{$d} {
|
|
289
|
+
margin-block: 8px;
|
|
290
|
+
margin-inline: calc(var(--ion-safe-area-left, 0) + $outer-note-inset) calc(var(--ion-safe-area-right, 0) + $outer-note-inset);
|
|
291
|
+
|
|
292
|
+
&:dir(rtl) {
|
|
293
|
+
margin-inline: calc(var(--ion-safe-area-right, 0) + $outer-note-inset) calc(var(--ion-safe-area-left, 0) + $outer-note-inset);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
@if $is-ios27 {
|
|
297
|
+
font-size: 0.76rem;
|
|
298
|
+
line-height: 1.06rem;
|
|
299
|
+
font-weight: 400;
|
|
300
|
+
display: block;
|
|
301
|
+
&:not(.ion-color) {
|
|
302
|
+
--color: var(--ion-color-medium-tint);
|
|
303
|
+
}
|
|
304
|
+
} @else {
|
|
305
|
+
--color: var(--ion-color-medium-tint);
|
|
306
|
+
font-size: 0.9rem;
|
|
307
|
+
display: block;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|